mirror of
https://github.com/hanzoai/cross-env.git
synced 2026-09-24 16:58:15 +00:00
feat: add support ofr default value syntax
- Introduced a new script `test-default-values.js` to validate default value syntax in commands. - Updated `package.json` to include the new test script in the e2e test command. - Modified GitHub Actions workflow to run the new e2e tests. - Enhanced `commandConvert` function to support default value syntax in environment variables. - Added unit tests for the new functionality in `command-default-values.test.ts`.
This commit is contained in:
@@ -90,6 +90,9 @@ jobs:
|
||||
- name: 🐚 Run cross-env-shell e2e tests
|
||||
run: node e2e/test-cross-env-shell.js
|
||||
|
||||
- name: 🔧 Run default value syntax e2e tests
|
||||
run: node e2e/test-default-values.js
|
||||
|
||||
release:
|
||||
name: 🚀 Release
|
||||
needs: [build, e2e]
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from 'child_process'
|
||||
import { dirname, join } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
// Path to the built cross-env binary
|
||||
const crossEnvPath = join(__dirname, '..', 'dist', 'bin', 'cross-env.js')
|
||||
|
||||
function runCommand(command, args = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn('node', [command, ...args], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: { ...process.env },
|
||||
})
|
||||
|
||||
let stdout = ''
|
||||
let stderr = ''
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data.toString()
|
||||
})
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data.toString()
|
||||
})
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve({ stdout, stderr, code })
|
||||
} else {
|
||||
reject(new Error(`Command failed with code ${code}: ${stderr}`))
|
||||
}
|
||||
})
|
||||
|
||||
child.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
console.log('🧪 Running cross-env default value syntax e2e tests...')
|
||||
|
||||
const tests = [
|
||||
{
|
||||
name: 'Default value syntax when variable is undefined',
|
||||
test: async () => {
|
||||
// Test that the command conversion works by using echo to see the converted command
|
||||
const result = await runCommand(crossEnvPath, [
|
||||
'echo',
|
||||
'wrangler dev --port ${PORT:-8787}',
|
||||
])
|
||||
|
||||
// On Windows, this should convert to the default value
|
||||
// On Unix, it should remain unchanged
|
||||
const expected =
|
||||
process.platform === 'win32'
|
||||
? 'wrangler dev --port 8787'
|
||||
: 'wrangler dev --port ${PORT:-8787}'
|
||||
if (result.stdout.trim() !== expected) {
|
||||
throw new Error(
|
||||
`Expected '${expected}', got '${result.stdout.trim()}'`,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Default value syntax when variable is defined',
|
||||
test: async () => {
|
||||
const result = await runCommand(crossEnvPath, [
|
||||
'PORT=3000',
|
||||
'echo',
|
||||
'wrangler dev --port ${PORT:-8787}',
|
||||
])
|
||||
|
||||
// On Windows, this should convert to the defined value
|
||||
// On Unix, it should remain unchanged
|
||||
const expected =
|
||||
process.platform === 'win32'
|
||||
? 'wrangler dev --port 3000'
|
||||
: 'wrangler dev --port ${PORT:-8787}'
|
||||
if (result.stdout.trim() !== expected) {
|
||||
throw new Error(
|
||||
`Expected '${expected}', got '${result.stdout.trim()}'`,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Multiple default value syntaxes',
|
||||
test: async () => {
|
||||
const result = await runCommand(crossEnvPath, [
|
||||
'HOST=localhost',
|
||||
'echo',
|
||||
'wrangler dev --host ${HOST:-0.0.0.0} --port ${PORT:-8787}',
|
||||
])
|
||||
|
||||
// On Windows, should use defined value for HOST and default for PORT
|
||||
// On Unix, should remain unchanged
|
||||
const expected =
|
||||
process.platform === 'win32'
|
||||
? 'wrangler dev --host localhost --port 8787'
|
||||
: 'wrangler dev --host ${HOST:-0.0.0.0} --port ${PORT:-8787}'
|
||||
if (result.stdout.trim() !== expected) {
|
||||
throw new Error(
|
||||
`Expected '${expected}', got '${result.stdout.trim()}'`,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Default value with special characters',
|
||||
test: async () => {
|
||||
const result = await runCommand(crossEnvPath, [
|
||||
'echo',
|
||||
'node app.js --env ${NODE_ENV:-development}',
|
||||
])
|
||||
|
||||
// On Windows, should use the default value
|
||||
// On Unix, should remain unchanged
|
||||
const expected =
|
||||
process.platform === 'win32'
|
||||
? 'node app.js --env development'
|
||||
: 'node app.js --env ${NODE_ENV:-development}'
|
||||
if (result.stdout.trim() !== expected) {
|
||||
throw new Error(
|
||||
`Expected '${expected}', got '${result.stdout.trim()}'`,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Default value with spaces',
|
||||
test: async () => {
|
||||
const result = await runCommand(crossEnvPath, [
|
||||
'echo',
|
||||
'echo ${MESSAGE:-hello world}',
|
||||
])
|
||||
|
||||
// On Windows, should use the default value with spaces
|
||||
// On Unix, should remain unchanged
|
||||
const expected =
|
||||
process.platform === 'win32'
|
||||
? 'echo hello world'
|
||||
: 'echo ${MESSAGE:-hello world}'
|
||||
if (result.stdout.trim() !== expected) {
|
||||
throw new Error(
|
||||
`Expected '${expected}', got '${result.stdout.trim()}'`,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
|
||||
for (const test of tests) {
|
||||
try {
|
||||
console.log(` ✓ ${test.name}`)
|
||||
await test.test()
|
||||
passed++
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${test.name}: ${error.message}`)
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 Test Results: ${passed} passed, ${failed} failed`)
|
||||
|
||||
if (failed > 0) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('✅ All default value syntax e2e tests passed!')
|
||||
}
|
||||
|
||||
runTests().catch((error) => {
|
||||
console.error('❌ Test runner failed:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
"test:ui": "vitest --ui",
|
||||
"test:run": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:e2e": "node e2e/test-cross-env.js && node e2e/test-cross-env-shell.js",
|
||||
"test:e2e": "node e2e/test-cross-env.js && node e2e/test-cross-env-shell.js && node e2e/test-default-values.js",
|
||||
"validate": "npm run build && npm run typecheck && npm run lint && npm run format:check && npm run test:run"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { commandConvert } from '../command.js'
|
||||
import * as isWindowsModule from '../is-windows.js'
|
||||
|
||||
vi.mock('../is-windows.js')
|
||||
|
||||
describe('commandConvert - Environment Variable Default Values', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
test('should handle ${VAR:-default} syntax on Windows when VAR is undefined', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(true)
|
||||
const env = {} // PORT is not defined
|
||||
const command = 'wrangler dev --port ${PORT:-8787}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// Should use the default value when PORT is not defined
|
||||
expect(result).toBe('wrangler dev --port 8787')
|
||||
})
|
||||
|
||||
test('should handle ${VAR:-default} syntax on Windows when VAR is defined', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(true)
|
||||
const env = { PORT: '3000' }
|
||||
const command = 'wrangler dev --port ${PORT:-8787}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// Should use the defined value when PORT is defined
|
||||
expect(result).toBe('wrangler dev --port 3000')
|
||||
})
|
||||
|
||||
test('should handle ${VAR:-default} syntax on Unix (no conversion needed)', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(false)
|
||||
const env = {} // PORT is not defined
|
||||
const command = 'wrangler dev --port ${PORT:-8787}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// On Unix, no conversion is needed
|
||||
expect(result).toBe('wrangler dev --port ${PORT:-8787}')
|
||||
})
|
||||
|
||||
test('should handle simple ${VAR} syntax on Windows', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(true)
|
||||
const env = { PORT: '3000' }
|
||||
const command = 'wrangler dev --port ${PORT}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// This works correctly
|
||||
expect(result).toBe('wrangler dev --port %PORT%')
|
||||
})
|
||||
|
||||
test('should handle simple ${VAR} syntax on Windows when VAR is undefined', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(true)
|
||||
const env = {} // PORT is not defined
|
||||
const command = 'wrangler dev --port ${PORT}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// This works correctly - strips undefined variables
|
||||
expect(result).toBe('wrangler dev --port ')
|
||||
})
|
||||
|
||||
test('should handle multiple default value syntaxes in one command', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(true)
|
||||
const env = { HOST: 'localhost' } // PORT is not defined
|
||||
const command = 'wrangler dev --host ${HOST:-0.0.0.0} --port ${PORT:-8787}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// Should use defined value for HOST and default for PORT
|
||||
expect(result).toBe('wrangler dev --host localhost --port 8787')
|
||||
})
|
||||
|
||||
test('should handle default values with special characters', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(true)
|
||||
const env = {} // NODE_ENV is not defined
|
||||
const command = 'node app.js --env ${NODE_ENV:-development}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// Should use the default value
|
||||
expect(result).toBe('node app.js --env development')
|
||||
})
|
||||
|
||||
test('should handle default values with spaces', () => {
|
||||
vi.mocked(isWindowsModule.isWindows).mockReturnValue(true)
|
||||
const env = {} // MESSAGE is not defined
|
||||
const command = 'echo ${MESSAGE:-hello world}'
|
||||
|
||||
const result = commandConvert(command, env)
|
||||
|
||||
// Should use the default value with spaces
|
||||
expect(result).toBe('echo hello world')
|
||||
})
|
||||
})
|
||||
+19
-2
@@ -17,8 +17,25 @@ export function commandConvert(
|
||||
return command
|
||||
}
|
||||
|
||||
const envUnixRegex = /\$(\w+)|\${(\w+)}/g // $my_var or ${my_var}
|
||||
const convertedCmd = command.replace(envUnixRegex, (match, $1, $2) => {
|
||||
// Handle simple variables: $var or ${var}
|
||||
const simpleEnvRegex = /\$(\w+)|\${(\w+)}/g
|
||||
// Handle bash parameter expansion with default values: ${var:-default}
|
||||
const defaultValueRegex = /\$\{(\w+):-([^}]+)\}/g
|
||||
|
||||
let convertedCmd = command
|
||||
|
||||
// First, handle bash parameter expansion with default values
|
||||
convertedCmd = convertedCmd.replace(
|
||||
defaultValueRegex,
|
||||
(match, varName, defaultValue) => {
|
||||
// If the variable exists, use its value; otherwise use the default
|
||||
const value = env[varName] || defaultValue
|
||||
return value
|
||||
},
|
||||
)
|
||||
|
||||
// Then handle simple variable references
|
||||
convertedCmd = convertedCmd.replace(simpleEnvRegex, (match, $1, $2) => {
|
||||
const varName = $1 || $2
|
||||
// In Windows, non-existent variables are not replaced by the shell,
|
||||
// so for example "echo %FOO%" will literally print the string "%FOO%", as
|
||||
|
||||
Reference in New Issue
Block a user