mirror of https://github.com/coder/coder.git
feat: generate typescript types from codersdk structs (#1047)
This commit is contained in:
parent
1df943e010
commit
f46b4cf3da
|
@ -76,6 +76,21 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cache Node
|
||||
id: cache-node
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
.eslintcache
|
||||
key: js-${{ runner.os }}-test-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
js-${{ runner.os }}-
|
||||
|
||||
- name: Install node_modules
|
||||
run: ./scripts/yarn_install.sh
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
|
|
7
Makefile
7
Makefile
|
@ -19,6 +19,11 @@ coderd/database/generate: fmt/sql coderd/database/dump.sql $(wildcard coderd/dat
|
|||
coderd/database/generate.sh
|
||||
.PHONY: coderd/database/generate
|
||||
|
||||
apitypings/generate: site/src/api/types.ts
|
||||
go run scripts/apitypings/main.go > site/src/api/types-generated.ts
|
||||
cd site && yarn run format:types
|
||||
.PHONY: apitypings/generate
|
||||
|
||||
fmt/prettier:
|
||||
@echo "--- prettier"
|
||||
# Avoid writing files in CI to reduce file write activity
|
||||
|
@ -48,7 +53,7 @@ fmt/terraform: $(wildcard *.tf)
|
|||
fmt: fmt/prettier fmt/sql fmt/terraform
|
||||
.PHONY: fmt
|
||||
|
||||
gen: coderd/database/generate peerbroker/proto provisionersdk/proto provisionerd/proto
|
||||
gen: coderd/database/generate peerbroker/proto provisionersdk/proto provisionerd/proto apitypings/generate
|
||||
.PHONY: gen
|
||||
|
||||
install: bin
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
baseDir = "./codersdk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
var (
|
||||
astFiles []*ast.File
|
||||
enums = make(map[string]string)
|
||||
)
|
||||
fset := token.NewFileSet()
|
||||
entries, err := os.ReadDir(baseDir)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("reading dir %s: %w", baseDir, 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
_, _ = fmt.Printf(out)
|
||||
})
|
||||
|
||||
// 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)
|
||||
})
|
||||
|
||||
// write each type alias declaration with possible values
|
||||
for _, v := range enums {
|
||||
_, _ = fmt.Printf("%s\n", v)
|
||||
}
|
||||
|
||||
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)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, spec := range genDecl.Specs {
|
||||
fn(spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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 "uint64", "uint32", "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 ""
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
"chromatic": "chromatic",
|
||||
"dev": "webpack-dev-server --config=webpack.dev.ts",
|
||||
"format:check": "prettier --check '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||
"format:types": "prettier --write 'src/api/types-generated.ts'",
|
||||
"format:write": "prettier --write '**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||
"lint": "jest --selectProjects lint",
|
||||
"lint:fix": "FIX=true yarn lint",
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
// From codersdk/buildinfo.go:10:6.
|
||||
export interface BuildInfoResponse {
|
||||
readonly external_url: string
|
||||
readonly version: 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.
|
||||
export interface AgentGitSSHKey {
|
||||
readonly private_key: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:17:6.
|
||||
export interface Organization {
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:25:6.
|
||||
export interface CreateTemplateVersionRequest {
|
||||
readonly storage_source: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go:38:6.
|
||||
export interface CreateTemplateRequest {
|
||||
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:37:6.
|
||||
export interface ProvisionerJob {
|
||||
readonly error: string
|
||||
readonly status: ProvisionerJobStatus
|
||||
}
|
||||
|
||||
// From codersdk/provisionerdaemons.go:47: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 User {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:25:6.
|
||||
export interface CreateFirstUserRequest {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
readonly password: string
|
||||
readonly organization: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:38:6.
|
||||
export interface CreateUserRequest {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
readonly password: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:45:6.
|
||||
export interface UpdateUserProfileRequest {
|
||||
readonly email: string
|
||||
readonly username: string
|
||||
readonly name?: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:52:6.
|
||||
export interface LoginWithPasswordRequest {
|
||||
readonly email: string
|
||||
readonly password: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:58:6.
|
||||
export interface LoginWithPasswordResponse {
|
||||
readonly session_token: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:63:6.
|
||||
export interface GenerateAPIKeyResponse {
|
||||
readonly key: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:67:6.
|
||||
export interface CreateOrganizationRequest {
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go:72:6.
|
||||
export interface CreateWorkspaceRequest {
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:31:6.
|
||||
export interface GoogleInstanceIdentityToken {
|
||||
readonly json_web_token: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:35:6.
|
||||
export interface AWSInstanceIdentityToken {
|
||||
readonly signature: string
|
||||
readonly document: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go:42:6.
|
||||
export interface WorkspaceAgentAuthenticateResponse {
|
||||
readonly session_token: string
|
||||
}
|
||||
|
||||
// From codersdk/workspacebuilds.go:17:6.
|
||||
export interface WorkspaceBuild {
|
||||
readonly name: string
|
||||
readonly job: ProvisionerJob
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:23:6.
|
||||
export interface WorkspaceResource {
|
||||
readonly type: string
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:33:6.
|
||||
export interface WorkspaceAgent {
|
||||
readonly status: WorkspaceAgentStatus
|
||||
readonly name: string
|
||||
readonly instance_id: string
|
||||
readonly architecture: 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/workspaceresources.go:58:6.
|
||||
export interface WorkspaceAgentInstanceMetadata {
|
||||
readonly jail_orchestrator: string
|
||||
readonly operating_system: string
|
||||
readonly platform: string
|
||||
readonly platform_family: string
|
||||
readonly kernel_version: string
|
||||
readonly kernel_architecture: string
|
||||
readonly cloud: string
|
||||
readonly jail: string
|
||||
readonly vnc: boolean
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:18:6.
|
||||
export interface Workspace {
|
||||
readonly template_name: string
|
||||
readonly latest_build: WorkspaceBuild
|
||||
readonly outdated: boolean
|
||||
readonly name: string
|
||||
readonly autostart_schedule: string
|
||||
readonly autostop_schedule: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:33:6.
|
||||
export interface CreateWorkspaceBuildRequest {
|
||||
readonly dry_run: boolean
|
||||
}
|
||||
|
||||
// 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/workspaceresources.go:15:6.
|
||||
export type WorkspaceAgentStatus = "connecting" | "connected" | "disconnected"
|
||||
|
||||
// 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"
|
Loading…
Reference in New Issue