-
Notifications
You must be signed in to change notification settings - Fork 11
feat(artifacts-helper): option to install executable wrappers or shell functions #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Wrap the underlying tools with an executable script, rather than relying on shell functions or aliases. This means that these scenarios will now work: - Invoking `npx` from a JS script, e.g. during a pre-commit hook - Invoking ```py popen(['npx'], shell=False) ```
| WRAPPED_BINS_DIR=$(mktemp -d) | ||
| BASH_BIN_DIR=$(dirname "$(command -v bash)") | ||
| TEST_PATH="$WRAPPER_INSTALL_PATH:$WRAPPER_INSTALL_PATH:$WRAPPED_BINS_DIR:$BASH_BIN_DIR" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this actually makes the executable wrapper appear twice in which -a. Probably best to copy or symlink the wrappers to simulate that scenario. It's important to prevent infinite recursion for the wrapper and this would make for a pretty robust test in that regard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for an alternative "EXECUTABLE" wrapper type to the artifacts-helper feature, allowing package manager commands to be wrapped via executable scripts instead of shell functions. This enables authentication injection in scenarios where shell functions aren't recognized (e.g., Python subprocess calls with shell=False).
- Introduces new
wrapperTypeconfiguration option with valuesSHELL_FUNCTION(default) andEXECUTABLE - Implements executable wrapper template that recursively unwraps itself from PATH to call the underlying binary
- Adds environment variable caching to avoid redundant
whichcalls in run scripts
Reviewed Changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/artifacts-helper/devcontainer-feature.json | Adds wrapperType option and PATH configuration for executable wrappers |
| src/artifacts-helper/install.sh | Implements installation logic for both wrapper types, renaming variables from ALIASES_ARR to WRAPPED_BINS_ARR |
| src/artifacts-helper/scripts/executable-wrapper-template.sh | New template script that recursively unwraps PATH to find and execute the real binary |
| src/artifacts-helper/scripts/run-*.sh | Adds caching logic to reuse environment variables for executable paths |
| src/artifacts-helper/README.md | Documents the new wrapperType option and wrapper type descriptions |
| test/artifacts-helper/scenarios.json | Adds test scenario for executable wrapper type |
| test/artifacts-helper/scenario_executable_wrapper.sh | Comprehensive test script validating wrapper behavior and token injection |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| if [ -z "$NEXT_SHADOWED_BIN" ]; then | ||
| log_line "Error: The real $TARGET_BIN_NAME could not be found on PATH." | ||
| log_line "which -a $TARGET_BIN_NAME:\n%s\n" "$(which -a $TARGET_BIN_NAME)" |
Copilot
AI
Oct 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The log_line function uses printf which expects format arguments to be separated, but here the format specifiers %s are embedded in the first argument string. The $(which -a $TARGET_BIN_NAME) expansion won't be properly substituted. This line should either use echo or properly separate the printf format string from its arguments.
| log_line "CC_SHADOWED_BINS:\n%s\n" "$ARTIFACTS_HELPER_TARGET_BIN_PATHS" | ||
| log_line "CURRENT_SCRIPT_PATH: %s\n" "$CURRENT_SCRIPT_PATH" |
Copilot
AI
Oct 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as line 51: log_line calls printf with a single argument, so the format specifiers %s won't be replaced with the variable values in the second argument. These arguments will be ignored by printf.
|
|
||
| | Wrapper Type | Description | | ||
| |-|-| | ||
| | `SHELL_FUNCTION` | Configures shell functions that wrap the commands to set the authentication token before calling the actual command. This is the default options. | |
Copilot
AI
Oct 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spelling of 'options' to 'option'.
| | `SHELL_FUNCTION` | Configures shell functions that wrap the commands to set the authentication token before calling the actual command. This is the default options. | | |
| | `SHELL_FUNCTION` | Configures shell functions that wrap the commands to set the authentication token before calling the actual command. This is the default option. | |
|
How reliable/fragile is PATH? Maybe since the Codespaces environment is more structured and predicitable it would be fine, but I was originally hesitant to try something like this as I did not think we could trust PATH order. |
The artifacts helper injects an access token before calling a shadowed tool (e.g.
npx). This works by setting a shell function with the same name as the shadowed tool, which would take precedence over the tool and invoke the corresponding<shadowed tool>-run.shscript to inject the token and call the shadowed bin.However, the above approach doesn't work in these situations:
Popen([tool], shell=False)in Python.fish.For all of the above situations, we can use an executable wrapper instead of a shell function. Add the devcontainer option to set
wrapperType == EXECUTABLE. When this option is selected, we should:Create a directory that takes precedence on the
$PATHover the shadowed tool, by ordering our devcontainer feature's installation after each tool's feature. I.e., we must install our feature after"ghcr.io/devcontainers/features/node"because they modify$PATHincontainerEnvas well.Create an executable in the above directory. When this executable is run, it should unwrap itself from the
PATHand handover to the corresponding<shadowed tool>-run.shscript.If the executable appears more than once in the
PATH, it should recursively unwrap itself until the shadowed tool is reached. The stack should look like this:Finally, add a set of tests to check that the executable wrappers work as expected. The dotnet wrapper is excluded because the injected environment variable differs from the others. This should be fine because the executable wrappers use the same template.
I haven't replicated the above "unwrap itself twice" scenario in the tests yet, so this PR is a draft. It could also use some polishing up, but this is the core idea behind the solution to #55.