Skip to content

Commit

Permalink
trpc-cli (updated 22:55)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmkal committed Sep 5, 2024
1 parent a91fb26 commit 238b78f
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 208 deletions.
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
"eslint": "^8.57.0",
"eslint-plugin-mmkal": "0.7.0",
"fast-glob": "^3.3.2",
"tsx": "^4.7.1"
"tsx": "^4.19.0"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"process": "^0.11.10",
"semver": "^7.6.0",
"sort-package-json": "^2.8.0",
"tsx": "^4.7.1",
"tsx": "^4.19.0",
"turbo": "^1.12.3",
"type-fest": "^4.10.3",
"typescript": ">=3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"shadcn-ui": "0.8.0",
"strip-indent": "^4.0.0",
"tailwindcss": "^3.4.3",
"tsx": "^4.7.1",
"tsx": "^4.19.0",
"typescript": "^5.2.2",
"typescript-eslint": "^7.1.0",
"vite": "^5.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/migra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"quicktype-core": "^23.0.81",
"sql-formatter": "^15.2.0",
"tsup": "^8.0.1",
"tsx": "^4.7.1",
"tsx": "^4.19.0",
"vitest": "^1.2.2"
}
}
2 changes: 1 addition & 1 deletion packages/migrator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"sql-formatter": "^15.2.0",
"ts-node": "^10.9.2",
"tsup": "^8.0.1",
"tsx": "^4.7.1",
"tsx": "^4.19.0",
"vitest": "^1.2.2"
},
"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/typegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"@pgkit/client": "workspace:^",
"@rebundled/execa": "8.0.2-next.0",
"@rebundled/p-memoize": "7.1.2-next.4",
"@rushstack/ts-command-line": "^4.11.0",
"chokidar": "^3.5.3",
"execa": "^5.1.1",
"find-up": "^5.0.0",
Expand All @@ -43,6 +42,7 @@
"neverthrow": "^7.1.0",
"pgsql-ast-parser": "^11.1.0",
"pluralize": "^8.0.0",
"trpc-cli": "https://pkg.pr.new/mmkal/trpc-cli@e57287f",
"zod": "^3.23.8"
},
"devDependencies": {
Expand All @@ -58,6 +58,7 @@
"js-yaml": "^4.1.0",
"strip-ansi": "^7.1.0",
"ts-morph": "^21.0.1",
"tsx": "^4.19.0",
"vitest": "^1.2.2"
}
}
164 changes: 4 additions & 160 deletions packages/typegen/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,162 +1,6 @@
import {Options, generate} from './index'
import * as cli from '@rushstack/ts-command-line'
import * as lodash from 'lodash'
import * as path from 'path'
import {createCli} from 'trpc-cli'
import {router} from './router'

import * as defaults from './defaults'
import {tryOrDefault} from './util'
const cli = createCli({router})

export class TypegenCLI extends cli.CommandLineParser {
constructor() {
super({
toolFilename: 'pg-typegen',
toolDescription: `CLI for https://npmjs.com/package/@pgkit/typegen.`,
})

this.addAction(new GenerateAction())
}

onDefineParameters() {}
}

export class GenerateAction extends cli.CommandLineAction {
private _params!: ReturnType<typeof GenerateAction._defineParameters>

constructor() {
super({
actionName: 'generate',
summary: `Scans source files for SQL queries and generates TypeScript interfaces for them.`,
documentation: `
Generates a directory containing with a 'sql' tag wrapper based on found queries found in source files.
By default, searches 'src' for source files.
`,
})
}

private static _defineParameters(action: cli.CommandLineAction) {
return {
config: action.defineStringParameter({
parameterLongName: '--config',
argumentName: 'PATH',
description: `
Path to a module containing parameters to be passed to 'generate'. If specified, it will be required
and the export will be used as parameters. If not specified, defaults will be used.
Note: other CLI arguments will override values set in this module
`,
}),
rootDir: action.defineStringParameter({
parameterLongName: '--root-dir',
argumentName: 'PATH',
description: `Path to the source directory containing SQL queries. Defaults to "src" if no value is provided`,
}),
connectionString: action.defineStringParameter({
parameterLongName: '--connection-string',
argumentName: 'URL',
description: `URL for connecting to postgres. Defaults to 'postgresql://postgres:postgres@localhost:5432/postgres'`,
}),
psql: action.defineStringParameter({
parameterLongName: '--psql',
argumentName: 'COMMAND',
description: `
psql command used to query postgres via CLI client. e.g.
'psql -h localhost -U postgres postgres' if running postgres locally, or
'docker-compose exec -T postgres psql -h localhost -U postgres postgres' if running with docker-compose.
You can test this by running "<<your_psql_command>> -c 'select 1 as a, 2 as b'". Note that this command will
be executed dynamically, so avoid using any escape characters in here.
`,
}),
defaultType: action.defineStringParameter({
parameterLongName: '--default-type',
argumentName: 'TYPESCRIPT',
description: `
TypeScript fallback type for when no type is found. Most simple types (text, int etc.)
are mapped to their TypeScript equivalent automatically. This should usually be 'unknown',
or 'any' if you like to live dangerously.
`,
}),
include: action.defineStringListParameter({
parameterLongName: '--include',
argumentName: 'PATTERN',
description: `
Glob pattern of files to search for SQL queries in.
By default searches for all .ts and .sql files: '**/*.{ts,sql}'
This option is repeatable to include multiple patterns.
`,
}),
exclude: action.defineStringListParameter({
parameterLongName: '--exclude',
argumentName: 'PATTERN',
description: `
Glob pattern for files to be excluded from processing.
By default excludes '**/node_modules/**'.
This option is repeatable to exlude multiple patterns.
`,
}),
since: action.defineStringParameter({
parameterLongName: '--since',
argumentName: 'REF',
description: `
Limit affected files to those which have been changed since the given git ref.
Use "--since HEAD" for files changed since the last commit, "--since main for files changed in a branch, etc.
This option has no effect in watch mode.
`,
}),
migrate: action.defineChoiceParameter({
parameterLongName: '--migrate',
alternatives: ['<=0.8.0'],
description: `Before generating types, attempt to migrate a codebase which has used a prior version of this tool`,
}),
skipCheckClean: action.defineFlagParameter({
parameterLongName: '--skip-check-clean',
description: `If enabled, the tool will not check the git status to ensure changes are checked in.`,
}),
watch: action.defineFlagParameter({
parameterLongName: '--watch',
description: `Run the type checker in watch mode. Files will be run through the code generator when changed or added.`,
}),
lazy: action.defineFlagParameter({
parameterLongName: '--lazy',
description: `Skip initial processing of input files. Only useful with '--watch'.`,
}),
}
}

onDefineParameters(): void {
this._params = GenerateAction._defineParameters(this)
}

async onExecute() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const optionsModule = this._params.config.value
? await import(path.resolve(process.cwd(), this._params.config.value))
: tryOrDefault(async () => import(path.resolve(process.cwd(), defaults.typegenConfigFile)), null)

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const options: {} = optionsModule?.default || optionsModule

const run = await generate(
lodash.merge({}, options, {
rootDir: this._params.rootDir.value,
connectionString: this._params.connectionString.value,
psqlCommand: this._params.psql.value,
defaultType: this._params.defaultType.value,
include: this._params.include.values.length > 0 ? this._params.include.values : undefined,
exclude: this._params.exclude.values.length > 0 ? this._params.exclude.values : undefined,
since: this._params.since.value,
migrate: this._params.migrate.value as Options['migrate'],
checkClean: this._params.skipCheckClean.value ? ['none'] : undefined,
lazy: this._params.lazy.value,
} as Partial<Options>),
)

if (this._params.watch.value) {
run.watch()
}
}
}

/* istanbul ignore if */
if (require.main === module) {
const program = new TypegenCLI()
void program.execute()
}
void cli.run()
13 changes: 8 additions & 5 deletions packages/typegen/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ export const defaultTypeScriptType = 'unknown'

export const defaultCheckClean: Options['checkClean'] = ['before-migrate', 'after']

export const defaultIncludePatterns = ['**/*.{ts,sql}']

export const defaultExcludePatterns = ['**/node_modules/**']

const getWithWarning = <T>(logger: Options['logger'], message: string, value: T) => {
logger.warn(message)
return value
}

export const getParams = (partial: Partial<Options>): Options => {
export const resolveOptions = (partial: Partial<Options>): Options => {
const {
logger = console,
connectionString = getWithWarning(
Expand All @@ -37,8 +41,8 @@ export const getParams = (partial: Partial<Options>): Options => {
psqlCommand = defaultPsqlCommand,
pgTypeToTypeScript: gdescToTypeScript = () => undefined,
rootDir = defaultRootDir,
include = ['**/*.{ts,sql}'],
exclude = ['**/node_modules/**'],
include = defaultIncludePatterns,
exclude = defaultExcludePatterns,
since = undefined,
defaultType = defaultTypeScriptType,
extractQueries = defaultExtractQueries,
Expand All @@ -60,8 +64,7 @@ export const getParams = (partial: Partial<Options>): Options => {
`The 'glob' option is deprecated. Instead please use 'include', 'exclude' or 'since' respectively.`,
)

// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
assert.strictEqual(Object.keys(rest).length, 0, `Unexpected configuration keys: ${Object.keys(rest)}`)
assert.strictEqual(Object.keys(rest).length, 0, `Unexpected configuration keys: ${Object.keys(rest).join(', ')}`)

assert.ok(!connectionString.includes(' \'"'), `Connection URI should not contain spaces or quotes`)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {changedFiles, checkClean, containsIgnoreComment, globList, promiseDotOne
export type {Options} from './types'

export const generate = async (inputOptions: Partial<Options>) => {
const options = defaults.getParams(inputOptions)
const options = defaults.resolveOptions(inputOptions)
const logger = options.logger

const pool = createClient(options.connectionString, options.poolConfig)
Expand Down
2 changes: 2 additions & 0 deletions packages/typegen/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './generate'
export * from './router'
29 changes: 14 additions & 15 deletions packages/typegen/src/query/parse.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as assert from 'assert'

import {match} from 'io-ts-extra'
import * as lodash from 'lodash'
import * as neverthrow from 'neverthrow'
import * as pgsqlAST from 'pgsql-ast-parser'
Expand Down Expand Up @@ -175,13 +173,11 @@ export const sqlTablesAndColumns = (sql: string): {tables?: string[]; columns?:
if (ast.type === 'select') {
return {
tables: ast.from
?.map(f =>
match(f)
.case({alias: {name: String}}, f => f.alias.name)
.case({type: 'table'} as const, t => t.name.name)
.default(() => '') // filtered out below
.get(),
)
?.map(f => {
if ('alias' in f && typeof f.alias === 'object') return f.alias.name
if (f.type === 'table') return f.name.name
return ''
})
.filter(Boolean),
columns: lodash
.chain(ast.columns)
Expand All @@ -195,12 +191,15 @@ export const sqlTablesAndColumns = (sql: string): {tables?: string[]; columns?:
}

const expressionName = (ex: pgsqlAST.Expr): string | undefined => {
return match(ex)
.case({type: 'ref' as const}, e => e.name)
.case({type: 'call', function: {name: String}} as const, e => e.function.name)
.case({type: 'cast'} as const, e => expressionName(e.operand))
.default(() => undefined)
.get()
if (ex.type === 'ref') {
return ex.name
} else if (ex.type === 'call' && ex.function.name) {
return ex.function.name
} else if (ex.type === 'cast') {
return expressionName(ex.operand)
}

return undefined
}

export interface AliasMapping {
Expand Down
Loading

0 comments on commit 238b78f

Please sign in to comment.