feat: generate typescript types from codersdk structs (#1047)

This commit is contained in:
Garrett Delfosse 2022-04-18 19:45:22 -05:00 committed by GitHub
parent 1df943e010
commit f46b4cf3da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 442 additions and 1 deletions

View File

@ -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:

View File

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

200
scripts/apitypings/main.go Normal file
View File

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

View File

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

View File

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