mirror of https://github.com/coder/coder.git
feat: Switch packages for typescript generation code (#1196)
* feat: Switch packages for typescript generation code Supports a larger set of types - [x] Basics (string/int/etc) - [x] Maps - [x] Slices - [x] Enums - [x] Pointers
This commit is contained in:
parent
afc43fe95f
commit
e330dc1321
|
@ -26,6 +26,7 @@ func New(serverURL *url.URL) *Client {
|
|||
}
|
||||
|
||||
// Client is an HTTP caller for methods to the Coder API.
|
||||
// @typescript-ignore Client
|
||||
type Client struct {
|
||||
HTTPClient *http.Client
|
||||
SessionToken string
|
||||
|
@ -113,6 +114,7 @@ func readBodyAsError(res *http.Response) error {
|
|||
}
|
||||
|
||||
// Error represents an unaccepted or invalid request to the API.
|
||||
// @typescript-ignore Error
|
||||
type Error struct {
|
||||
httpapi.Response
|
||||
|
||||
|
|
1
go.mod
1
go.mod
|
@ -109,6 +109,7 @@ require (
|
|||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/tools v0.1.10
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f
|
||||
google.golang.org/api v0.75.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
|
|
1
go.sum
1
go.sum
|
@ -2287,6 +2287,7 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# APITypings
|
||||
|
||||
This main.go generates typescript types from the codersdk types in Go.
|
||||
|
||||
# Features
|
||||
|
||||
- Supports Go types
|
||||
- [x] Basics (string/int/etc)
|
||||
- [x] Maps
|
||||
- [x] Slices
|
||||
- [x] Enums
|
||||
- [x] Pointers
|
||||
- [ ] External Types (uses `any` atm)
|
||||
- Some custom external types are hardcoded in (eg: time.Time)
|
||||
|
||||
|
||||
## Type overrides
|
||||
|
||||
```golang
|
||||
type Foo struct {
|
||||
// Force the typescript type to be a number
|
||||
CreatedAt time.Duration `json:"created_at" typescript:"number"`
|
||||
}
|
||||
```
|
||||
|
||||
## Ignore Types
|
||||
|
||||
Do not generate ignored types.
|
||||
|
||||
```golang
|
||||
// @typescript-ignore InternalType
|
||||
type InternalType struct {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
# Future Ideas
|
||||
|
||||
- Should `omitempty` in the `json` tag indicate optional?
|
||||
- Use a yaml config for overriding certain types
|
|
@ -1,208 +1,426 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"go/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/sloghuman"
|
||||
)
|
||||
|
||||
const (
|
||||
baseDir = "./codersdk"
|
||||
indent = " "
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := run()
|
||||
ctx := context.Background()
|
||||
log := slog.Make(sloghuman.Sink(os.Stderr))
|
||||
codeBlocks, err := GenerateFromDirectory(ctx, log, baseDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal(ctx, err.Error())
|
||||
}
|
||||
|
||||
// Just cat the output to a file to capture it
|
||||
_, _ = fmt.Println(codeBlocks.String())
|
||||
}
|
||||
|
||||
func run() error {
|
||||
var (
|
||||
astFiles []*ast.File
|
||||
enums = make(map[string]string)
|
||||
)
|
||||
fset := token.NewFileSet()
|
||||
entries, err := os.ReadDir(baseDir)
|
||||
// TypescriptTypes holds all the code blocks created.
|
||||
type TypescriptTypes struct {
|
||||
// Each entry is the type name, and it's typescript code block.
|
||||
Types map[string]string
|
||||
Enums map[string]string
|
||||
}
|
||||
|
||||
// String just combines all the codeblocks.
|
||||
func (t TypescriptTypes) String() string {
|
||||
var s strings.Builder
|
||||
_, _ = s.WriteString("// Code generated by 'make coder/scripts/apitypings/main.go'. DO NOT EDIT.\n\n")
|
||||
|
||||
sortedTypes := make([]string, 0, len(t.Types))
|
||||
sortedEnums := make([]string, 0, len(t.Enums))
|
||||
|
||||
for k := range t.Types {
|
||||
sortedTypes = append(sortedTypes, k)
|
||||
}
|
||||
for k := range t.Enums {
|
||||
sortedEnums = append(sortedEnums, k)
|
||||
}
|
||||
|
||||
sort.Strings(sortedTypes)
|
||||
sort.Strings(sortedEnums)
|
||||
|
||||
for _, k := range sortedTypes {
|
||||
v := t.Types[k]
|
||||
_, _ = s.WriteString(v)
|
||||
_, _ = s.WriteRune('\n')
|
||||
}
|
||||
|
||||
for _, k := range sortedEnums {
|
||||
v := t.Enums[k]
|
||||
_, _ = s.WriteString(v)
|
||||
_, _ = s.WriteRune('\n')
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
err := g.parsePackage(ctx, directory)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("reading dir %s: %w", baseDir, err)
|
||||
return nil, xerrors.Errorf("parse package %q: %w", directory, err)
|
||||
}
|
||||
|
||||
// loop each file in directory
|
||||
for _, entry := range entries {
|
||||
astFile, err := parser.ParseFile(fset, filepath.Join(baseDir, entry.Name()), nil, 0)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parsing file %s: %w", filepath.Join(baseDir, entry.Name()), err)
|
||||
}
|
||||
|
||||
astFiles = append(astFiles, astFile)
|
||||
codeBlocks, err := g.generateAll()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse package %q: %w", directory, err)
|
||||
}
|
||||
|
||||
// TypeSpec case for structs and type alias
|
||||
loopSpecs(astFiles, func(spec ast.Spec) {
|
||||
pos := fset.Position(spec.Pos())
|
||||
s, ok := spec.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
out, err := handleTypeSpec(s, pos, enums)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return codeBlocks, nil
|
||||
}
|
||||
|
||||
_, _ = fmt.Printf(out)
|
||||
})
|
||||
type Generator struct {
|
||||
// Package we are scanning.
|
||||
pkg *packages.Package
|
||||
log slog.Logger
|
||||
}
|
||||
|
||||
// ValueSpec case for loading type alias values into the enum map
|
||||
loopSpecs(astFiles, func(spec ast.Spec) {
|
||||
s, ok := spec.(*ast.ValueSpec)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
handleValueSpec(s, enums)
|
||||
})
|
||||
|
||||
// sort keys so output is always the same
|
||||
var keys []string
|
||||
for k := range enums {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// write each type alias declaration with possible values
|
||||
for _, k := range keys {
|
||||
_, _ = fmt.Printf("%s\n", enums[k])
|
||||
// parsePackage takes a list of patterns such as a directory, and parses them.
|
||||
func (g *Generator) parsePackage(ctx context.Context, patterns ...string) error {
|
||||
cfg := &packages.Config{
|
||||
// Just accept the fact we need these flags for what we want. Feel free to add
|
||||
// more, it'll just increase the time it takes to parse.
|
||||
Mode: packages.NeedTypes | packages.NeedName | packages.NeedTypesInfo |
|
||||
packages.NeedTypesSizes | packages.NeedSyntax,
|
||||
Tests: false,
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
pkgs, err := packages.Load(cfg, patterns...)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("load package: %w", err)
|
||||
}
|
||||
|
||||
// Only support 1 package for now. We can expand it if we need later, we
|
||||
// just need to hook up multiple packages in the generator.
|
||||
if len(pkgs) != 1 {
|
||||
return xerrors.Errorf("expected 1 package, found %d", len(pkgs))
|
||||
}
|
||||
|
||||
g.pkg = pkgs[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func loopSpecs(astFiles []*ast.File, fn func(spec ast.Spec)) {
|
||||
for _, astFile := range astFiles {
|
||||
// loop each declaration in file
|
||||
for _, node := range astFile.Decls {
|
||||
genDecl, ok := node.(*ast.GenDecl)
|
||||
// generateAll will generate for all types found in the pkg
|
||||
func (g *Generator) generateAll() (*TypescriptTypes, error) {
|
||||
structs := make(map[string]string)
|
||||
enums := make(map[string]types.Object)
|
||||
enumConsts := make(map[string][]*types.Const)
|
||||
|
||||
// Look for comments that indicate to ignore a type for typescript generation.
|
||||
ignoredTypes := make(map[string]struct{})
|
||||
ignoreRegex := regexp.MustCompile("@typescript-ignore[:]?(?P<ignored_types>.*)")
|
||||
for _, file := range g.pkg.Syntax {
|
||||
for _, comment := range file.Comments {
|
||||
for _, line := range comment.List {
|
||||
text := line.Text
|
||||
matches := ignoreRegex.FindStringSubmatch(text)
|
||||
ignored := ignoreRegex.SubexpIndex("ignored_types")
|
||||
if len(matches) >= ignored && matches[ignored] != "" {
|
||||
arr := strings.Split(matches[ignored], ",")
|
||||
for _, s := range arr {
|
||||
ignoredTypes[strings.TrimSpace(s)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range g.pkg.Types.Scope().Names() {
|
||||
obj := g.pkg.Types.Scope().Lookup(n)
|
||||
if obj == nil || obj.Type() == nil {
|
||||
// This would be weird, but it is if the package does not have the type def.
|
||||
continue
|
||||
}
|
||||
|
||||
// Exclude ignored types
|
||||
if _, ok := ignoredTypes[obj.Name()]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch obj := obj.(type) {
|
||||
// All named types are type declarations
|
||||
case *types.TypeName:
|
||||
named, ok := obj.Type().(*types.Named)
|
||||
if !ok {
|
||||
continue
|
||||
panic("all typename should be named types")
|
||||
}
|
||||
for _, spec := range genDecl.Specs {
|
||||
fn(spec)
|
||||
switch named.Underlying().(type) {
|
||||
case *types.Struct:
|
||||
// type <Name> struct
|
||||
// Structs are obvious.
|
||||
st, _ := obj.Type().Underlying().(*types.Struct)
|
||||
codeBlock, err := g.buildStruct(obj, st)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("generate %q: %w", obj.Name())
|
||||
}
|
||||
structs[obj.Name()] = codeBlock
|
||||
case *types.Basic:
|
||||
// type <Name> string
|
||||
// These are enums. Store to expand later.
|
||||
enums[obj.Name()] = obj
|
||||
}
|
||||
case *types.Var:
|
||||
// TODO: Are any enums var declarations? This is also codersdk.Me.
|
||||
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()
|
||||
enumConsts[name] = append(enumConsts[name], obj)
|
||||
}
|
||||
case *types.Func:
|
||||
// Noop
|
||||
default:
|
||||
fmt.Println(obj.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Write all enums
|
||||
enumCodeBlocks := make(map[string]string)
|
||||
for name, v := range enums {
|
||||
var values []string
|
||||
for _, elem := range enumConsts[name] {
|
||||
// TODO: If we have non string constants, we need to handle that
|
||||
// here.
|
||||
values = append(values, elem.Val().String())
|
||||
}
|
||||
sort.Strings(values)
|
||||
var s strings.Builder
|
||||
_, _ = s.WriteString(g.posLine(v))
|
||||
_, _ = s.WriteString(fmt.Sprintf("export type %s = %s\n",
|
||||
name, strings.Join(values, " | "),
|
||||
))
|
||||
|
||||
enumCodeBlocks[name] = s.String()
|
||||
}
|
||||
|
||||
return &TypescriptTypes{
|
||||
Types: structs,
|
||||
Enums: enumCodeBlocks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func handleTypeSpec(typeSpec *ast.TypeSpec, pos token.Position, enums map[string]string) (string, error) {
|
||||
jsonFields := 0
|
||||
s := fmt.Sprintf("// From %s.\n", pos.String())
|
||||
switch t := typeSpec.Type.(type) {
|
||||
// Struct declaration
|
||||
case *ast.StructType:
|
||||
s = fmt.Sprintf("%sexport interface %s {\n", s, typeSpec.Name.Name)
|
||||
for _, field := range t.Fields.List {
|
||||
i, optional, err := getIdent(field.Type)
|
||||
func (g *Generator) posLine(obj types.Object) string {
|
||||
file := g.pkg.Fset.File(obj.Pos())
|
||||
position := file.Position(obj.Pos())
|
||||
position.Filename = filepath.Join("codersdk", filepath.Base(position.Filename))
|
||||
return fmt.Sprintf("// From %s\n",
|
||||
position.String(),
|
||||
)
|
||||
}
|
||||
|
||||
// buildStruct just prints the typescript def for a type.
|
||||
func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, error) {
|
||||
var s strings.Builder
|
||||
_, _ = s.WriteString(g.posLine(obj))
|
||||
|
||||
_, _ = s.WriteString(fmt.Sprintf("export interface %s {\n", obj.Name()))
|
||||
// For each field in the struct, we print 1 line of the typescript interface
|
||||
for i := 0; i < st.NumFields(); i++ {
|
||||
field := st.Field(i)
|
||||
tag := reflect.StructTag(st.Tag(i))
|
||||
|
||||
// Use the json name if present
|
||||
jsonName := tag.Get("json")
|
||||
arr := strings.Split(jsonName, ",")
|
||||
jsonName = arr[0]
|
||||
if jsonName == "" {
|
||||
jsonName = field.Name()
|
||||
}
|
||||
|
||||
var tsType TypescriptType
|
||||
// If a `typescript:"string"` exists, we take this, and do not try to infer.
|
||||
typescriptTag := tag.Get("typescript")
|
||||
if typescriptTag == "-" {
|
||||
// Ignore this field
|
||||
continue
|
||||
} else if typescriptTag != "" {
|
||||
tsType.ValueType = typescriptTag
|
||||
} else {
|
||||
var err error
|
||||
tsType, err = g.typescriptType(field.Type())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldType := toTsType(i.Name)
|
||||
if fieldType == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldName := toJSONField(field)
|
||||
if fieldName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
s = fmt.Sprintf("%s readonly %s%s: %s\n", s, fieldName, optional, fieldType)
|
||||
jsonFields++
|
||||
}
|
||||
|
||||
// Do not print struct if it has no json fields
|
||||
if jsonFields == 0 {
|
||||
return "", xerrors.New("no json fields")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s}\n\n", s), nil
|
||||
// Type alias declaration
|
||||
case *ast.Ident:
|
||||
// save type declaration to map of types
|
||||
// later we come back and add union types to this declaration
|
||||
enums[typeSpec.Name.Name] = fmt.Sprintf("%sexport type %s = \n", s, typeSpec.Name.Name)
|
||||
return "", xerrors.New("enums are not printed at this stage")
|
||||
default:
|
||||
return "", xerrors.New("not struct or alias")
|
||||
}
|
||||
}
|
||||
|
||||
func handleValueSpec(valueSpec *ast.ValueSpec, enums map[string]string) {
|
||||
valueValue := ""
|
||||
i, ok := valueSpec.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
valueType := i.Name
|
||||
|
||||
for _, value := range valueSpec.Values {
|
||||
bl, ok := value.(*ast.BasicLit)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
valueValue = bl.Value
|
||||
break
|
||||
}
|
||||
|
||||
enums[valueType] = fmt.Sprintf("%s | %s\n", enums[valueType], valueValue)
|
||||
}
|
||||
|
||||
func getIdent(e ast.Expr) (*ast.Ident, string, error) {
|
||||
switch t := e.(type) {
|
||||
case *ast.Ident:
|
||||
return t, "", nil
|
||||
case *ast.StarExpr:
|
||||
i, ok := t.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return nil, "", xerrors.New("failed to cast star expr to indent")
|
||||
}
|
||||
return i, "?", nil
|
||||
default:
|
||||
return nil, "", xerrors.New("unknown expr type")
|
||||
}
|
||||
}
|
||||
|
||||
func toTsType(fieldType string) string {
|
||||
switch fieldType {
|
||||
case "bool":
|
||||
return "boolean"
|
||||
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64":
|
||||
return "number"
|
||||
}
|
||||
|
||||
return fieldType
|
||||
}
|
||||
|
||||
func toJSONField(field *ast.Field) string {
|
||||
if field.Tag != nil && field.Tag.Value != "" {
|
||||
fieldName := strings.Trim(field.Tag.Value, "`")
|
||||
for _, pair := range strings.Split(fieldName, " ") {
|
||||
if strings.Contains(pair, `json:`) {
|
||||
fieldName := strings.TrimPrefix(pair, `json:`)
|
||||
fieldName = strings.Trim(fieldName, `"`)
|
||||
fieldName = strings.Split(fieldName, ",")[0]
|
||||
|
||||
return fieldName
|
||||
return "", xerrors.Errorf("typescript type: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tsType.AboveTypeLine != "" {
|
||||
_, _ = s.WriteString(tsType.AboveTypeLine)
|
||||
_, _ = s.WriteRune('\n')
|
||||
}
|
||||
optional := ""
|
||||
if tsType.Optional {
|
||||
optional = "?"
|
||||
}
|
||||
_, _ = s.WriteString(fmt.Sprintf("%sreadonly %s%s: %s\n", indent, jsonName, optional, tsType.ValueType))
|
||||
}
|
||||
_, _ = s.WriteString("}\n")
|
||||
return s.String(), nil
|
||||
}
|
||||
|
||||
type TypescriptType struct {
|
||||
ValueType string
|
||||
// AboveTypeLine lets you put whatever text you want above the typescript
|
||||
// type line.
|
||||
AboveTypeLine string
|
||||
// Optional indicates the value is an optional field in typescript.
|
||||
Optional bool
|
||||
}
|
||||
|
||||
// typescriptType this function returns a typescript type for a given
|
||||
// golang type.
|
||||
// Eg:
|
||||
// []byte returns "string"
|
||||
func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
|
||||
switch ty := ty.(type) {
|
||||
case *types.Basic:
|
||||
bs := ty
|
||||
// All basic literals (string, bool, int, etc).
|
||||
switch {
|
||||
case bs.Info()&types.IsNumeric > 0:
|
||||
return TypescriptType{ValueType: "number"}, nil
|
||||
case bs.Info()&types.IsBoolean > 0:
|
||||
return TypescriptType{ValueType: "boolean"}, nil
|
||||
case bs.Kind() == types.Byte:
|
||||
// TODO: @emyrk What is a byte for typescript? A string? A uint8?
|
||||
return TypescriptType{ValueType: "number", AboveTypeLine: indentedComment("This is a byte in golang")}, nil
|
||||
default:
|
||||
return TypescriptType{ValueType: bs.Name()}, nil
|
||||
}
|
||||
case *types.Struct:
|
||||
// This handles anonymous structs. This should never happen really.
|
||||
// Such as:
|
||||
// type Name struct {
|
||||
// Embedded struct {
|
||||
// Field string `json:"field"`
|
||||
// }
|
||||
// }
|
||||
return TypescriptType{
|
||||
ValueType: "any",
|
||||
AboveTypeLine: fmt.Sprintf("%s\n%s",
|
||||
indentedComment("Embedded struct, please fix by naming it"),
|
||||
indentedComment("eslint-disable-next-line @typescript-eslint/no-explicit-any"),
|
||||
),
|
||||
}, nil
|
||||
case *types.Map:
|
||||
// map[string][string] -> Record<string, string>
|
||||
m := ty
|
||||
keyType, err := g.typescriptType(m.Key())
|
||||
if err != nil {
|
||||
return TypescriptType{}, xerrors.Errorf("map key: %w", err)
|
||||
}
|
||||
valueType, err := g.typescriptType(m.Elem())
|
||||
if err != nil {
|
||||
return TypescriptType{}, xerrors.Errorf("map key: %w", err)
|
||||
}
|
||||
|
||||
return TypescriptType{
|
||||
ValueType: fmt.Sprintf("Record<%s, %s>", keyType.ValueType, valueType.ValueType),
|
||||
}, nil
|
||||
case *types.Slice, *types.Array:
|
||||
// Slice/Arrays are pretty much the same.
|
||||
type hasElem interface {
|
||||
Elem() types.Type
|
||||
}
|
||||
|
||||
arr, _ := ty.(hasElem)
|
||||
switch {
|
||||
// When type checking here, just use the string. You can cast it
|
||||
// to a types.Basic and get the kind if you want too :shrug:
|
||||
case arr.Elem().String() == "byte":
|
||||
// All byte arrays are strings on the typescript.
|
||||
// Is this ok?
|
||||
return TypescriptType{ValueType: "string"}, nil
|
||||
default:
|
||||
// By default, just do an array of the underlying type.
|
||||
underlying, err := g.typescriptType(arr.Elem())
|
||||
if err != nil {
|
||||
return TypescriptType{}, xerrors.Errorf("array: %w", err)
|
||||
}
|
||||
return TypescriptType{ValueType: underlying.ValueType + "[]", AboveTypeLine: underlying.AboveTypeLine}, nil
|
||||
}
|
||||
case *types.Named:
|
||||
n := ty
|
||||
// First see if the type is defined elsewhere. If it is, we can just
|
||||
// put the name as it will be defined in the typescript codeblock
|
||||
// we generate.
|
||||
name := n.Obj().Name()
|
||||
if obj := g.pkg.Types.Scope().Lookup(name); obj != nil {
|
||||
// Sweet! Using other typescript types as fields. This could be an
|
||||
// enum or another struct
|
||||
return TypescriptType{ValueType: name}, nil
|
||||
}
|
||||
|
||||
// These are external named types that we handle uniquely.
|
||||
switch n.String() {
|
||||
case "net/url.URL":
|
||||
return TypescriptType{ValueType: "string"}, nil
|
||||
case "time.Time":
|
||||
// We really should come up with a standard for time.
|
||||
return TypescriptType{ValueType: "string"}, nil
|
||||
case "database/sql.NullTime":
|
||||
return TypescriptType{ValueType: "string", Optional: true}, nil
|
||||
case "github.com/google/uuid.NullUUID":
|
||||
return TypescriptType{ValueType: "string", Optional: true}, nil
|
||||
case "github.com/google/uuid.UUID":
|
||||
return TypescriptType{ValueType: "string"}, nil
|
||||
}
|
||||
|
||||
// If it's a struct, just use the name of the struct type
|
||||
if _, ok := n.Underlying().(*types.Struct); ok {
|
||||
return TypescriptType{ValueType: "any", AboveTypeLine: fmt.Sprintf("%s\n%s",
|
||||
indentedComment(fmt.Sprintf("Named type %q unknown, using \"any\"", n.String())),
|
||||
indentedComment("eslint-disable-next-line @typescript-eslint/no-explicit-any"),
|
||||
)}, nil
|
||||
}
|
||||
|
||||
// Defer to the underlying type.
|
||||
ts, err := g.typescriptType(ty.Underlying())
|
||||
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()))
|
||||
return ts, nil
|
||||
case *types.Pointer:
|
||||
// Dereference pointers.
|
||||
pt := ty
|
||||
resp, err := g.typescriptType(pt.Elem())
|
||||
if err != nil {
|
||||
return TypescriptType{}, xerrors.Errorf("pointer: %w", err)
|
||||
}
|
||||
resp.Optional = true
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return ""
|
||||
// These are all the other types we need to support.
|
||||
// time.Time, uuid, etc.
|
||||
return TypescriptType{}, xerrors.Errorf("unknown type: %s", ty.String())
|
||||
}
|
||||
|
||||
func indentedComment(comment string) string {
|
||||
return fmt.Sprintf("%s// %s", indent, comment)
|
||||
}
|
||||
|
|
|
@ -1,96 +1,36 @@
|
|||
// From codersdk/buildinfo.go:10:6.
|
||||
export interface BuildInfoResponse {
|
||||
readonly external_url: string
|
||||
readonly version: string
|
||||
// Code generated by 'make coder/scripts/apitypings/main.go'. DO NOT EDIT.
|
||||
|
||||
// From codersdk/workspaceagents.go:35:6
|
||||
export interface AWSInstanceIdentityToken {
|
||||
readonly signature: string
|
||||
readonly document: string
|
||||
}
|
||||
|
||||
// From codersdk/files.go:16:6.
|
||||
export interface UploadResponse {
|
||||
readonly hash: string
|
||||
}
|
||||
|
||||
// From codersdk/gitsshkey.go:14:6.
|
||||
export interface GitSSHKey {
|
||||
readonly public_key: string
|
||||
}
|
||||
|
||||
// From codersdk/gitsshkey.go:21:6.
|
||||
// From codersdk/gitsshkey.go:21:6
|
||||
export interface AgentGitSSHKey {
|
||||
readonly public_key: string
|
||||
readonly private_key: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:17:6.
|
||||
export interface Organization {
|
||||
readonly name: string
|
||||
// From codersdk/users.go:94:6
|
||||
export interface AuthMethods {
|
||||
readonly password: boolean
|
||||
readonly github: boolean
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:25:6.
|
||||
export interface CreateTemplateVersionRequest {
|
||||
readonly storage_source: string
|
||||
// From codersdk/workspaceagents.go:40:6
|
||||
export interface AzureInstanceIdentityToken {
|
||||
readonly signature: string
|
||||
readonly encoding: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:38:6.
|
||||
export interface CreateTemplateRequest {
|
||||
readonly name: string
|
||||
// From codersdk/buildinfo.go:10:6
|
||||
export interface BuildInfoResponse {
|
||||
readonly external_url: string
|
||||
readonly version: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:52:6.
|
||||
export interface CreateWorkspaceRequest {
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/parameters.go:26:6.
|
||||
export interface Parameter {
|
||||
readonly scope: ParameterScope
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/parameters.go:38:6.
|
||||
export interface CreateParameterRequest {
|
||||
readonly name: string
|
||||
readonly source_value: string
|
||||
}
|
||||
|
||||
// From codersdk/provisionerdaemons.go:46:6.
|
||||
export interface ProvisionerJob {
|
||||
readonly error: string
|
||||
readonly status: ProvisionerJobStatus
|
||||
}
|
||||
|
||||
// From codersdk/provisionerdaemons.go:56:6.
|
||||
export interface ProvisionerJobLog {
|
||||
readonly stage: string
|
||||
readonly output: string
|
||||
}
|
||||
|
||||
// From codersdk/templates.go:17:6.
|
||||
export interface Template {
|
||||
readonly name: string
|
||||
readonly workspace_owner_count: number
|
||||
}
|
||||
|
||||
// From codersdk/templateversions.go:17:6.
|
||||
export interface TemplateVersion {
|
||||
readonly name: string
|
||||
readonly job: ProvisionerJob
|
||||
}
|
||||
|
||||
// From codersdk/users.go:17:6.
|
||||
export interface UsersRequest {
|
||||
readonly search: string
|
||||
readonly limit: number
|
||||
readonly offset: number
|
||||
}
|
||||
|
||||
// From codersdk/users.go:39:6.
|
||||
export interface User {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
readonly status: UserStatus
|
||||
}
|
||||
|
||||
// From codersdk/users.go:48:6.
|
||||
// From codersdk/users.go:48:6
|
||||
export interface CreateFirstUserRequest {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
|
@ -98,100 +38,291 @@ export interface CreateFirstUserRequest {
|
|||
readonly organization: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:61:6.
|
||||
// From codersdk/users.go:56:6
|
||||
export interface CreateFirstUserResponse {
|
||||
readonly user_id: string
|
||||
readonly organization_id: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:89:6
|
||||
export interface CreateOrganizationRequest {
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/parameters.go:38:6
|
||||
export interface CreateParameterRequest {
|
||||
readonly name: string
|
||||
readonly source_value: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterSourceScheme")
|
||||
readonly source_scheme: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterDestinationScheme")
|
||||
readonly destination_scheme: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:38:6
|
||||
export interface CreateTemplateRequest {
|
||||
readonly name: string
|
||||
readonly template_version_id: string
|
||||
readonly parameter_values: CreateParameterRequest[]
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:25:6
|
||||
export interface CreateTemplateVersionRequest {
|
||||
readonly template_id: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ProvisionerStorageMethod")
|
||||
readonly storage_method: string
|
||||
readonly storage_source: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ProvisionerType")
|
||||
readonly provisioner: string
|
||||
readonly parameter_values: CreateParameterRequest[]
|
||||
}
|
||||
|
||||
// From codersdk/users.go:61:6
|
||||
export interface CreateUserRequest {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
readonly password: string
|
||||
readonly organization_id: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:68:6.
|
||||
export interface UpdateUserProfileRequest {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
// From codersdk/workspaces.go:33:6
|
||||
export interface CreateWorkspaceBuildRequest {
|
||||
readonly template_version_id: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition")
|
||||
readonly transition: string
|
||||
readonly dry_run: boolean
|
||||
}
|
||||
|
||||
// From codersdk/users.go:74:6.
|
||||
// From codersdk/organizations.go:52:6
|
||||
export interface CreateWorkspaceRequest {
|
||||
readonly template_id: string
|
||||
readonly name: string
|
||||
readonly parameter_values: CreateParameterRequest[]
|
||||
}
|
||||
|
||||
// From codersdk/users.go:85:6
|
||||
export interface GenerateAPIKeyResponse {
|
||||
readonly key: string
|
||||
}
|
||||
|
||||
// From codersdk/gitsshkey.go:14:6
|
||||
export interface GitSSHKey {
|
||||
readonly user_id: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly public_key: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:31:6
|
||||
export interface GoogleInstanceIdentityToken {
|
||||
readonly json_web_token: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:74:6
|
||||
export interface LoginWithPasswordRequest {
|
||||
readonly email: string
|
||||
readonly password: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:80:6.
|
||||
// From codersdk/users.go:80:6
|
||||
export interface LoginWithPasswordResponse {
|
||||
readonly session_token: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:85:6.
|
||||
export interface GenerateAPIKeyResponse {
|
||||
readonly key: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:89:6.
|
||||
export interface CreateOrganizationRequest {
|
||||
// From codersdk/organizations.go:17:6
|
||||
export interface Organization {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:94:6.
|
||||
export interface AuthMethods {
|
||||
readonly password: boolean
|
||||
readonly github: boolean
|
||||
// From codersdk/parameters.go:26:6
|
||||
export interface Parameter {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly scope: ParameterScope
|
||||
readonly scope_id: string
|
||||
readonly name: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterSourceScheme")
|
||||
readonly source_scheme: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterDestinationScheme")
|
||||
readonly destination_scheme: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:31:6.
|
||||
export interface GoogleInstanceIdentityToken {
|
||||
readonly json_web_token: string
|
||||
// From codersdk/provisionerdaemons.go:23:6
|
||||
export interface ProvisionerDaemon {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly updated_at?: string
|
||||
readonly organization_id?: string
|
||||
readonly name: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ProvisionerType")
|
||||
readonly provisioners: string[]
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:35:6.
|
||||
export interface AWSInstanceIdentityToken {
|
||||
readonly signature: string
|
||||
readonly document: string
|
||||
// From codersdk/provisionerdaemons.go:46:6
|
||||
export interface ProvisionerJob {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly started_at?: string
|
||||
readonly completed_at?: string
|
||||
readonly error: string
|
||||
readonly status: ProvisionerJobStatus
|
||||
readonly worker_id?: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:40:6.
|
||||
export interface AzureInstanceIdentityToken {
|
||||
readonly signature: string
|
||||
readonly encoding: string
|
||||
// From codersdk/provisionerdaemons.go:56:6
|
||||
export interface ProvisionerJobLog {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.LogSource")
|
||||
readonly log_source: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.LogLevel")
|
||||
readonly log_level: string
|
||||
readonly stage: string
|
||||
readonly output: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:47:6.
|
||||
export interface WorkspaceAgentAuthenticateResponse {
|
||||
readonly session_token: string
|
||||
// From codersdk/templates.go:17:6
|
||||
export interface Template {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly organization_id: string
|
||||
readonly name: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ProvisionerType")
|
||||
readonly provisioner: string
|
||||
readonly active_version_id: string
|
||||
readonly workspace_owner_count: number
|
||||
}
|
||||
|
||||
// From codersdk/workspacebuilds.go:17:6.
|
||||
export interface WorkspaceBuild {
|
||||
// From codersdk/templateversions.go:17:6
|
||||
export interface TemplateVersion {
|
||||
readonly id: string
|
||||
readonly template_id?: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly name: string
|
||||
readonly job: ProvisionerJob
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:23:6.
|
||||
export interface WorkspaceResource {
|
||||
readonly type: string
|
||||
readonly name: string
|
||||
// From codersdk/templateversions.go:30:6
|
||||
export interface TemplateVersionParameter {
|
||||
// Named type "github.com/coder/coder/coderd/database.ParameterValue" unknown, using "any"
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
readonly ParameterValue: any
|
||||
readonly schema_id: string
|
||||
readonly default_source_value: boolean
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:33:6.
|
||||
// From codersdk/templateversions.go:27:6
|
||||
export interface TemplateVersionParameterSchema {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly job_id: string
|
||||
readonly name: string
|
||||
readonly description: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterSourceScheme")
|
||||
readonly default_source_scheme: string
|
||||
readonly default_source_value: string
|
||||
readonly allow_override_source: boolean
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterDestinationScheme")
|
||||
readonly default_destination_scheme: string
|
||||
readonly allow_override_destination: boolean
|
||||
readonly default_refresh: string
|
||||
readonly redisplay_value: boolean
|
||||
readonly validation_error: string
|
||||
readonly validation_condition: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterTypeSystem")
|
||||
readonly validation_type_system: string
|
||||
readonly validation_value_type: string
|
||||
}
|
||||
|
||||
// From codersdk/templates.go:28:6
|
||||
export interface UpdateActiveTemplateVersion {
|
||||
readonly id: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:68:6
|
||||
export interface UpdateUserProfileRequest {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:94:6
|
||||
export interface UpdateWorkspaceAutostartRequest {
|
||||
readonly schedule: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:114:6
|
||||
export interface UpdateWorkspaceAutostopRequest {
|
||||
readonly schedule: string
|
||||
}
|
||||
|
||||
// From codersdk/files.go:16:6
|
||||
export interface UploadResponse {
|
||||
readonly hash: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:39:6
|
||||
export interface User {
|
||||
readonly id: string
|
||||
readonly email: string
|
||||
readonly created_at: string
|
||||
readonly username: string
|
||||
readonly status: UserStatus
|
||||
readonly organization_ids: string[]
|
||||
}
|
||||
|
||||
// From codersdk/users.go:17:6
|
||||
export interface UsersRequest {
|
||||
readonly after_user: string
|
||||
readonly search: string
|
||||
readonly limit: number
|
||||
readonly offset: number
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:18:6
|
||||
export interface Workspace {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly owner_id: string
|
||||
readonly template_id: string
|
||||
readonly template_name: string
|
||||
readonly latest_build: WorkspaceBuild
|
||||
readonly outdated: boolean
|
||||
readonly name: string
|
||||
readonly autostart_schedule: string
|
||||
readonly autostop_schedule: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:33:6
|
||||
export interface WorkspaceAgent {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly first_connected_at?: string
|
||||
readonly last_connected_at?: string
|
||||
readonly disconnected_at?: string
|
||||
readonly status: WorkspaceAgentStatus
|
||||
readonly name: string
|
||||
readonly resource_id: string
|
||||
readonly instance_id: string
|
||||
readonly architecture: string
|
||||
readonly environment_variables: Record<string, string>
|
||||
readonly operating_system: string
|
||||
readonly startup_script: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:50:6.
|
||||
export interface WorkspaceAgentResourceMetadata {
|
||||
readonly memory_total: number
|
||||
readonly disk_total: number
|
||||
readonly cpu_cores: number
|
||||
readonly cpu_model: string
|
||||
readonly cpu_mhz: number
|
||||
// From codersdk/workspaceagents.go:47:6
|
||||
export interface WorkspaceAgentAuthenticateResponse {
|
||||
readonly session_token: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:58:6.
|
||||
// From codersdk/workspaceresources.go:58:6
|
||||
export interface WorkspaceAgentInstanceMetadata {
|
||||
readonly jail_orchestrator: string
|
||||
readonly operating_system: string
|
||||
|
@ -204,39 +335,51 @@ export interface WorkspaceAgentInstanceMetadata {
|
|||
readonly vnc: boolean
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:18:6.
|
||||
export interface Workspace {
|
||||
readonly template_name: string
|
||||
readonly latest_build: WorkspaceBuild
|
||||
readonly outdated: boolean
|
||||
// From codersdk/workspaceresources.go:50:6
|
||||
export interface WorkspaceAgentResourceMetadata {
|
||||
readonly memory_total: number
|
||||
readonly disk_total: number
|
||||
readonly cpu_cores: number
|
||||
readonly cpu_model: string
|
||||
readonly cpu_mhz: number
|
||||
}
|
||||
|
||||
// From codersdk/workspacebuilds.go:17:6
|
||||
export interface WorkspaceBuild {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly workspace_id: string
|
||||
readonly template_version_id: string
|
||||
readonly before_id: string
|
||||
readonly after_id: string
|
||||
readonly name: string
|
||||
readonly autostart_schedule: string
|
||||
readonly autostop_schedule: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition")
|
||||
readonly transition: string
|
||||
readonly initiator_id: string
|
||||
readonly job: ProvisionerJob
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:33:6.
|
||||
export interface CreateWorkspaceBuildRequest {
|
||||
readonly dry_run: boolean
|
||||
// From codersdk/workspaceresources.go:23:6
|
||||
export interface WorkspaceResource {
|
||||
readonly id: string
|
||||
readonly created_at: string
|
||||
readonly job_id: string
|
||||
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition")
|
||||
readonly workspace_transition: string
|
||||
readonly type: string
|
||||
readonly name: string
|
||||
readonly agents: WorkspaceAgent[]
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:94:6.
|
||||
export interface UpdateWorkspaceAutostartRequest {
|
||||
readonly schedule: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:114:6.
|
||||
export interface UpdateWorkspaceAutostopRequest {
|
||||
readonly schedule: string
|
||||
}
|
||||
|
||||
// From codersdk/parameters.go:16:6.
|
||||
// From codersdk/parameters.go:16:6
|
||||
export type ParameterScope = "organization" | "template" | "user" | "workspace"
|
||||
|
||||
// From codersdk/provisionerdaemons.go:26:6.
|
||||
export type ProvisionerJobStatus = "pending" | "running" | "succeeded" | "canceling" | "canceled" | "failed"
|
||||
// From codersdk/provisionerdaemons.go:26:6
|
||||
export type ProvisionerJobStatus = "canceled" | "canceling" | "failed" | "pending" | "running" | "succeeded"
|
||||
|
||||
// From codersdk/users.go:31:6.
|
||||
// From codersdk/users.go:31:6
|
||||
export type UserStatus = "active" | "suspended"
|
||||
|
||||
// From codersdk/workspaceresources.go:15:6.
|
||||
export type WorkspaceAgentStatus = "connecting" | "connected" | "disconnected"
|
||||
// From codersdk/workspaceresources.go:15:6
|
||||
export type WorkspaceAgentStatus = "connected" | "connecting" | "disconnected"
|
||||
|
|
Loading…
Reference in New Issue