Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# The PowerShell script binder consumes a bare -- before populating $args.
# Exercise the generated wrapper with a real native child, without a registry.
function Assert-Arguments($actual, $expected) {
if ($LASTEXITCODE -ne 0 -or ($actual -join "`n") -ne $expected) {
throw "Expected arguments $expected, got $actual (exit $LASTEXITCODE)"
}
}

Assert-Arguments (vp env exec node print-args.cjs -- --template react-ts) '["--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs '--' --template react-ts) '["--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs "--" --template react-ts) '["--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs '--' -- --template react-ts) '["--","--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs -- -- --template react-ts) '["--","--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs --) '["--"]'
Assert-Arguments (vp env exec node print-args.cjs 'value with spaces' -- --template 'react ts') '["value with spaces","--","--template","react ts"]'

# The stop-parsing token preserves its literal remainder without consuming --.
# Capture on a separate line because --% makes the rest of the line literal.
$literalArguments = vp env exec node print-args.cjs --% --
Assert-Arguments $literalArguments '["--"]'
$literalArguments = vp env exec node print-args.cjs --% -- --
Assert-Arguments $literalArguments '["--","--"]'
$literalArguments = vp env exec node print-args.cjs -- --% --
Assert-Arguments $literalArguments '["--","--"]'

# A quoted --% allows recovery; escaping its first dash still stops parsing.
$literalArguments = vp env exec node print-args.cjs '--%' -- after
Assert-Arguments $literalArguments '["--","after"]'
$literalArguments = vp env exec node print-args.cjs `--% --
Assert-Arguments $literalArguments '["--"]'

$prefix = @('env', 'exec', 'node', 'print-args.cjs')
$suffix = @('--template', 'react-ts')
$allArguments = $prefix + @('--') + $suffix
Assert-Arguments (vp env exec node print-args.cjs -- @suffix) '["--","--template","react-ts"]'
Assert-Arguments (vp @allArguments) '["--","--template","react-ts"]'
Assert-Arguments (vp @prefix '--' @suffix) '["--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs @('before', 'separator') -- @('after', 'separator')) '["before","separator","--","after","separator"]'

# A splat before -- makes its position unknowable from syntax alone. Preserve
# PowerShell's bound arguments in that case instead of guessing an insertion point.
Assert-Arguments (vp @prefix -- --template react-ts) '["--template","react-ts"]'
Assert-Arguments (vp @prefix -- @suffix) '["--template","react-ts"]'
$emptyArguments = @()
Assert-Arguments (vp @prefix @emptyArguments -- @emptyArguments @suffix) '["--template","react-ts"]'
$script:scopedPrefix = $prefix
$script:scopedSuffix = $suffix
Assert-Arguments (vp @script:scopedPrefix -- @script:scopedSuffix) '["--template","react-ts"]'
Assert-Arguments (vp @script:scopedPrefix '--' @script:scopedSuffix) '["--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs -- @script:scopedSuffix) '["--","--template","react-ts"]'

# Argument expressions can change a splatted variable after it has been expanded.
Assert-Arguments (vp @prefix -- @script:scopedSuffix $($script:scopedSuffix = @(); 'last')) '["--template","react-ts","last"]'

# The caller AST must identify the invocation inside a function or on a later line.
function Invoke-ArgumentCheck {
Assert-Arguments (vp env exec node print-args.cjs -- @suffix) '["--","--template","react-ts"]'
}
Invoke-ArgumentCheck
Assert-Arguments (& vp env exec node print-args.cjs `
-- `
--template react-ts) '["--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs first -- second | ForEach-Object { $_ }) '["first","--","second"]'
$first = vp env exec node print-args.cjs one -- two; $second = vp env exec node print-args.cjs three -- four
Assert-Arguments $first '["one","--","two"]'
Assert-Arguments $second '["three","--","four"]'

# Recovering the separator must not evaluate argument expressions a second time.
$script:argumentEvaluations = 0
Assert-Arguments (vp env exec node print-args.cjs -- $($script:argumentEvaluations++; 'value with spaces')) '["--","value with spaces"]'
if ($script:argumentEvaluations -ne 1) { throw 'An argument expression ran more than once' }

Assert-Arguments (vp env exec node print-args.cjs 'text -- with spaces' --template react-ts) '["text -- with spaces","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs `-- --template react-ts) '["--","--template","react-ts"]'
Assert-Arguments (vp env exec node print-args.cjs --template react-ts) '["--template","react-ts"]'
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ if ($env:VP_NODE_VERSION -ne "22.18.0") {
throw "file-based VP_NODE_VERSION mismatch: expected 22.18.0, got $env:VP_NODE_VERSION"
}

. (Join-Path $PSScriptRoot "assert-arguments.ps1")
. (Join-Path $PSScriptRoot "assert-status.ps1")

Write-Output "PowerShell environment checks passed"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(JSON.stringify(process.argv.slice(2)));
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,48 @@ PowerShell wrapper keeps global -C before env use and propagates the native exit
```
# Vite+ PowerShell wrapper (https://viteplus.dev)
$__vp_bin = '<workspace>/home/bin'
# PowerShell consumes the first bare -- when calling a script. Recover its
# position from the caller's AST, without evaluating argument expressions again.
# The call stack also works on Windows PowerShell 5.1, which has no Statement.
$__vp_stack = @(Get-PSCallStack)
$__vp_command = $null
if ($__vp_stack.Count -gt 1) {
$__vp_ast = $__vp_stack[1].InvocationInfo.MyCommand.ScriptBlock.Ast
$__vp_line = $MyInvocation.ScriptLineNumber
$__vp_column = $MyInvocation.OffsetInLine
if ($__vp_ast) {
$__vp_command = $__vp_ast.Find({ param($node)
$node -is [System.Management.Automation.Language.CommandAst] -and
$node.Extent.StartLineNumber -eq $__vp_line -and $node.Extent.StartColumnNumber -eq $__vp_column
}, $true)
}
}
if ($__vp_command) {
$__vp_index = 0
$__vp_elements = $__vp_command.CommandElements
for ($__vp_i = 1; $__vp_i -lt $__vp_elements.Count; $__vp_i++) {
$__vp_element = $__vp_elements[$__vp_i]
# A preceding splat has an unknown length. Leave $args unchanged rather
# than reading caller variables, which may have changed or belong to another scope.
if ($__vp_element -is [System.Management.Automation.Language.VariableExpressionAst] -and $__vp_element.Splatted) {
break
}
# Only a parameter AST can be consumed. After --%, the separator is literal.
if ($__vp_element -is [System.Management.Automation.Language.CommandParameterAst] -and $__vp_element.Extent.Text -ceq '--') {
if ($__vp_index -le $args.Count) {
$__vp_arguments = [System.Collections.Generic.List[object]]::new()
$__vp_arguments.AddRange($args)
$__vp_arguments.Insert($__vp_index, '--')
$args = $__vp_arguments.ToArray()
}
break
}
$__vp_index++
if ($__vp_element -is [System.Management.Automation.Language.CommandParameterAst] -and $__vp_element.Argument) {
$__vp_index++
}
}
}
$__vp_command_index = 0
if ($args.Count -ge 1) {
if ($args[0] -eq "-C") {
Expand Down
44 changes: 44 additions & 0 deletions crates/vp_global_cli/src/commands/env/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1095,8 +1095,52 @@ Register-ArgumentCompleter -Native -CommandName vpr -ScriptBlock $__vpr_comp
// Keep the wrapper outside bin so it cannot shadow the native executable.
// Unlike an advanced function, this script forwards all arguments without
// PowerShell consuming common parameters such as -Verbose or its -V alias.
// Bare -- recovery is inspired by mise's __mise_restore_double_dash:
// https://github.com/jdx/mise/blob/34ec0439c868379be87a28591f50d41f84fb8d02/src/shell/pwsh.rs#L45-L103
const VP_WRAPPER_PS1: &str = r#"# Vite+ PowerShell wrapper (https://viteplus.dev)
$__vp_bin = '__VP_BIN_WIN__'
# PowerShell consumes the first bare -- when calling a script. Recover its
# position from the caller's AST, without evaluating argument expressions again.
# The call stack also works on Windows PowerShell 5.1, which has no Statement.
$__vp_stack = @(Get-PSCallStack)
$__vp_command = $null
if ($__vp_stack.Count -gt 1) {
$__vp_ast = $__vp_stack[1].InvocationInfo.MyCommand.ScriptBlock.Ast
$__vp_line = $MyInvocation.ScriptLineNumber
$__vp_column = $MyInvocation.OffsetInLine
if ($__vp_ast) {
$__vp_command = $__vp_ast.Find({ param($node)
$node -is [System.Management.Automation.Language.CommandAst] -and
$node.Extent.StartLineNumber -eq $__vp_line -and $node.Extent.StartColumnNumber -eq $__vp_column
}, $true)
}
}
if ($__vp_command) {
$__vp_index = 0
$__vp_elements = $__vp_command.CommandElements
for ($__vp_i = 1; $__vp_i -lt $__vp_elements.Count; $__vp_i++) {
$__vp_element = $__vp_elements[$__vp_i]
# A preceding splat has an unknown length. Leave $args unchanged rather
# than reading caller variables, which may have changed or belong to another scope.
if ($__vp_element -is [System.Management.Automation.Language.VariableExpressionAst] -and $__vp_element.Splatted) {
break
}
# Only a parameter AST can be consumed. After --%, the separator is literal.
if ($__vp_element -is [System.Management.Automation.Language.CommandParameterAst] -and $__vp_element.Extent.Text -ceq '--') {
if ($__vp_index -le $args.Count) {
$__vp_arguments = [System.Collections.Generic.List[object]]::new()
$__vp_arguments.AddRange($args)
$__vp_arguments.Insert($__vp_index, '--')
$args = $__vp_arguments.ToArray()
}
break
}
$__vp_index++
if ($__vp_element -is [System.Management.Automation.Language.CommandParameterAst] -and $__vp_element.Argument) {
$__vp_index++
}
}
}
$__vp_command_index = 0
if ($args.Count -ge 1) {
if ($args[0] -eq "-C") {
Expand Down
Loading