Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
26 changes: 25 additions & 1 deletion cmd/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
}

type ConfigParser struct {
EnvVars map[string]string
EnvVars map[string]string
OptionalEnvVars []string
RequiredEnvVars []string
}

// parseEnv replaces environment variables ${ENV_NAME} with their values.
Expand All @@ -59,6 +61,28 @@

// extract the variable name
variableName := parts[1]

isOptional := len(parts) >= 4 && parts[2] != ""
if isOptional {
// Add to optional list only if it hasn't been explicitly required
if !slices.Contains(p.RequiredEnvVars, variableName) && !slices.Contains(p.OptionalEnvVars, variableName) {
p.OptionalEnvVars = append(p.OptionalEnvVars, variableName)
}
} else {
// Mark as required
if !slices.Contains(p.RequiredEnvVars, variableName) {
p.RequiredEnvVars = append(p.RequiredEnvVars, variableName)
}

Check failure on line 76 in cmd/internal/config.go

View workflow job for this annotation

GitHub Actions / lint

File is not properly formatted (goimports)
// Remove from optional list if it's there
for i, v := range p.OptionalEnvVars {
if v == variableName {
p.OptionalEnvVars = append(p.OptionalEnvVars[:i], p.OptionalEnvVars[i+1:]...)
break
}
}
}

if value, found := os.LookupEnv(variableName); found {
p.EnvVars[variableName] = value
return value
Expand Down
42 changes: 36 additions & 6 deletions cmd/internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@

func TestParseEnv(t *testing.T) {
tcs := []struct {
desc string
env map[string]string
in string
want string
err bool
errString string
desc string
env map[string]string
in string
want string
err bool
errString string
wantOptional []string
}{
{
desc: "without default without env",
Expand All @@ -61,14 +62,16 @@
want: "bar",
},
{
desc: "with empty default",

Check failure on line 65 in cmd/internal/config_test.go

View workflow job for this annotation

GitHub Actions / lint

File is not properly formatted (goimports)
in: "${FOO:}",
want: "",
wantOptional: []string{"FOO"},
},
{
desc: "with default",
in: "${FOO:bar}",
want: "bar",
wantOptional: []string{"FOO"},
},
{
desc: "with default with env",
Expand All @@ -77,6 +80,25 @@
},
in: "${FOO:bar}",
want: "hello",
wantOptional: []string{"FOO"},
},
{
desc: "multiple variables",
in: "user: ${USER_NAME:}, password: ${PASSWORD:}, ip: ${IP:public}, region: ${REGION}",
env: map[string]string{
"REGION": "us-central1",
},
want: "user: , password: , ip: public, region: us-central1",
wantOptional: []string{"USER_NAME", "PASSWORD", "IP"},
},
{
desc: "variable required in one place and optional in another",
in: "project_req: ${PROJECT_ID}, project_opt: ${PROJECT_ID:default}",
env: map[string]string{
"PROJECT_ID": "my_project",
},
want: "project_req: my_project, project_opt: my_project",
wantOptional: []string{}, // Because it was marked required at least once
},
}
for _, tc := range tcs {
Expand All @@ -99,6 +121,14 @@
if tc.want != got {
t.Fatalf("unexpected want: got %s, want %s", got, tc.want)
}
if len(parser.OptionalEnvVars) != len(tc.wantOptional) {
t.Fatalf("OptionalEnvVars length mismatch: got %d, want %d. Got: %v, Want: %v", len(parser.OptionalEnvVars), len(tc.wantOptional), parser.OptionalEnvVars, tc.wantOptional)
}
for i, v := range parser.OptionalEnvVars {
if v != tc.wantOptional[i] {
t.Errorf("OptionalEnvVars element %d mismatch: got %q, want %q", i, v, tc.wantOptional[i])
}
}
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/skills/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func run(cmd *skillsCmd, opts *internal.ToolboxOptions) error {

for _, toolName := range toolNames {
// Generate wrapper script in scripts directory
scriptContent, err := generateScriptContent(toolName, configArgsStr, cmd.licenseHeader, cmd.invocationMode, cmd.toolboxVersion)
scriptContent, err := generateScriptContent(toolName, configArgsStr, cmd.licenseHeader, cmd.invocationMode, cmd.toolboxVersion, parser.OptionalEnvVars)
if err != nil {
errMsg := fmt.Errorf("error generating script content for %s: %w", toolName, err)
opts.Logger.ErrorContext(ctx, errMsg.Error())
Expand Down
18 changes: 15 additions & 3 deletions cmd/internal/skills/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@

const toolName = "{{.Name}}";
const configArgs = [{{.ConfigArgs}}];

{{if .OptionalVars}}
const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [
{{range .OptionalVars}} '{{.}}',
{{end}}];
{{end}}
function getEnv() {
const envPath = path.resolve(__dirname, '../../../.env');
const env = { ...process.env };
Expand Down Expand Up @@ -158,7 +162,13 @@
env = getEnv();
userAgent = "skills-geminicli";
}

{{if .OptionalVars}}
OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => {
if (env[varName] === '') {
delete env[varName];
}
});
{{end}}
const args = process.argv.slice(2);

{{if eq .InvocationMode "npx"}}
Expand Down Expand Up @@ -219,18 +229,20 @@
LicenseHeader string
InvocationMode string
ToolboxVersion string
OptionalVars []string

Check failure on line 232 in cmd/internal/skills/generator.go

View workflow job for this annotation

GitHub Actions / lint

File is not properly formatted (goimports)
}

// generateScriptContent creates the content for a Node.js wrapper script.
// This script invokes the toolbox CLI with the appropriate configuration
// (using a generated config) and arguments to execute the specific tool.
func generateScriptContent(name string, configArgs string, licenseHeader string, mode string, version string) (string, error) {
func generateScriptContent(name string, configArgs string, licenseHeader string, mode string, version string, optionalVars []string) (string, error) {
data := scriptData{
Name: name,
ConfigArgs: configArgs,
LicenseHeader: licenseHeader,
InvocationMode: mode,
ToolboxVersion: version,
OptionalVars: optionalVars,
}

tmpl, err := template.New("script").Parse(nodeScriptTemplate)
Expand Down
18 changes: 17 additions & 1 deletion cmd/internal/skills/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ func TestGenerateScriptContent(t *testing.T) {
licenseHeader string
mode string
version string
optionalVars []string
}{
{
name: "basic script (binary default)",
Expand Down Expand Up @@ -252,6 +253,21 @@ func TestGenerateScriptContent(t *testing.T) {
"// My License",
},
},
{
name: "script with optional vars",
toolName: "test-tool",
configArgs: `"--prebuilt", "test"`,
optionalVars: []string{"OPTIONAL_A", "OPTIONAL_B"},
wantContains: []string{
"const OPTIONAL_VARS_TO_OMIT_IF_EMPTY = [",
" 'OPTIONAL_A',",
" 'OPTIONAL_B',",
"];",
"OPTIONAL_VARS_TO_OMIT_IF_EMPTY.forEach(varName => {",
" if (env[varName] === '') {",
" delete env[varName];",
},
},
{
name: "npx mode script",
toolName: "npx-tool",
Expand All @@ -267,7 +283,7 @@ func TestGenerateScriptContent(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := generateScriptContent(tt.toolName, tt.configArgs, tt.licenseHeader, tt.mode, tt.version)
got, err := generateScriptContent(tt.toolName, tt.configArgs, tt.licenseHeader, tt.mode, tt.version, tt.optionalVars)
if err != nil {
t.Fatalf("generateScriptContent() error = %v", err)
}
Expand Down
Loading