chore: support external types in typescript codegen (#9633)

* chore: support external types in typescript codegen
* fix enums on external packages
* Support clibase.struct
* Add regexp
* Make gen with updated generator
This commit is contained in:
Steven Masley 2023-09-12 18:21:09 -05:00 committed by GitHub
parent 641bf272ed
commit 18c34ee456
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 255 additions and 84 deletions

View File

@ -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"`

View File

@ -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)
}
codeBlocks, err := g.generateAll()
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, xerrors.Errorf("parse package %q: %w", directory, err)
return nil, err
}
return codeBlocks, nil
codeBlocks, err := g.generateAll()
if err != nil {
return nil, xerrors.Errorf("generate package %q: %w", directory, err)
}
g.cachedResult = codeBlocks
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<ignored_types>.*)")
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 <Name> 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.

View File

@ -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<string, string>;
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<string, string>;
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<string, string[]>;
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<string, string>;
// 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",
];

View File

@ -12,6 +12,15 @@ const meta: Meta<typeof GitAuthSettingsPageView> = {
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: "",
},
],
},

View File

@ -56,7 +56,7 @@ export const GitAuthSettingsPageView = ({
</TableRow>
</TableHead>
<TableBody>
{((config.git_auth === null || config.git_auth.length === 0) && (
{((config.git_auth === null || config.git_auth?.length === 0) && (
<TableRow>
<TableCell colSpan={999}>
<div className={styles.empty}>
@ -65,7 +65,7 @@ export const GitAuthSettingsPageView = ({
</TableCell>
</TableRow>
)) ||
config.git_auth.map((git: GitAuthConfig) => {
config.git_auth?.map((git: GitAuthConfig) => {
const name = git.id || git.type;
return (
<TableRow key={name}>