diff --git a/plugins/vercel/api_token.go b/plugins/vercel/api_token.go new file mode 100644 index 000000000..839b0de6c --- /dev/null +++ b/plugins/vercel/api_token.go @@ -0,0 +1,82 @@ +package vercel + +import ( + "context" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/importer" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func APIToken() schema.CredentialType { + return schema.CredentialType{ + Name: credname.APIToken, + DocsURL: sdk.URL("https://vercel.com/docs/rest-api#authentication"), + ManagementURL: sdk.URL("https://vercel.com/account/tokens"), + Fields: []schema.CredentialField{ + { + Name: fieldname.Token, + MarkdownDescription: "Token used to authenticate to Vercel.", + Secret: true, + Composition: &schema.ValueComposition{ + Length: 24, + Charset: schema.Charset{ + Lowercase: true, + Uppercase: true, + Digits: true, + }, + }, + }, + }, + DefaultProvisioner: vercelProvisioner{}, + Importer: importer.TryAll( + importer.MacOnly(TryVercelConfigFile("~/Library/Application Support/com.vercel.cli/auth.json")), + importer.LinuxOnly(TryVercelConfigFile("~/.config/com.vercel.cli/auth.json")), + ), + } +} + +type vercelProvisioner struct{} + +func (v vercelProvisioner) Description() string { + return "Vercel cli token provisioner" +} + +func (v vercelProvisioner) Provision(ctx context.Context, input sdk.ProvisionInput, output *sdk.ProvisionOutput) { + output.AddArgs("--token", input.ItemFields[fieldname.Token]) +} + +func (v vercelProvisioner) Deprovision(ctx context.Context, input sdk.DeprovisionInput, output *sdk.DeprovisionOutput) { + // No-op +} + +func TryVercelConfigFile(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 + } + + if config.Token == "" { + return + } + + out.AddCandidate( + sdk.ImportCandidate{ + Fields: map[sdk.FieldName]string{ + fieldname.Token: config.Token, + }, + }, + ) + }, + ) +} + +type Config struct { + Token string `json:"token"` +} diff --git a/plugins/vercel/api_token_test.go b/plugins/vercel/api_token_test.go new file mode 100644 index 000000000..d689011a8 --- /dev/null +++ b/plugins/vercel/api_token_test.go @@ -0,0 +1,57 @@ +package vercel + +import ( + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestAPITokenProvisioner(t *testing.T) { + plugintest.TestProvisioner( + t, APIToken().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "default": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Token: "tZk79pLyPLGgUVlkHbnLeXgl", + }, + ExpectedOutput: sdk.ProvisionOutput{ + CommandLine: []string{"--token", "tZk79pLyPLGgUVlkHbnLeXgl"}, + }, + }, + }, + ) +} + +func TestAPITokenImporter(t *testing.T) { + plugintest.TestImporter( + t, APIToken().Importer, map[string]plugintest.ImportCase{ + "config file (macOS)": { + OS: "darwin", + Files: map[string]string{ + "~/Library/Application Support/com.vercel.cli/auth.json": plugintest.LoadFixture(t, "auth.json"), + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Token: "tZk79pLyPLGgUVlkHbnLeXgl", + }, + }, + }, + }, + "config file (Linux)": { + OS: "linux", + Files: map[string]string{ + "~/.config/com.vercel.cli/auth.json": plugintest.LoadFixture(t, "auth.json"), + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Token: "tZk79pLyPLGgUVlkHbnLeXgl", + }, + }, + }, + }, + }, + ) +} diff --git a/plugins/vercel/plugin.go b/plugins/vercel/plugin.go new file mode 100644 index 000000000..3d4598661 --- /dev/null +++ b/plugins/vercel/plugin.go @@ -0,0 +1,22 @@ +package vercel + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "vercel", + Platform: schema.PlatformInfo{ + Name: "Vercel", + Homepage: sdk.URL("https://vercel.com"), + }, + Credentials: []schema.CredentialType{ + APIToken(), + }, + Executables: []schema.Executable{ + VercelCLI(), + }, + } +} diff --git a/plugins/vercel/test-fixtures/auth.json b/plugins/vercel/test-fixtures/auth.json new file mode 100644 index 000000000..e64b47253 --- /dev/null +++ b/plugins/vercel/test-fixtures/auth.json @@ -0,0 +1,3 @@ +{ + "token": "tZk79pLyPLGgUVlkHbnLeXgl" +} diff --git a/plugins/vercel/vercel.go b/plugins/vercel/vercel.go new file mode 100644 index 000000000..52cf3f067 --- /dev/null +++ b/plugins/vercel/vercel.go @@ -0,0 +1,28 @@ +package vercel + +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 VercelCLI() schema.Executable { + return schema.Executable{ + Name: "Vercel CLI", + Runs: []string{"vercel"}, + DocsURL: sdk.URL("https://vercel.com/docs/cli"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + needsauth.NotWhenContainsArgs("login"), + needsauth.NotWhenContainsArgs("-t"), + needsauth.NotWhenContainsArgs("--token"), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.APIToken, + }, + }, + } +}