From 560938835d379fd9debf8181cc3b93577632247e Mon Sep 17 00:00:00 2001 From: Devin Alldrit <32575057+dalldrit13@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:06:55 -0400 Subject: [PATCH] Update npm plugin to fix double temp file read issue --- plugins/npm/access_token.go | 98 ++++++++++++++++++++++++-------- plugins/npm/access_token_test.go | 45 ++++++++++----- 2 files changed, 106 insertions(+), 37 deletions(-) diff --git a/plugins/npm/access_token.go b/plugins/npm/access_token.go index 6f6a9ada..1eb754a5 100644 --- a/plugins/npm/access_token.go +++ b/plugins/npm/access_token.go @@ -3,6 +3,7 @@ package npm import ( "bufio" "context" + "encoding/json" "fmt" "net/url" "os" @@ -11,7 +12,6 @@ import ( "github.com/1Password/shell-plugins/sdk" "github.com/1Password/shell-plugins/sdk/importer" - "github.com/1Password/shell-plugins/sdk/provision" "github.com/1Password/shell-plugins/sdk/schema" "github.com/1Password/shell-plugins/sdk/schema/credname" "github.com/1Password/shell-plugins/sdk/schema/fieldname" @@ -41,11 +41,7 @@ func AccessToken() schema.CredentialType { Optional: true, }, }, - DefaultProvisioner: provision.TempFile( - npmConfigFile, - provision.Filename(".npmrc"), - provision.AddArgs("--userconfig", "{{ .Path }}"), - ), + DefaultProvisioner: npmProvisioner(), Importer: importer.TryAll( importer.TryAllEnvVars(fieldname.Token, "NPM_TOKEN", "NODE_AUTH_TOKEN"), tryNPMRCFile("~/.npmrc"), @@ -56,35 +52,89 @@ func AccessToken() schema.CredentialType { } } -func pnpmProvisioner() sdk.Provisioner { - return provision.TempFile( - npmConfigFile, - provision.Filename(".npmrc"), - provision.SetPathAsEnvVar("NPM_CONFIG_USERCONFIG"), - ) +func npmProvisioner() sdk.Provisioner { + return npmEnvProvisioner{} } -func npmConfigFile(in sdk.ProvisionInput) ([]byte, error) { +type npmEnvProvisioner struct{} + +func (npmEnvProvisioner) Provision(_ context.Context, in sdk.ProvisionInput, out *sdk.ProvisionOutput) { registry, err := normalizeRegistry(in.ItemFields[fieldname.Host]) if err != nil { - return nil, err + out.AddError(err) + return + } + + authEnvVar := fmt.Sprintf("npm_config_//%s/:_authToken", registryAuthKey(registry)) + if strings.ContainsAny(authEnvVar, "=\x00") { + out.AddError(fmt.Errorf("registry URL cannot be represented as an npm environment variable")) + return } + out.AddEnvVar(authEnvVar, in.ItemFields[fieldname.Token]) - scope := strings.TrimPrefix(strings.TrimSpace(in.ItemFields[fieldname.Organization]), "@") - var contents strings.Builder - if scope != "" { - fmt.Fprintf(&contents, "@%s:registry=%s\n", scope, registry.String()) + organization := strings.TrimPrefix(strings.TrimSpace(in.ItemFields[fieldname.Organization]), "@") + if organization != "" { + registryEnvVar := "npm_config_@" + organization + ":registry" + if strings.ContainsAny(registryEnvVar, "=\x00") { + out.AddError(fmt.Errorf("organization cannot be represented as an npm environment variable")) + return + } + out.AddEnvVar(registryEnvVar, registry.String()) } else if strings.TrimSpace(in.ItemFields[fieldname.Host]) != "" { - fmt.Fprintf(&contents, "registry=%s\n", registry.String()) + out.AddEnvVar("npm_config_registry", registry.String()) } +} + +func (npmEnvProvisioner) Deprovision(_ context.Context, _ sdk.DeprovisionInput, _ *sdk.DeprovisionOutput) { + // Nothing to do here: environment variables get wiped automatically when the process exits. +} + +func (npmEnvProvisioner) Description() string { + return "Provision npm configuration environment variables" +} + +func pnpmProvisioner() sdk.Provisioner { + return pnpmEnvProvisioner{} +} + +type pnpmEnvProvisioner struct{} - registryPath := registry.EscapedPath() - if !strings.HasSuffix(registryPath, "/") { - registryPath += "/" +type pnpmAuthCredentials struct { + AuthToken string `json:"authToken"` +} + +func (pnpmEnvProvisioner) Provision(_ context.Context, in sdk.ProvisionInput, out *sdk.ProvisionOutput) { + registry, err := normalizeRegistry(in.ItemFields[fieldname.Host]) + if err != nil { + out.AddError(err) + return } - fmt.Fprintf(&contents, "//%s%s:_authToken=%s\n", registry.Host, registryPath, in.ItemFields[fieldname.Token]) - return []byte(contents.String()), nil + scope := "@" + if organization := strings.TrimPrefix(strings.TrimSpace(in.ItemFields[fieldname.Organization]), "@"); organization != "" { + scope = "@" + organization + } + + authConfig := map[string]map[string]pnpmAuthCredentials{ + registry.String(): { + scope: {AuthToken: in.ItemFields[fieldname.Token]}, + }, + } + contents, err := json.Marshal(authConfig) + if err != nil { + out.AddError(fmt.Errorf("marshalling pnpm auth configuration: %w", err)) + return + } + + out.AddEnvVar("PNPM_CONFIG__AUTH", string(contents)) +} + +func (pnpmEnvProvisioner) Deprovision(_ context.Context, _ sdk.DeprovisionInput, _ *sdk.DeprovisionOutput) { + // Nothing to do here: environment variables get wiped automatically when the process exits. +} + +func (pnpmEnvProvisioner) Description() string { + return "Provision PNPM_CONFIG__AUTH environment variable" } func normalizeRegistry(value string) (*url.URL, error) { diff --git a/plugins/npm/access_token_test.go b/plugins/npm/access_token_test.go index c468da17..add4d664 100644 --- a/plugins/npm/access_token_test.go +++ b/plugins/npm/access_token_test.go @@ -15,10 +15,9 @@ func TestAccessTokenProvisioner(t *testing.T) { fieldname.Token: "npm_example123", }, ExpectedOutput: sdk.ProvisionOutput{ - Files: map[string]sdk.OutputFile{ - "/tmp/.npmrc": {Contents: []byte("//registry.npmjs.org/:_authToken=npm_example123\n")}, + Environment: map[string]string{ + "npm_config_//registry.npmjs.org/:_authToken": "npm_example123", }, - CommandLine: []string{"--userconfig", "/tmp/.npmrc"}, }, }, "custom default registry": { @@ -27,10 +26,10 @@ func TestAccessTokenProvisioner(t *testing.T) { fieldname.Host: "registry.example.com/npm", }, ExpectedOutput: sdk.ProvisionOutput{ - Files: map[string]sdk.OutputFile{ - "/tmp/.npmrc": {Contents: []byte("registry=https://registry.example.com/npm/\n//registry.example.com/npm/:_authToken=custom_example123\n")}, + Environment: map[string]string{ + "npm_config_//registry.example.com/npm/:_authToken": "custom_example123", + "npm_config_registry": "https://registry.example.com/npm/", }, - CommandLine: []string{"--userconfig", "/tmp/.npmrc"}, }, }, "scoped custom registry": { @@ -40,10 +39,10 @@ func TestAccessTokenProvisioner(t *testing.T) { fieldname.Organization: "@acme", }, ExpectedOutput: sdk.ProvisionOutput{ - Files: map[string]sdk.OutputFile{ - "/tmp/.npmrc": {Contents: []byte("@acme:registry=https://registry.example.com/npm/\n//registry.example.com/npm/:_authToken=custom_example123\n")}, + Environment: map[string]string{ + "npm_config_//registry.example.com/npm/:_authToken": "custom_example123", + "npm_config_@acme:registry": "https://registry.example.com/npm/", }, - CommandLine: []string{"--userconfig", "/tmp/.npmrc"}, }, }, }) @@ -51,16 +50,36 @@ func TestAccessTokenProvisioner(t *testing.T) { func TestPNPMProvisioner(t *testing.T) { plugintest.TestProvisioner(t, PNPMCLI().Uses[0].Provisioner, map[string]plugintest.ProvisionCase{ - "uses an environment variable instead of an unsupported CLI option": { + "default registry": { ItemFields: map[sdk.FieldName]string{ fieldname.Token: "npm_example123", }, ExpectedOutput: sdk.ProvisionOutput{ Environment: map[string]string{ - "NPM_CONFIG_USERCONFIG": "/tmp/.npmrc", + "PNPM_CONFIG__AUTH": `{"https://registry.npmjs.org/":{"@":{"authToken":"npm_example123"}}}`, + }, + }, + }, + "custom default registry": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Token: "custom_example123", + fieldname.Host: "registry.example.com/npm", + }, + ExpectedOutput: sdk.ProvisionOutput{ + Environment: map[string]string{ + "PNPM_CONFIG__AUTH": `{"https://registry.example.com/npm/":{"@":{"authToken":"custom_example123"}}}`, }, - Files: map[string]sdk.OutputFile{ - "/tmp/.npmrc": {Contents: []byte("//registry.npmjs.org/:_authToken=npm_example123\n")}, + }, + }, + "scoped custom registry": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Token: "custom_example123", + fieldname.Host: "https://registry.example.com/npm/", + fieldname.Organization: "@acme", + }, + ExpectedOutput: sdk.ProvisionOutput{ + Environment: map[string]string{ + "PNPM_CONFIG__AUTH": `{"https://registry.example.com/npm/":{"@acme":{"authToken":"custom_example123"}}}`, }, }, },