diff --git a/codersdk/deployment.go b/codersdk/deployment.go index dcbe2a1e94679..46fab26685082 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -409,9 +409,6 @@ func DefaultCacheDir() string { } // DeploymentConfig contains both the deployment values and how they're set. -// -// @typescript-ignore DeploymentConfig -// apitypings doesn't know how to generate the OptionSet... yet. type DeploymentConfig struct { Values *DeploymentValues `json:"config,omitempty"` Options clibase.OptionSet `json:"options,omitempty"` diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index a149b869999ca..45c97399610e3 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -15,6 +15,7 @@ import ( "text/template" "github.com/fatih/structtag" + "golang.org/x/exp/slices" "golang.org/x/text/cases" "golang.org/x/text/language" "golang.org/x/tools/go/packages" @@ -26,17 +27,35 @@ import ( ) var ( + // baseDirs are the directories to introspect for types to generate. baseDirs = [...]string{"./codersdk", "./coderd/healthcheck", "./coderd/healthcheck/derphealth"} - indent = " " + // externalTypes are types that are not in the baseDirs, but we want to + // support. These are usually types that are used in the baseDirs. + // Do not include things like "Database", as that would break the idea + // of splitting db and api types. + // Only include dirs that are client facing packages. + externalTypeDirs = [...]string{"./cli/clibase"} + indent = " " ) func main() { ctx := context.Background() log := slog.Make(sloghuman.Sink(os.Stderr)) + + external := []*Generator{} + for _, dir := range externalTypeDirs { + extGen, err := ParseDirectory(ctx, log, dir) + if err != nil { + log.Fatal(ctx, fmt.Sprintf("parse external directory %s: %s", dir, err.Error())) + } + extGen.onlyOptIn = true + external = append(external, extGen) + } + _, _ = fmt.Print("// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.\n\n") for _, baseDir := range baseDirs { _, _ = fmt.Printf("// The code below is generated from %s.\n\n", strings.TrimPrefix(baseDir, "./")) - output, err := Generate(baseDir) + output, err := Generate(baseDir, external...) if err != nil { log.Fatal(ctx, err.Error()) } @@ -44,18 +63,40 @@ func main() { // Just cat the output to a file to capture it _, _ = fmt.Print(output, "\n\n") } + + for i, ext := range external { + var ts *TypescriptTypes + for { + var err error + start := len(ext.allowList) + ts, err = ext.generateAll() + if err != nil { + log.Fatal(ctx, fmt.Sprintf("generate external: %s", err.Error())) + } + if len(ext.allowList) != start { + // This is so dumb, but basically the allowList can grow, and if + // it does, we need to regenerate. + continue + } + break + } + + dir := externalTypeDirs[i] + _, _ = fmt.Printf("// The code below is generated from %s.\n\n", strings.TrimPrefix(dir, "./")) + _, _ = fmt.Print(ts.String(), "\n\n") + } } -func Generate(directory string) (string, error) { +func Generate(directory string, externals ...*Generator) (string, error) { ctx := context.Background() log := slog.Make(sloghuman.Sink(os.Stderr)) - codeBlocks, err := GenerateFromDirectory(ctx, log, directory) + gen, err := GenerateFromDirectory(ctx, log, directory, externals...) if err != nil { return "", err } // Just cat the output to a file to capture it - return codeBlocks.String(), nil + return gen.cachedResult.String(), nil } // TypescriptTypes holds all the code blocks created. @@ -109,23 +150,34 @@ func (t TypescriptTypes) String() string { return strings.TrimRight(s.String(), "\n") } -// GenerateFromDirectory will return all the typescript code blocks for a directory -func GenerateFromDirectory(ctx context.Context, log slog.Logger, directory string) (*TypescriptTypes, error) { - g := Generator{ - log: log, - builtins: make(map[string]string), +func ParseDirectory(ctx context.Context, log slog.Logger, directory string, externals ...*Generator) (*Generator, error) { + g := &Generator{ + log: log, + builtins: make(map[string]string), + externals: externals, } err := g.parsePackage(ctx, directory) if err != nil { return nil, xerrors.Errorf("parse package %q: %w", directory, err) } + return g, nil +} + +// GenerateFromDirectory will return all the typescript code blocks for a directory +func GenerateFromDirectory(ctx context.Context, log slog.Logger, directory string, externals ...*Generator) (*Generator, error) { + g, err := ParseDirectory(ctx, log, directory, externals...) + if err != nil { + return nil, err + } + codeBlocks, err := g.generateAll() if err != nil { - return nil, xerrors.Errorf("parse package %q: %w", directory, err) + return nil, xerrors.Errorf("generate package %q: %w", directory, err) } + g.cachedResult = codeBlocks - return codeBlocks, nil + return g, nil } type Generator struct { @@ -133,6 +185,16 @@ type Generator struct { pkg *packages.Package log slog.Logger + // allowList if set only generates types in the allow list. + // This is kinda a hack to get around the fact that external types + // only should generate referenced types, and multiple packages can + // reference the same external types. + onlyOptIn bool + allowList []string + + // externals are other packages referenced. Optional + externals []*Generator + // builtins is kinda a hack to get around the fact that using builtin // generic constraints is common. We want to support them even though // they are external to our package. @@ -141,6 +203,8 @@ type Generator struct { // cannot be implemented in go. So they are a first class thing that we just // have to make a static string for ¯\_(ツ)_/¯ builtins map[string]string + + cachedResult *TypescriptTypes } // parsePackage takes a list of patterns such as a directory, and parses them. @@ -180,6 +244,10 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) { AllowedTypes: make(map[string]struct{}), } + for _, a := range g.allowList { + m.AllowedTypes[strings.TrimSpace(a)] = struct{}{} + } + // Look for comments that indicate to ignore a type for typescript generation. ignoreRegex := regexp.MustCompile("@typescript-ignore[:]?(?P.*)") for _, file := range g.pkg.Syntax { @@ -303,11 +371,16 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error { } // If we have allowed types, only allow those to be generated. - if _, ok := m.AllowedTypes[obj.Name()]; len(m.AllowedTypes) > 0 && !ok { - return nil + if _, ok := m.AllowedTypes[obj.Name()]; (len(m.AllowedTypes) > 0 || g.onlyOptIn) && !ok { + // Allow constants to pass through, they are only included if the enum + // is allowed. + _, ok := obj.(*types.Const) + if !ok { + return nil + } } - objName := objName(obj) + objectName := objName(obj) switch obj := obj.(type) { // All named types are type declarations @@ -322,13 +395,13 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error { // Structs are obvious. codeBlock, err := g.buildStruct(obj, underNamed) if err != nil { - return xerrors.Errorf("generate %q: %w", objName, err) + return xerrors.Errorf("generate %q: %w", objectName, err) } - m.Structs[objName] = codeBlock + m.Structs[objectName] = codeBlock case *types.Basic: // type string // These are enums. Store to expand later. - m.Enums[objName] = obj + m.Enums[objectName] = obj case *types.Map, *types.Array, *types.Slice: // Declared maps that are not structs are still valid codersdk objects. // Handle them custom by calling 'typescriptType' directly instead of @@ -337,7 +410,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error { // These are **NOT** enums, as a map in Go would never be used for an enum. ts, err := g.typescriptType(obj.Type().Underlying()) if err != nil { - return xerrors.Errorf("(map) generate %q: %w", objName, err) + return xerrors.Errorf("(map) generate %q: %w", objectName, err) } var str strings.Builder @@ -347,8 +420,8 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error { _, _ = str.WriteRune('\n') } // Use similar output syntax to enums. - _, _ = str.WriteString(fmt.Sprintf("export type %s = %s\n", objName, ts.ValueType)) - m.Structs[objName] = str.String() + _, _ = str.WriteString(fmt.Sprintf("export type %s = %s\n", objectName, ts.ValueType)) + m.Structs[objectName] = str.String() case *types.Interface: // Interfaces are used as generics. Non-generic interfaces are // not supported. @@ -366,9 +439,9 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error { block, err := g.buildUnion(obj, union) if err != nil { - return xerrors.Errorf("generate union %q: %w", objName, err) + return xerrors.Errorf("generate union %q: %w", objectName, err) } - m.Generics[objName] = block + m.Generics[objectName] = block } case *types.Signature: // Ignore named functions. @@ -383,13 +456,13 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error { case *types.Const: // We only care about named constant types, since they are enums if named, ok := obj.Type().(*types.Named); ok { - name := named.Obj().Name() - m.EnumConsts[name] = append(m.EnumConsts[name], obj) + enumObjName := objName(named.Obj()) + m.EnumConsts[enumObjName] = append(m.EnumConsts[enumObjName], obj) } case *types.Func: // Noop default: - _, _ = fmt.Println(objName) + _, _ = fmt.Println(objectName) } return nil } @@ -751,9 +824,22 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { n := ty // These are external named types that we handle uniquely. + // This is unfortunate, but our current code assumes all defined + // types are enums, but these are really just basic primitives. + // We would need to add more logic to determine this, but for now + // just hard code them. switch n.String() { + case "github.com/coder/coder/v2/cli/clibase.Regexp": + return TypescriptType{ValueType: "string"}, nil + case "github.com/coder/coder/v2/cli/clibase.HostPort": + // Custom marshal json to be a string + return TypescriptType{ValueType: "string"}, nil + case "github.com/coder/coder/v2/cli/clibase.StringArray": + return TypescriptType{ValueType: "string[]"}, nil case "github.com/coder/coder/v2/cli/clibase.String": return TypescriptType{ValueType: "string"}, nil + case "github.com/coder/coder/v2/cli/clibase.YAMLConfigPath": + return TypescriptType{ValueType: "string"}, nil case "github.com/coder/coder/v2/cli/clibase.Strings": return TypescriptType{ValueType: "string[]"}, nil case "github.com/coder/coder/v2/cli/clibase.Int64": @@ -783,14 +869,42 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { return TypescriptType{ValueType: "string"}, nil } + // Some hard codes are a bit trickier. + //nolint:gocritic,revive // I prefer the switch for extensibility later. + switch { + // Struct is a generic, so the type has generic constraints in the string. + case regexp.MustCompile(`github\.com/coder/coder/v2/cli/clibase.Struct\[.*\]`).MatchString(n.String()): + // The marshal json just marshals the underlying value. + str, ok := ty.Underlying().(*types.Struct) + if ok { + return g.typescriptType(str.Field(0).Type()) + } + } + // Then see if the type is defined elsewhere. If it is, we can just // put the objName as it will be defined in the typescript codeblock // we generate. objName := objName(n.Obj()) genericName := "" genericTypes := make(map[string]string) - pkgName := n.Obj().Pkg().Name() - if obj := g.pkg.Types.Scope().Lookup(n.Obj().Name()); g.pkg.Name == pkgName && obj != nil { + + obj, objGen, local := g.lookupNamedReference(n) + if obj != nil { + if g.onlyOptIn && !slices.Contains(g.allowList, n.Obj().Name()) { + // This is kludgy, but if we are an external package, + // we need to also include dependencies. There is no + // good way to return all extra types we need to include, + // so just add them to the allow list and hope the caller notices + // the slice grew... + g.allowList = append(g.allowList, n.Obj().Name()) + } + if !local { + objGen.allowList = append(objGen.allowList, n.Obj().Name()) + g.log.Debug(context.Background(), "found external type", + "name", objName, + "ext_pkg", objGen.pkg.String(), + ) + } // Sweet! Using other typescript types as fields. This could be an // enum or another struct if args := n.TypeArgs(); args != nil && args.Len() > 0 { @@ -817,10 +931,13 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { genericName = objName + fmt.Sprintf("<%s>", strings.Join(genericNames, ", ")) objName += fmt.Sprintf("<%s>", strings.Join(genericConstraints, ", ")) } + + cmt := "" return TypescriptType{ - GenericTypes: genericTypes, - GenericValue: genericName, - ValueType: objName, + GenericTypes: genericTypes, + GenericValue: genericName, + ValueType: objName, + AboveTypeLine: cmt, }, nil } @@ -842,7 +959,10 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { if err != nil { return TypescriptType{}, xerrors.Errorf("named underlying: %w", err) } - ts.AboveTypeLine = indentedComment(fmt.Sprintf("This is likely an enum in an external package (%q)", n.String())) + if ts.AboveTypeLine == "" { + // If no comment exists explaining where this type comes from, add one. + ts.AboveTypeLine = indentedComment(fmt.Sprintf("This is likely an enum in an external package (%q)", n.String())) + } return ts, nil case *types.Pointer: // Dereference pointers. @@ -868,6 +988,20 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { ), }, nil } + + // Do support "Stringer" interfaces, they likely can get string + // marshalled. + for i := 0; i < intf.NumMethods(); i++ { + meth := intf.Method(i) + if meth.Name() == "String" { + return TypescriptType{ + ValueType: "string", + AboveTypeLine: indentedComment("actual value is an interface that implements 'String()'"), + Optional: false, + }, nil + } + } + // All complex interfaces should be named. So if we get here, that means // we are using anonymous interfaces. Which is just weird and not supported. // Example: @@ -928,6 +1062,22 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { return TypescriptType{}, xerrors.Errorf("unknown type: %s", ty.String()) } +func (g *Generator) lookupNamedReference(n *types.Named) (obj types.Object, generator *Generator, local bool) { + pkgName := n.Obj().Pkg().Name() + + if obj := g.pkg.Types.Scope().Lookup(n.Obj().Name()); g.pkg.Name == pkgName && obj != nil { + return obj, g, true + } + + for _, ext := range g.externals { + if obj := ext.pkg.Types.Scope().Lookup(n.Obj().Name()); ext.pkg.Name == pkgName && obj != nil { + return obj, ext, false + } + } + + return nil, nil, false +} + // isBuiltIn returns the string for a builtin type that we want to support // if the name is a reserved builtin type. This is for types like 'comparable'. // These types are not implemented in golang, so we just have to hardcode it. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 90398957fa91c..a1b1c9e29f8d1 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -323,7 +323,6 @@ export interface DERPServerConfig { readonly region_id: number; readonly region_code: string; readonly region_name: string; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly stun_addresses: string[]; readonly relay_url: string; } @@ -335,6 +334,12 @@ export interface DangerousConfig { readonly allow_all_cors: boolean; } +// From codersdk/deployment.go +export interface DeploymentConfig { + readonly config?: DeploymentValues; + readonly options?: ClibaseOptionSet; +} + // From codersdk/deployment.go export interface DeploymentStats { readonly aggregated_from: string; @@ -357,9 +362,7 @@ export interface DeploymentValues { readonly derp?: DERP; readonly prometheus?: PrometheusConfig; readonly pprof?: PprofConfig; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly proxy_trusted_headers?: string[]; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly proxy_trusted_origins?: string[]; readonly cache_directory?: string; readonly in_memory_database?: boolean; @@ -371,7 +374,6 @@ export interface DeploymentValues { readonly trace?: TraceConfig; readonly secure_auth_cookie?: boolean; readonly strict_transport_security?: number; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly strict_transport_security_options?: string[]; readonly ssh_keygen_algorithm?: string; readonly metrics_cache_refresh_interval?: number; @@ -379,11 +381,9 @@ export interface DeploymentValues { readonly agent_fallback_troubleshooting_url?: string; readonly browser_only?: boolean; readonly scim_api_key?: string; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly external_token_encryption_keys?: string[]; readonly provisioner?: ProvisionerConfig; readonly rate_limit?: RateLimitConfig; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly experiments?: string[]; readonly update_check?: boolean; readonly max_token_lifetime?: number; @@ -395,21 +395,16 @@ export interface DeploymentValues { readonly disable_session_expiry_refresh?: boolean; readonly disable_password_auth?: boolean; readonly support?: SupportConfig; - // Named type "github.com/coder/coder/v2/cli/clibase.Struct[[]github.com/coder/coder/v2/codersdk.GitAuthConfig]" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly git_auth?: any; + readonly git_auth?: GitAuthConfig[]; readonly config_ssh?: SSHConfig; readonly wgtunnel_host?: string; readonly disable_owner_workspace_exec?: boolean; readonly proxy_health_status_interval?: number; readonly enable_terraform_debug_mode?: boolean; readonly user_quiet_hours_schedule?: UserQuietHoursScheduleConfig; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.YAMLConfigPath") readonly config?: string; readonly write_config?: boolean; - // Named type "github.com/coder/coder/v2/cli/clibase.HostPort" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly address?: any; + readonly address?: string; } // From codersdk/deployment.go @@ -559,7 +554,6 @@ export interface LinkConfig { // From codersdk/deployment.go export interface LoggingConfig { - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly log_filter: string[]; readonly human: string; readonly json: string; @@ -593,9 +587,7 @@ export interface OAuth2Config { export interface OAuth2GithubConfig { readonly client_id: string; readonly client_secret: string; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly allowed_orgs: string[]; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly allowed_teams: string[]; readonly allow_signups: boolean; readonly allow_everyone: boolean; @@ -623,31 +615,20 @@ export interface OIDCConfig { readonly client_secret: string; readonly client_key_file: string; readonly client_cert_file: string; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly email_domain: string[]; readonly issuer_url: string; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly scopes: string[]; readonly ignore_email_verified: boolean; readonly username_field: string; readonly email_field: string; - // Named type "github.com/coder/coder/v2/cli/clibase.Struct[map[string]string]" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly auth_url_params: any; + readonly auth_url_params: Record; readonly ignore_user_info: boolean; readonly group_auto_create: boolean; - // Named type "github.com/coder/coder/v2/cli/clibase.Regexp" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly group_regex_filter: any; + readonly group_regex_filter: string; readonly groups_field: string; - // Named type "github.com/coder/coder/v2/cli/clibase.Struct[map[string]string]" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly group_mapping: any; + readonly group_mapping: Record; readonly user_role_field: string; - // Named type "github.com/coder/coder/v2/cli/clibase.Struct[map[string][]string]" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly user_role_mapping: any; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") + readonly user_role_mapping: Record; readonly user_roles_default: string[]; readonly sign_in_text: string; readonly icon_url: string; @@ -705,17 +686,13 @@ export interface PatchWorkspaceProxy { // From codersdk/deployment.go export interface PprofConfig { readonly enable: boolean; - // Named type "github.com/coder/coder/v2/cli/clibase.HostPort" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly address: any; + readonly address: string; } // From codersdk/deployment.go export interface PrometheusConfig { readonly enable: boolean; - // Named type "github.com/coder/coder/v2/cli/clibase.HostPort" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly address: any; + readonly address: string; readonly collect_agent_stats: boolean; readonly collect_db_metrics: boolean; } @@ -827,7 +804,6 @@ export interface Role { // From codersdk/deployment.go export interface SSHConfig { readonly DeploymentName: string; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly SSHConfigOptions: string[]; } @@ -862,9 +838,7 @@ export interface SessionCountDeploymentStats { // From codersdk/deployment.go export interface SupportConfig { - // Named type "github.com/coder/coder/v2/cli/clibase.Struct[[]github.com/coder/coder/v2/codersdk.LinkConfig]" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly links: any; + readonly links: LinkConfig[]; } // From codersdk/deployment.go @@ -875,15 +849,11 @@ export interface SwaggerConfig { // From codersdk/deployment.go export interface TLSConfig { readonly enable: boolean; - // Named type "github.com/coder/coder/v2/cli/clibase.HostPort" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly address: any; + readonly address: string; readonly redirect_http: boolean; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly cert_file: string[]; readonly client_auth: string; readonly client_ca_file: string; - // This is likely an enum in an external package ("github.com/coder/coder/v2/cli/clibase.StringArray") readonly key_file: string[]; readonly min_version: string; readonly client_cert_file: string; @@ -2076,3 +2046,48 @@ export interface DerphealthStunReport { readonly CanSTUN: boolean; readonly Error?: string; } + +// The code below is generated from cli/clibase. + +// From clibase/clibase.go +export type ClibaseAnnotations = Record; + +// From clibase/clibase.go +export interface ClibaseGroup { + readonly parent?: ClibaseGroup; + readonly name?: string; + readonly yaml?: string; + readonly description?: string; +} + +// From clibase/option.go +export interface ClibaseOption { + readonly name?: string; + readonly description?: string; + readonly required?: boolean; + readonly flag?: string; + readonly flag_shorthand?: string; + readonly env?: string; + readonly yaml?: string; + readonly default?: string; + // actual value is an interface that implements 'String()' + readonly value?: string; + readonly annotations?: ClibaseAnnotations; + readonly group?: ClibaseGroup; + readonly use_instead?: ClibaseOption[]; + readonly hidden?: boolean; + readonly value_source?: ClibaseValueSource; +} + +// From clibase/option.go +export type ClibaseOptionSet = ClibaseOption[]; + +// From clibase/option.go +export type ClibaseValueSource = "" | "default" | "env" | "flag" | "yaml"; +export const ClibaseValueSources: ClibaseValueSource[] = [ + "", + "default", + "env", + "flag", + "yaml", +]; diff --git a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx index 4c9892de562d5..1708841caaca2 100644 --- a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx @@ -12,6 +12,15 @@ const meta: Meta = { type: "GitHub", client_id: "client_id", regex: "regex", + auth_url: "", + token_url: "", + validate_url: "", + app_install_url: "https://github.com/apps/coder/installations/new", + app_installations_url: "", + no_refresh: false, + scopes: [], + device_flow: true, + device_code_url: "", }, ], }, diff --git a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx index 5d1deb35ee8c0..e56634a93f39f 100644 --- a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx @@ -56,7 +56,7 @@ export const GitAuthSettingsPageView = ({ - {((config.git_auth === null || config.git_auth.length === 0) && ( + {((config.git_auth === null || config.git_auth?.length === 0) && (
@@ -65,7 +65,7 @@ export const GitAuthSettingsPageView = ({ )) || - config.git_auth.map((git: GitAuthConfig) => { + config.git_auth?.map((git: GitAuthConfig) => { const name = git.id || git.type; return (