shakapacker 9.0.0.beta.6 → 9.0.0.beta.7
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.
- checksums.yaml +4 -4
- data/.github/workflows/generator.yml +6 -0
- data/.gitignore +0 -1
- data/.npmignore +55 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -0
- data/docs/transpiler-migration.md +191 -0
- data/docs/typescript-migration.md +378 -0
- data/lib/install/template.rb +54 -7
- data/lib/shakapacker/version.rb +1 -1
- data/package/.npmignore +4 -0
- data/package/config.ts +23 -10
- data/package/env.ts +15 -2
- data/package/environments/{development.js → development.ts} +30 -8
- data/package/environments/{production.js → production.ts} +18 -4
- data/package/environments/test.ts +53 -0
- data/package/environments/types.ts +90 -0
- data/package/types/README.md +87 -0
- data/package/types/index.ts +60 -0
- data/package/utils/errorCodes.ts +219 -0
- data/package/utils/errorHelpers.ts +68 -2
- data/package/utils/pathValidation.ts +139 -0
- data/package/utils/typeGuards.ts +161 -47
- data/package.json +5 -3
- data/scripts/remove-use-strict.js +45 -0
- data/test/package/transpiler-defaults.test.js +127 -0
- data/test/scripts/remove-use-strict.test.js +125 -0
- data/test/typescript/build.test.js +3 -2
- data/test/typescript/environments.test.js +107 -0
- data/test/typescript/pathValidation.test.js +142 -0
- data/test/typescript/securityValidation.test.js +182 -0
- metadata +25 -6
- data/package/environments/base.js +0 -103
- data/package/environments/test.js +0 -19
@@ -0,0 +1,219 @@
|
|
1
|
+
/**
|
2
|
+
* Error codes for programmatic error handling in Shakapacker
|
3
|
+
* These codes allow consumers to handle specific errors programmatically
|
4
|
+
* @module shakapacker/utils/errorCodes
|
5
|
+
*/
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Error code enumeration for Shakapacker errors
|
9
|
+
*/
|
10
|
+
export enum ErrorCode {
|
11
|
+
// Configuration errors (1xxx)
|
12
|
+
CONFIG_NOT_FOUND = 'SHAKAPACKER_1001',
|
13
|
+
CONFIG_INVALID_YAML = 'SHAKAPACKER_1002',
|
14
|
+
CONFIG_MISSING_REQUIRED = 'SHAKAPACKER_1003',
|
15
|
+
CONFIG_VALIDATION_FAILED = 'SHAKAPACKER_1004',
|
16
|
+
CONFIG_MERGE_FAILED = 'SHAKAPACKER_1005',
|
17
|
+
CONFIG_TYPE_MISMATCH = 'SHAKAPACKER_1006',
|
18
|
+
|
19
|
+
// File system errors (2xxx)
|
20
|
+
FILE_NOT_FOUND = 'SHAKAPACKER_2001',
|
21
|
+
FILE_READ_ERROR = 'SHAKAPACKER_2002',
|
22
|
+
FILE_WRITE_ERROR = 'SHAKAPACKER_2003',
|
23
|
+
FILE_PERMISSION_DENIED = 'SHAKAPACKER_2004',
|
24
|
+
PATH_TRAVERSAL_DETECTED = 'SHAKAPACKER_2005',
|
25
|
+
INVALID_PATH = 'SHAKAPACKER_2006',
|
26
|
+
|
27
|
+
// Module errors (3xxx)
|
28
|
+
MODULE_NOT_FOUND = 'SHAKAPACKER_3001',
|
29
|
+
MODULE_LOAD_FAILED = 'SHAKAPACKER_3002',
|
30
|
+
MODULE_INVALID_EXPORT = 'SHAKAPACKER_3003',
|
31
|
+
LOADER_NOT_FOUND = 'SHAKAPACKER_3004',
|
32
|
+
PLUGIN_NOT_FOUND = 'SHAKAPACKER_3005',
|
33
|
+
PLUGIN_INVALID = 'SHAKAPACKER_3006',
|
34
|
+
|
35
|
+
// Environment errors (4xxx)
|
36
|
+
ENV_INVALID_NODE_ENV = 'SHAKAPACKER_4001',
|
37
|
+
ENV_MISSING_REQUIRED = 'SHAKAPACKER_4002',
|
38
|
+
ENV_INVALID_VALUE = 'SHAKAPACKER_4003',
|
39
|
+
ENV_SANITIZATION_REQUIRED = 'SHAKAPACKER_4004',
|
40
|
+
|
41
|
+
// Bundler errors (5xxx)
|
42
|
+
BUNDLER_UNSUPPORTED = 'SHAKAPACKER_5001',
|
43
|
+
BUNDLER_CONFIG_INVALID = 'SHAKAPACKER_5002',
|
44
|
+
WEBPACK_CONFIG_INVALID = 'SHAKAPACKER_5003',
|
45
|
+
RSPACK_CONFIG_INVALID = 'SHAKAPACKER_5004',
|
46
|
+
TRANSPILER_NOT_FOUND = 'SHAKAPACKER_5005',
|
47
|
+
TRANSPILER_CONFIG_INVALID = 'SHAKAPACKER_5006',
|
48
|
+
|
49
|
+
// Dev server errors (6xxx)
|
50
|
+
DEVSERVER_CONFIG_INVALID = 'SHAKAPACKER_6001',
|
51
|
+
DEVSERVER_PORT_INVALID = 'SHAKAPACKER_6002',
|
52
|
+
DEVSERVER_PORT_IN_USE = 'SHAKAPACKER_6003',
|
53
|
+
DEVSERVER_START_FAILED = 'SHAKAPACKER_6004',
|
54
|
+
|
55
|
+
// Security errors (7xxx)
|
56
|
+
SECURITY_PATH_TRAVERSAL = 'SHAKAPACKER_7001',
|
57
|
+
SECURITY_INVALID_INPUT = 'SHAKAPACKER_7002',
|
58
|
+
SECURITY_CONTROL_CHARS = 'SHAKAPACKER_7003',
|
59
|
+
SECURITY_INJECTION_ATTEMPT = 'SHAKAPACKER_7004',
|
60
|
+
|
61
|
+
// Validation errors (8xxx)
|
62
|
+
VALIDATION_FAILED = 'SHAKAPACKER_8001',
|
63
|
+
VALIDATION_TYPE_ERROR = 'SHAKAPACKER_8002',
|
64
|
+
VALIDATION_RANGE_ERROR = 'SHAKAPACKER_8003',
|
65
|
+
VALIDATION_FORMAT_ERROR = 'SHAKAPACKER_8004',
|
66
|
+
VALIDATION_CONSTRAINT_ERROR = 'SHAKAPACKER_8005',
|
67
|
+
|
68
|
+
// Generic errors (9xxx)
|
69
|
+
UNKNOWN_ERROR = 'SHAKAPACKER_9000',
|
70
|
+
INTERNAL_ERROR = 'SHAKAPACKER_9001',
|
71
|
+
DEPRECATED_FEATURE = 'SHAKAPACKER_9002',
|
72
|
+
NOT_IMPLEMENTED = 'SHAKAPACKER_9003',
|
73
|
+
OPERATION_FAILED = 'SHAKAPACKER_9004'
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Error message templates for each error code
|
78
|
+
*/
|
79
|
+
export const ErrorMessages: Record<ErrorCode, string> = {
|
80
|
+
// Configuration errors
|
81
|
+
[ErrorCode.CONFIG_NOT_FOUND]: 'Configuration file not found: {path}',
|
82
|
+
[ErrorCode.CONFIG_INVALID_YAML]: 'Invalid YAML in configuration file: {path}',
|
83
|
+
[ErrorCode.CONFIG_MISSING_REQUIRED]: 'Missing required configuration field: {field}',
|
84
|
+
[ErrorCode.CONFIG_VALIDATION_FAILED]: 'Configuration validation failed: {reason}',
|
85
|
+
[ErrorCode.CONFIG_MERGE_FAILED]: 'Failed to merge configurations: {reason}',
|
86
|
+
[ErrorCode.CONFIG_TYPE_MISMATCH]: 'Configuration type mismatch for {field}: expected {expected}, got {actual}',
|
87
|
+
|
88
|
+
// File system errors
|
89
|
+
[ErrorCode.FILE_NOT_FOUND]: 'File not found: {path}',
|
90
|
+
[ErrorCode.FILE_READ_ERROR]: 'Error reading file: {path}',
|
91
|
+
[ErrorCode.FILE_WRITE_ERROR]: 'Error writing file: {path}',
|
92
|
+
[ErrorCode.FILE_PERMISSION_DENIED]: 'Permission denied accessing: {path}',
|
93
|
+
[ErrorCode.PATH_TRAVERSAL_DETECTED]: 'Path traversal attempt detected: {path}',
|
94
|
+
[ErrorCode.INVALID_PATH]: 'Invalid path: {path}',
|
95
|
+
|
96
|
+
// Module errors
|
97
|
+
[ErrorCode.MODULE_NOT_FOUND]: 'Module not found: {module}',
|
98
|
+
[ErrorCode.MODULE_LOAD_FAILED]: 'Failed to load module: {module}',
|
99
|
+
[ErrorCode.MODULE_INVALID_EXPORT]: 'Invalid export from module: {module}',
|
100
|
+
[ErrorCode.LOADER_NOT_FOUND]: 'Loader not found: {loader}',
|
101
|
+
[ErrorCode.PLUGIN_NOT_FOUND]: 'Plugin not found: {plugin}',
|
102
|
+
[ErrorCode.PLUGIN_INVALID]: 'Invalid plugin: {plugin}',
|
103
|
+
|
104
|
+
// Environment errors
|
105
|
+
[ErrorCode.ENV_INVALID_NODE_ENV]: 'Invalid NODE_ENV value: {value}. Valid values are: {valid}',
|
106
|
+
[ErrorCode.ENV_MISSING_REQUIRED]: 'Missing required environment variable: {variable}',
|
107
|
+
[ErrorCode.ENV_INVALID_VALUE]: 'Invalid value for environment variable {variable}: {value}',
|
108
|
+
[ErrorCode.ENV_SANITIZATION_REQUIRED]: 'Environment variable {variable} contained unsafe characters and was sanitized',
|
109
|
+
|
110
|
+
// Bundler errors
|
111
|
+
[ErrorCode.BUNDLER_UNSUPPORTED]: 'Unsupported bundler: {bundler}',
|
112
|
+
[ErrorCode.BUNDLER_CONFIG_INVALID]: 'Invalid bundler configuration: {reason}',
|
113
|
+
[ErrorCode.WEBPACK_CONFIG_INVALID]: 'Invalid webpack configuration: {reason}',
|
114
|
+
[ErrorCode.RSPACK_CONFIG_INVALID]: 'Invalid rspack configuration: {reason}',
|
115
|
+
[ErrorCode.TRANSPILER_NOT_FOUND]: 'Transpiler not found: {transpiler}',
|
116
|
+
[ErrorCode.TRANSPILER_CONFIG_INVALID]: 'Invalid transpiler configuration: {reason}',
|
117
|
+
|
118
|
+
// Dev server errors
|
119
|
+
[ErrorCode.DEVSERVER_CONFIG_INVALID]: 'Invalid dev server configuration: {reason}',
|
120
|
+
[ErrorCode.DEVSERVER_PORT_INVALID]: 'Invalid port: {port}',
|
121
|
+
[ErrorCode.DEVSERVER_PORT_IN_USE]: 'Port {port} is already in use',
|
122
|
+
[ErrorCode.DEVSERVER_START_FAILED]: 'Failed to start dev server: {reason}',
|
123
|
+
|
124
|
+
// Security errors
|
125
|
+
[ErrorCode.SECURITY_PATH_TRAVERSAL]: 'Security: Path traversal attempt blocked: {path}',
|
126
|
+
[ErrorCode.SECURITY_INVALID_INPUT]: 'Security: Invalid input detected: {input}',
|
127
|
+
[ErrorCode.SECURITY_CONTROL_CHARS]: 'Security: Control characters detected and removed from: {field}',
|
128
|
+
[ErrorCode.SECURITY_INJECTION_ATTEMPT]: 'Security: Potential injection attempt blocked: {details}',
|
129
|
+
|
130
|
+
// Validation errors
|
131
|
+
[ErrorCode.VALIDATION_FAILED]: 'Validation failed: {reason}',
|
132
|
+
[ErrorCode.VALIDATION_TYPE_ERROR]: 'Type validation error: {field} should be {type}',
|
133
|
+
[ErrorCode.VALIDATION_RANGE_ERROR]: 'Value out of range: {field} must be between {min} and {max}',
|
134
|
+
[ErrorCode.VALIDATION_FORMAT_ERROR]: 'Format error: {field} does not match expected format',
|
135
|
+
[ErrorCode.VALIDATION_CONSTRAINT_ERROR]: 'Constraint violation: {constraint}',
|
136
|
+
|
137
|
+
// Generic errors
|
138
|
+
[ErrorCode.UNKNOWN_ERROR]: 'An unknown error occurred',
|
139
|
+
[ErrorCode.INTERNAL_ERROR]: 'Internal error: {details}',
|
140
|
+
[ErrorCode.DEPRECATED_FEATURE]: 'Deprecated feature: {feature}. Use {alternative} instead',
|
141
|
+
[ErrorCode.NOT_IMPLEMENTED]: 'Feature not yet implemented: {feature}',
|
142
|
+
[ErrorCode.OPERATION_FAILED]: 'Operation failed: {operation}'
|
143
|
+
}
|
144
|
+
|
145
|
+
/**
|
146
|
+
* Shakapacker error class with error code support
|
147
|
+
*/
|
148
|
+
export class ShakapackerError extends Error {
|
149
|
+
public readonly code: ErrorCode
|
150
|
+
public readonly details?: Record<string, any>
|
151
|
+
|
152
|
+
constructor(code: ErrorCode, details?: Record<string, any>, customMessage?: string) {
|
153
|
+
const template = ErrorMessages[code] || 'An error occurred'
|
154
|
+
const message = customMessage || ShakapackerError.formatMessage(template, details)
|
155
|
+
|
156
|
+
super(message)
|
157
|
+
this.name = 'ShakapackerError'
|
158
|
+
this.code = code
|
159
|
+
this.details = details
|
160
|
+
|
161
|
+
// Maintain proper stack trace for where error was thrown
|
162
|
+
if (Error.captureStackTrace) {
|
163
|
+
Error.captureStackTrace(this, ShakapackerError)
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Format error message with template values
|
169
|
+
*/
|
170
|
+
private static formatMessage(template: string, details?: Record<string, any>): string {
|
171
|
+
if (!details) return template
|
172
|
+
|
173
|
+
return template.replace(/{(\w+)}/g, (match, key) => {
|
174
|
+
const value = details[key]
|
175
|
+
if (value === undefined) return match
|
176
|
+
if (typeof value === 'object') {
|
177
|
+
return JSON.stringify(value)
|
178
|
+
}
|
179
|
+
return String(value)
|
180
|
+
})
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
* Convert error to JSON for logging or API responses
|
185
|
+
*/
|
186
|
+
toJSON(): Record<string, any> {
|
187
|
+
return {
|
188
|
+
name: this.name,
|
189
|
+
code: this.code,
|
190
|
+
message: this.message,
|
191
|
+
details: this.details,
|
192
|
+
stack: this.stack
|
193
|
+
}
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Helper function to create a Shakapacker error
|
199
|
+
*/
|
200
|
+
export function createError(code: ErrorCode, details?: Record<string, any>): ShakapackerError {
|
201
|
+
return new ShakapackerError(code, details)
|
202
|
+
}
|
203
|
+
|
204
|
+
/**
|
205
|
+
* Helper function to check if an error is a Shakapacker error
|
206
|
+
*/
|
207
|
+
export function isShakapackerError(error: unknown): error is ShakapackerError {
|
208
|
+
return error instanceof ShakapackerError
|
209
|
+
}
|
210
|
+
|
211
|
+
/**
|
212
|
+
* Helper function to get error code from any error
|
213
|
+
*/
|
214
|
+
export function getErrorCode(error: unknown): ErrorCode | null {
|
215
|
+
if (isShakapackerError(error)) {
|
216
|
+
return error.code
|
217
|
+
}
|
218
|
+
return null
|
219
|
+
}
|
@@ -2,6 +2,8 @@
|
|
2
2
|
* Error handling utilities for consistent error management
|
3
3
|
*/
|
4
4
|
|
5
|
+
import { ErrorCode, ShakapackerError } from './errorCodes'
|
6
|
+
|
5
7
|
/**
|
6
8
|
* Checks if an error is a file not found error (ENOENT)
|
7
9
|
*/
|
@@ -33,10 +35,31 @@ export function createFileOperationError(
|
|
33
35
|
operation: 'read' | 'write' | 'delete',
|
34
36
|
filePath: string,
|
35
37
|
details?: string
|
38
|
+
): ShakapackerError {
|
39
|
+
const errorCode = operation === 'read'
|
40
|
+
? ErrorCode.FILE_READ_ERROR
|
41
|
+
: operation === 'write'
|
42
|
+
? ErrorCode.FILE_WRITE_ERROR
|
43
|
+
: ErrorCode.FILE_NOT_FOUND
|
44
|
+
|
45
|
+
return new ShakapackerError(errorCode, {
|
46
|
+
path: filePath,
|
47
|
+
operation,
|
48
|
+
details
|
49
|
+
})
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
* Creates a consistent error message for file operations (backward compatibility)
|
54
|
+
*/
|
55
|
+
export function createFileOperationErrorLegacy(
|
56
|
+
operation: 'read' | 'write' | 'delete',
|
57
|
+
filePath: string,
|
58
|
+
details?: string
|
36
59
|
): Error {
|
37
60
|
const baseMessage = `Failed to ${operation} file at path '${filePath}'`
|
38
61
|
const errorDetails = details ? ` - ${details}` : ''
|
39
|
-
const suggestion = operation === 'read'
|
62
|
+
const suggestion = operation === 'read'
|
40
63
|
? ' (check if file exists and permissions are correct)'
|
41
64
|
: operation === 'write'
|
42
65
|
? ' (check write permissions and disk space)'
|
@@ -70,8 +93,51 @@ export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
70
93
|
return (
|
71
94
|
error instanceof Error &&
|
72
95
|
'code' in error &&
|
73
|
-
typeof (error as
|
96
|
+
typeof (error as NodeJS.ErrnoException).code === 'string'
|
74
97
|
)
|
75
98
|
}
|
76
99
|
|
100
|
+
/**
|
101
|
+
* Creates a configuration validation error
|
102
|
+
*/
|
103
|
+
export function createConfigValidationErrorWithCode(
|
104
|
+
configPath: string,
|
105
|
+
environment: string,
|
106
|
+
reason: string
|
107
|
+
): ShakapackerError {
|
108
|
+
return new ShakapackerError(ErrorCode.CONFIG_VALIDATION_FAILED, {
|
109
|
+
path: configPath,
|
110
|
+
environment,
|
111
|
+
reason
|
112
|
+
})
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Creates a module not found error
|
117
|
+
*/
|
118
|
+
export function createModuleNotFoundError(moduleName: string, details?: string): ShakapackerError {
|
119
|
+
return new ShakapackerError(ErrorCode.MODULE_NOT_FOUND, {
|
120
|
+
module: moduleName,
|
121
|
+
details
|
122
|
+
})
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* Creates a path traversal security error
|
127
|
+
*/
|
128
|
+
export function createPathTraversalError(path: string): ShakapackerError {
|
129
|
+
return new ShakapackerError(ErrorCode.SECURITY_PATH_TRAVERSAL, {
|
130
|
+
path
|
131
|
+
})
|
132
|
+
}
|
133
|
+
|
134
|
+
/**
|
135
|
+
* Creates a port validation error
|
136
|
+
*/
|
137
|
+
export function createPortValidationError(port: unknown): ShakapackerError {
|
138
|
+
return new ShakapackerError(ErrorCode.DEVSERVER_PORT_INVALID, {
|
139
|
+
port: String(port)
|
140
|
+
})
|
141
|
+
}
|
142
|
+
|
77
143
|
|
@@ -0,0 +1,139 @@
|
|
1
|
+
import * as path from "path"
|
2
|
+
import * as fs from "fs"
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Security utilities for validating and sanitizing file paths
|
6
|
+
*/
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Validates a path doesn't contain traversal patterns
|
10
|
+
*/
|
11
|
+
export function isPathTraversalSafe(inputPath: string): boolean {
|
12
|
+
// Check for common traversal patterns
|
13
|
+
// Null byte short-circuit (avoid regex with control chars)
|
14
|
+
if (inputPath.includes("\0")) return false
|
15
|
+
|
16
|
+
const dangerousPatterns = [
|
17
|
+
/\.\.[\/\\]/, // ../ or ..\
|
18
|
+
/^\//, // POSIX absolute
|
19
|
+
/^[A-Za-z]:[\/\\]/, // Windows absolute (C:\ or C:/)
|
20
|
+
/^\\\\/, // Windows UNC (\\server\share)
|
21
|
+
/~[\/\\]/, // Home directory expansion
|
22
|
+
/%2e%2e/i, // URL encoded traversal
|
23
|
+
]
|
24
|
+
|
25
|
+
return !dangerousPatterns.some(pattern => pattern.test(inputPath))
|
26
|
+
}
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Resolves and validates a path within a base directory
|
30
|
+
* Prevents directory traversal attacks by ensuring the resolved path
|
31
|
+
* stays within the base directory
|
32
|
+
*/
|
33
|
+
export function safeResolvePath(basePath: string, userPath: string): string {
|
34
|
+
// Normalize the base path
|
35
|
+
const normalizedBase = path.resolve(basePath)
|
36
|
+
|
37
|
+
// Resolve the user path relative to base
|
38
|
+
const resolved = path.resolve(normalizedBase, userPath)
|
39
|
+
|
40
|
+
// Ensure the resolved path is within the base directory
|
41
|
+
if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) {
|
42
|
+
throw new Error(
|
43
|
+
`[SHAKAPACKER SECURITY] Path traversal attempt detected.\n` +
|
44
|
+
`Requested path would resolve outside of allowed directory.\n` +
|
45
|
+
`Base: ${normalizedBase}\n` +
|
46
|
+
`Attempted: ${userPath}\n` +
|
47
|
+
`Resolved to: ${resolved}`
|
48
|
+
)
|
49
|
+
}
|
50
|
+
|
51
|
+
return resolved
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Validates that a path exists and is accessible
|
56
|
+
*/
|
57
|
+
export function validatePathExists(filePath: string): boolean {
|
58
|
+
try {
|
59
|
+
fs.accessSync(filePath, fs.constants.R_OK)
|
60
|
+
return true
|
61
|
+
} catch {
|
62
|
+
return false
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Validates an array of paths for security issues
|
68
|
+
*/
|
69
|
+
export function validatePaths(paths: string[], basePath: string): string[] {
|
70
|
+
const validatedPaths: string[] = []
|
71
|
+
|
72
|
+
for (const userPath of paths) {
|
73
|
+
if (!isPathTraversalSafe(userPath)) {
|
74
|
+
console.warn(
|
75
|
+
`[SHAKAPACKER WARNING] Skipping potentially unsafe path: ${userPath}`
|
76
|
+
)
|
77
|
+
continue
|
78
|
+
}
|
79
|
+
|
80
|
+
try {
|
81
|
+
const safePath = safeResolvePath(basePath, userPath)
|
82
|
+
validatedPaths.push(safePath)
|
83
|
+
} catch (error) {
|
84
|
+
console.warn(
|
85
|
+
`[SHAKAPACKER WARNING] Invalid path configuration: ${userPath}\n` +
|
86
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
87
|
+
)
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
return validatedPaths
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Sanitizes environment variable values to prevent injection
|
96
|
+
*/
|
97
|
+
export function sanitizeEnvValue(value: string | undefined): string | undefined {
|
98
|
+
if (!value) return value
|
99
|
+
|
100
|
+
// Remove control characters and null bytes
|
101
|
+
// Filter by character code to avoid control character regex (Biome compliance)
|
102
|
+
const sanitized = value.split('').filter(char => {
|
103
|
+
const code = char.charCodeAt(0)
|
104
|
+
// Keep chars with code > 31 (after control chars) and not 127 (DEL)
|
105
|
+
return code > 31 && code !== 127
|
106
|
+
}).join('')
|
107
|
+
|
108
|
+
// Warn if sanitization changed the value
|
109
|
+
if (sanitized !== value) {
|
110
|
+
console.warn(
|
111
|
+
`[SHAKAPACKER SECURITY] Environment variable value contained control characters that were removed`
|
112
|
+
)
|
113
|
+
}
|
114
|
+
|
115
|
+
return sanitized
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Validates a port number or string
|
120
|
+
*/
|
121
|
+
export function validatePort(port: unknown): boolean {
|
122
|
+
if (port === 'auto') return true
|
123
|
+
|
124
|
+
if (typeof port === 'number') {
|
125
|
+
return port > 0 && port <= 65535 && Number.isInteger(port)
|
126
|
+
}
|
127
|
+
|
128
|
+
if (typeof port === 'string') {
|
129
|
+
// First check if the string contains only digits
|
130
|
+
if (!/^\d+$/.test(port)) {
|
131
|
+
return false
|
132
|
+
}
|
133
|
+
// Only then parse and validate range
|
134
|
+
const num = parseInt(port, 10)
|
135
|
+
return num > 0 && num <= 65535
|
136
|
+
}
|
137
|
+
|
138
|
+
return false
|
139
|
+
}
|