From fcf1e683963879596d53b42e6065267d4ee93d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 16 Jul 2026 17:46:40 -0600 Subject: [PATCH 1/3] Add shell plugin for Netlify CLI --- plugins/netlify/netlify.go | 30 ++++++++++++++ plugins/netlify/personal_access_token.go | 31 ++++++++++++++ plugins/netlify/personal_access_token_test.go | 41 +++++++++++++++++++ plugins/netlify/plugin.go | 22 ++++++++++ 4 files changed, 124 insertions(+) create mode 100644 plugins/netlify/netlify.go create mode 100644 plugins/netlify/personal_access_token.go create mode 100644 plugins/netlify/personal_access_token_test.go create mode 100644 plugins/netlify/plugin.go diff --git a/plugins/netlify/netlify.go b/plugins/netlify/netlify.go new file mode 100644 index 000000000..eb122895d --- /dev/null +++ b/plugins/netlify/netlify.go @@ -0,0 +1,30 @@ +package netlify + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func NetlifyCLI() schema.Executable { + return schema.Executable{ + Name: "Netlify CLI", + Runs: []string{"netlify"}, + DocsURL: sdk.URL("https://cli.netlify.com"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + needsauth.NotWhenContainsArgs("login"), + needsauth.NotForExactArgs("logout"), + needsauth.NotWhenContainsArgs("switch"), + needsauth.NotWhenContainsArgs("completion"), + needsauth.NotWhenContainsArgs("unlink"), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.PersonalAccessToken, + }, + }, + } +} diff --git a/plugins/netlify/personal_access_token.go b/plugins/netlify/personal_access_token.go new file mode 100644 index 000000000..660b99717 --- /dev/null +++ b/plugins/netlify/personal_access_token.go @@ -0,0 +1,31 @@ +package netlify + +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" +) + +func PersonalAccessToken() schema.CredentialType { + return schema.CredentialType{ + Name: credname.PersonalAccessToken, + DocsURL: sdk.URL("https://docs.netlify.com/api-and-cli-guides/cli-guides/get-started-with-cli"), + ManagementURL: sdk.URL("https://app.netlify.com/user/applications#personal-access-tokens"), + Fields: []schema.CredentialField{ + { + Name: fieldname.Token, + MarkdownDescription: "Token used to authenticate to Netlify.", + Secret: true, + }, + }, + DefaultProvisioner: provision.EnvVars(defaultEnvVarMapping), + Importer: importer.TryEnvVarPair(defaultEnvVarMapping), + } +} + +var defaultEnvVarMapping = map[string]sdk.FieldName{ + "NETLIFY_AUTH_TOKEN": fieldname.Token, +} diff --git a/plugins/netlify/personal_access_token_test.go b/plugins/netlify/personal_access_token_test.go new file mode 100644 index 000000000..598570059 --- /dev/null +++ b/plugins/netlify/personal_access_token_test.go @@ -0,0 +1,41 @@ +package netlify + +import ( + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestPersonalAccessTokenProvisioner(t *testing.T) { + plugintest.TestProvisioner(t, PersonalAccessToken().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "default": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Token: "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uV", + }, + ExpectedOutput: sdk.ProvisionOutput{ + Environment: map[string]string{ + "NETLIFY_AUTH_TOKEN": "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uV", + }, + }, + }, + }) +} + +func TestPersonalAccessTokenImporter(t *testing.T) { + plugintest.TestImporter(t, PersonalAccessToken().Importer, map[string]plugintest.ImportCase{ + "environment": { + Environment: map[string]string{ + "NETLIFY_AUTH_TOKEN": "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uV", + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Token: "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uV", + }, + }, + }, + }, + }) +} diff --git a/plugins/netlify/plugin.go b/plugins/netlify/plugin.go new file mode 100644 index 000000000..a70fb068d --- /dev/null +++ b/plugins/netlify/plugin.go @@ -0,0 +1,22 @@ +package netlify + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "netlify", + Platform: schema.PlatformInfo{ + Name: "Netlify", + Homepage: sdk.URL("https://netlify.com"), + }, + Credentials: []schema.CredentialType{ + PersonalAccessToken(), + }, + Executables: []schema.Executable{ + NetlifyCLI(), + }, + } +} From e76d1b1989ad26d60b6ae25980fffce16d8ce747 Mon Sep 17 00:00:00 2001 From: rr3khan <65298773+rr3khan@users.noreply.github.com> Date: Sun, 18 Oct 2026 18:50:54 -0400 Subject: [PATCH 2/3] Update personal access token in test case Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- plugins/netlify/personal_access_token_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/netlify/personal_access_token_test.go b/plugins/netlify/personal_access_token_test.go index 598570059..49abe6e96 100644 --- a/plugins/netlify/personal_access_token_test.go +++ b/plugins/netlify/personal_access_token_test.go @@ -12,11 +12,11 @@ func TestPersonalAccessTokenProvisioner(t *testing.T) { plugintest.TestProvisioner(t, PersonalAccessToken().DefaultProvisioner, map[string]plugintest.ProvisionCase{ "default": { ItemFields: map[sdk.FieldName]string{ - fieldname.Token: "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uV", + fieldname.Token: "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uVexample", }, ExpectedOutput: sdk.ProvisionOutput{ Environment: map[string]string{ - "NETLIFY_AUTH_TOKEN": "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uV", + "NETLIFY_AUTH_TOKEN": "kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6oP0qR4sT8uVexample", }, }, }, From 6abb2f9633b6ca82ad0ea0672eebb48ba724f839 Mon Sep 17 00:00:00 2001 From: Riyad Khan Date: Sun, 18 Oct 2026 18:57:10 -0400 Subject: [PATCH 3/3] netlify: import token from netlify-cli config.json netlify login stores the token in config.json under users..auth.token (~/Library/Preferences/netlify on macOS, ~/.config/netlify on Linux), per https://docs.netlify.com/api-and-cli-guides/cli-guides/get-started-with-cli/#configjson-location. Keep the NETLIFY_AUTH_TOKEN importer and also try the config file. --- plugins/netlify/personal_access_token.go | 42 ++++++++++++++++++- plugins/netlify/personal_access_token_test.go | 33 +++++++++++++++ plugins/netlify/test-fixtures/config.json | 16 +++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 plugins/netlify/test-fixtures/config.json diff --git a/plugins/netlify/personal_access_token.go b/plugins/netlify/personal_access_token.go index 660b99717..1dd63b699 100644 --- a/plugins/netlify/personal_access_token.go +++ b/plugins/netlify/personal_access_token.go @@ -1,6 +1,8 @@ package netlify import ( + "context" + "github.com/1Password/shell-plugins/sdk" "github.com/1Password/shell-plugins/sdk/importer" "github.com/1Password/shell-plugins/sdk/provision" @@ -22,10 +24,48 @@ func PersonalAccessToken() schema.CredentialType { }, }, DefaultProvisioner: provision.EnvVars(defaultEnvVarMapping), - Importer: importer.TryEnvVarPair(defaultEnvVarMapping), + Importer: importer.TryAll( + importer.TryEnvVarPair(defaultEnvVarMapping), + importer.MacOnly(TryNetlifyConfigFile("~/Library/Preferences/netlify/config.json")), + importer.LinuxOnly(TryNetlifyConfigFile("~/.config/netlify/config.json")), + ), } } var defaultEnvVarMapping = map[string]sdk.FieldName{ "NETLIFY_AUTH_TOKEN": fieldname.Token, } + +// TryNetlifyConfigFile imports the token that `netlify login` stores in the global config.json, +// under users..auth.token for the active userId. +func TryNetlifyConfigFile(path string) sdk.Importer { + return importer.TryFile(path, func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { + var config Config + if err := contents.ToJSON(&config); err != nil { + out.AddError(err) + return + } + + user, ok := config.Users[config.UserID] + if !ok || user.Auth.Token == "" { + return + } + + out.AddCandidate(sdk.ImportCandidate{ + Fields: map[sdk.FieldName]string{ + fieldname.Token: user.Auth.Token, + }, + }) + }) +} + +type Config struct { + UserID string `json:"userId"` + Users map[string]ConfigUser `json:"users"` +} + +type ConfigUser struct { + Auth struct { + Token string `json:"token"` + } `json:"auth"` +} diff --git a/plugins/netlify/personal_access_token_test.go b/plugins/netlify/personal_access_token_test.go index 49abe6e96..2abd81f8c 100644 --- a/plugins/netlify/personal_access_token_test.go +++ b/plugins/netlify/personal_access_token_test.go @@ -37,5 +37,38 @@ func TestPersonalAccessTokenImporter(t *testing.T) { }, }, }, + "config file (macOS)": { + OS: "darwin", + Files: map[string]string{ + "~/Library/Preferences/netlify/config.json": plugintest.LoadFixture(t, "config.json"), + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Token: "nfp_kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6EXAMPLE", + }, + }, + }, + }, + "config file (Linux)": { + OS: "linux", + Files: map[string]string{ + "~/.config/netlify/config.json": plugintest.LoadFixture(t, "config.json"), + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Token: "nfp_kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6EXAMPLE", + }, + }, + }, + }, + "config file (logged out)": { + OS: "darwin", + Files: map[string]string{ + "~/Library/Preferences/netlify/config.json": `{"telemetryDisabled": false, "cliId": "55f2daf5-ee38-4fa0-8fd8-e67892a123d6", "userId": null}`, + }, + ExpectedCandidates: nil, + }, }) } diff --git a/plugins/netlify/test-fixtures/config.json b/plugins/netlify/test-fixtures/config.json new file mode 100644 index 000000000..37d7c1786 --- /dev/null +++ b/plugins/netlify/test-fixtures/config.json @@ -0,0 +1,16 @@ +{ + "telemetryDisabled": false, + "cliId": "55f2daf5-ee38-4fa0-8fd8-e67892a123d6", + "userId": "5f3a1b2c4d5e6f7a8b9c0d1e", + "users": { + "5f3a1b2c4d5e6f7a8b9c0d1e": { + "id": "5f3a1b2c4d5e6f7a8b9c0d1e", + "name": "Wendy Appleseed", + "email": "wendy.appleseed@example.com", + "auth": { + "token": "nfp_kV9wTq3ZtN0aB4cD7eF1gH5iJ8kL2mN6EXAMPLE", + "github": {} + } + } + } +}