Skip to content

Instantly share code, notes, and snippets.

@steelbrain
Created June 22, 2023 14:22
Show Gist options
  • Save steelbrain/a0bc2db31d99f84e60622fde8bb58f04 to your computer and use it in GitHub Desktop.
Save steelbrain/a0bc2db31d99f84e60622fde8bb58f04 to your computer and use it in GitHub Desktop.
Generate static types from a Typescript type for use in sibling projects
import childProcess from 'child_process'
import fs from 'fs'
import path from 'path'
import ts from 'typescript'
import { fileURLToPath } from 'url'
const PATH_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const PATH_TYPES_SOURCE = path.join(PATH_ROOT, 'src', 'types.ts')
const PATH_TYPES_OUTPUT = path.join(PATH_ROOT, 'src', 'types.output.d.ts')
let output = null
// ======= Helper function
// Source: https://github.com/typescript-eslint/typescript-eslint/blob/c09b1c0252cbbf58e94ca5d671b6e05f29511144/packages/typescript-estree/src/create-program/useProvidedPrograms.ts#L54
function createProgramFromConfigFile(configFile) {
const parsed = ts.getParsedCommandLineOfConfigFile(
configFile,
{},
{
onUnRecoverableConfigFileDiagnostic: _ => {
throw new Error('Unable to parse TSConfig file')
},
getCurrentDirectory: () => path.dirname(configFile),
readDirectory: ts.sys.readDirectory,
readFile: file => fs.readFileSync(file, 'utf-8'),
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
},
)
if (parsed == null || parsed.errors.length) {
throw new Error('Unable to parse TSConfig file')
}
const host = ts.createCompilerHost(parsed.options, true)
return ts.createProgram(parsed.fileNames, parsed.options, host)
}
// ======= END: Helper functions
// Create a Program to represent your project.
const program = createProgramFromConfigFile(path.join(PATH_ROOT, 'tsconfig.json'), PATH_ROOT)
// Get the checker, we will use it to find more about classes.
const checker = program.getTypeChecker()
// Visitor function for TS AST
function visit(node) {
// Only consider type aliases
if (!ts.isTypeAliasDeclaration(node)) {
return
}
// Only consider the type alias named "LambdaPayload"
if (node.name.getText() !== 'LambdaPayload') {
return
}
// Use type checker to get symbol of the type.
const symbol = checker.getSymbolAtLocation(node.name)
if (symbol) {
const type = checker.getDeclaredTypeOfSymbol(symbol)
// How to get "getResolvedSignature" on "type?"
output = checker.typeToString(type)
}
}
program.getSourceFiles().forEach(sourceFile => {
if (sourceFile.fileName === PATH_TYPES_SOURCE) {
// Walk the tree to search for classes.
ts.forEachChild(sourceFile, visit)
}
})
if (output == null) {
console.error('Unable to find type "LambdaPayload" in the types file')
process.exit(1)
}
// Write the output to a file
fs.writeFileSync(PATH_TYPES_OUTPUT, `export type LambdaPayload = ${output}\n`)
console.log(`Successfully generated types 🎉 -- ${path.relative(PATH_ROOT, PATH_TYPES_OUTPUT)}`)
// Run ESLint fix on output file
childProcess.spawnSync('yarn', ['eslint', '--fix', PATH_TYPES_OUTPUT], { stdio: 'ignore', cwd: PATH_ROOT })
// Run prettier on output file
childProcess.spawnSync('yarn', ['prettier', '--write', PATH_TYPES_OUTPUT], { stdio: 'ignore', cwd: PATH_ROOT })
console.log(`Successfully formatted types 🎉`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment