Skip to content
Merged
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
12 changes: 6 additions & 6 deletions crates/vfox/plugins/dummy/hooks/available.lua
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
--- Return all available versions provided by this plugin
--- @param ctx table Empty table used as context, for future extension
--- @return table Descriptions of available versions and accompanying tool descriptions
--- Get the available version list.
--- @param ctx table Empty table, no data provided. Always {}.
--- @return table Version list
function PLUGIN:Available(ctx)
return {
{
version = "1.0.0",
version = "1.0.0"
},
{
version = "1.0.1",
version = "1.0.1"
},
}
end
end
28 changes: 11 additions & 17 deletions crates/vfox/plugins/dummy/hooks/parse_legacy_file.lua
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
--- Parse legacy version files like .node-version, .nvmrc, etc.
--- @param ctx table Context information
--- @field ctx.filepath string Path to the legacy file
--- @return table Version information
--- Parse the legacy file found by vfox to determine the version of the tool.
--- Useful to extract version numbers from files like JavaScript's package.json or Golangs go.mod.
function PLUGIN:ParseLegacyFile(ctx)
local filepath = ctx.filepath
local content = io.open(filepath, "r")
if content then
local version = content:read("*line")
content:close()
if version then
-- Remove any "v" prefix and trim whitespace
version = version:gsub("^v", ""):match("^%s*(.-)%s*$")
return {
version = version
}
end
local file = io.open(filepath, "r")
Copy link

Copilot AI Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If io.open fails and returns nil, calling file:read() and file:close() on nil will cause a runtime error. Add a nil check after io.open before proceeding with file operations.

Suggested change
local file = io.open(filepath, "r")
local file = io.open(filepath, "r")
if not file then
return {}
end

Copilot uses AI. Check for mistakes.
local content = file:read("*a")
file:close()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: File Handling Error: Unchecked io.open Call

The io.open call isn't checked for failure. If the file is missing or unreadable, io.open returns nil, causing a runtime error when file:read or file:close are subsequently called. The previous implementation safely handled this case.

Fix in Cursor Fix in Web

content = content:gsub("%s+", "")
if content == "" then
return {}
end

return {
version = nil
version = content
}
end
end
19 changes: 12 additions & 7 deletions crates/vfox/plugins/dummy/hooks/post_install.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ function PLUGIN:PostInstall(ctx)
--- SDK installation root path
local rootPath = ctx.rootPath
local runtimeVersion = ctx.runtimeVersion
--- Other SDK information, the `addition` field returned in PreInstall, obtained by name
--TODO
--local sdkInfo = ctx.sdkInfo['dummy']
--local path = sdkInfo.path
--local version = sdkInfo.version
--local name = sdkInfo.name
end

-- Create the installation directory structure for dummy plugin
os.execute("mkdir -p " .. rootPath .. "/bin")
Copy link

Copilot AI Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using os.execute with user-controlled input can lead to command injection vulnerabilities. Consider using Lua's built-in file system functions or properly sanitizing the rootPath variable before concatenating it into shell commands.

Copilot uses AI. Check for mistakes.

-- Create a dummy executable
local dummy_file = io.open(rootPath .. "/bin/dummy", "w")
if dummy_file then
dummy_file:write("#!/bin/sh\necho 'dummy version 1.0.0'\n")
dummy_file:close()
os.execute("chmod +x " .. rootPath .. "/bin/dummy")
Copy link

Copilot AI Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the mkdir command, this os.execute call with concatenated user input poses a command injection risk. Consider using Lua's file system APIs or ensure proper input sanitization.

Copilot uses AI. Check for mistakes.
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Cross-Platform Shell Command Issues

The post_install hook uses non-portable Unix-specific shell commands (mkdir -p, chmod +x) and path separators (/) via os.execute, and writes a sh script. This fails on Windows, preventing directory and binary creation. Unquoted paths also break when rootPath contains spaces, causing installations and tests to fail.

Fix in Cursor Fix in Web

end
2 changes: 1 addition & 1 deletion crates/vfox/plugins/dummy/hooks/pre_install.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ function PLUGIN:PreInstall(ctx)
return {
version = version,
}
end
end
4 changes: 2 additions & 2 deletions crates/vfox/plugins/dummy/metadata.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ PLUGIN.notes = {}
--- List legacy configuration filenames for determining the specified version of the tool.
--- such as ".node-version", ".nvmrc", etc.
PLUGIN.legacyFilenames = {
".dummy-version",
}
".dummy-version"
}
4 changes: 2 additions & 2 deletions crates/vfox/src/snapshots/vfox__vfox__tests__env_keys.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/vfox.rs
source: crates/vfox/src/vfox.rs
expression: output
---
[EnvKey { key: "PATH", value: "<INSTALL_DIR>/nodejs/20.0.0/bin" }]
[EnvKey { key: "PATH", value: "<INSTALL_DIR>/dummy/1.0.0/bin" }]
5 changes: 2 additions & 3 deletions crates/vfox/src/snapshots/vfox__vfox__tests__metadata.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: src/vfox.rs
source: crates/vfox/src/vfox.rs
expression: out
snapshot_kind: text
---
Metadata { name: "nodejs", legacy_filenames: [".node-version", ".nvmrc"], version: "0.3.0", description: Some("Node.js runtime environment."), author: None, license: Some("Apache 2.0"), homepage: Some("https://github.com/version-fox/vfox-nodejs"), hooks: {"available", "env_keys", "parse_legacy_file", "pre_install", "pre_use"} }
Metadata { name: "dummy", legacy_filenames: [".dummy-version"], version: "0.3.0", description: Some("Dummy plugin for testing."), author: None, license: Some("Apache 2.0"), homepage: Some("https://github.com/version-fox/vfox-nodejs"), hooks: {"available", "env_keys", "parse_legacy_file", "post_install", "pre_install"} }
38 changes: 15 additions & 23 deletions crates/vfox/src/vfox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ mod tests {
#[tokio::test]
async fn test_env_keys() {
let vfox = Vfox::test();
vfox.install_plugin("nodejs").unwrap();
let keys = vfox.env_keys("nodejs", "20.0.0").await.unwrap();
// dummy plugin already exists in plugins/dummy, no need to install
let keys = vfox.env_keys("dummy", "1.0.0").await.unwrap();
let output = format!("{keys:?}").replace(
&vfox.install_dir.to_string_lossy().to_string(),
"<INSTALL_DIR>",
Expand All @@ -389,31 +389,22 @@ mod tests {
#[tokio::test]
async fn test_install_plugin() {
let vfox = Vfox::test();
vfox.uninstall_plugin("nodejs").unwrap();
assert!(!vfox.plugin_dir.join("nodejs").exists());
vfox.install_plugin("nodejs").unwrap();
assert!(vfox.plugin_dir.join("nodejs").exists());
// dummy plugin already exists in plugins/dummy, just verify it's there
assert!(vfox.plugin_dir.join("dummy").exists());
let plugin = Plugin::from_dir(&vfox.plugin_dir.join("dummy")).unwrap();
assert_eq!(plugin.name, "dummy");
}

#[tokio::test]
async fn test_install() {
let vfox = Vfox::test();
let install_dir = vfox.install_dir.join("nodejs").join("20.0.0");
vfox.install("nodejs", "20.0.0", &install_dir)
.await
.unwrap();
assert!(vfox
.install_dir
.join("nodejs")
.join("20.0.0")
.join("bin")
.join("node")
.exists());
vfox.uninstall_plugin("nodejs").unwrap();
assert!(!vfox.plugin_dir.join("nodejs").exists());
vfox.uninstall("nodejs", "20.0.0").unwrap();
assert!(!vfox.install_dir.join("nodejs").join("20.0.0").exists());
file::remove_dir_all(vfox.plugin_dir.join("nodejs")).unwrap();
let install_dir = vfox.install_dir.join("dummy").join("1.0.0");
// dummy plugin already exists in plugins/dummy
vfox.install("dummy", "1.0.0", &install_dir).await.unwrap();
// dummy plugin doesn't actually install binaries, so we just check the directory
assert!(vfox.install_dir.join("dummy").join("1.0.0").exists());
vfox.uninstall("dummy", "1.0.0").unwrap();
assert!(!vfox.install_dir.join("dummy").join("1.0.0").exists());
file::remove_dir_all(vfox.install_dir).unwrap();
file::remove_dir_all(vfox.download_dir).unwrap();
}
Expand Down Expand Up @@ -464,7 +455,8 @@ mod tests {
#[tokio::test]
async fn test_metadata() {
let vfox = Vfox::test();
let metadata = vfox.metadata("nodejs").await.unwrap();
// dummy plugin already exists in plugins/dummy
let metadata = vfox.metadata("dummy").await.unwrap();
let out = format!("{metadata:?}");
assert_snapshot!(out);
}
Expand Down
Loading