prisma-generator-express 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -6
- package/src/bin.ts +2 -0
- package/src/constants.ts +1 -0
- package/src/generators/generateImportPrismaStatement.ts +78 -0
- package/src/generators/generateQueryBuilderHelper.ts +138 -0
- package/src/generators/generateRouter.ts +352 -0
- package/src/generators/generateUnifiedDocs.ts +168 -0
- package/src/generators/generateUnifiedHandler.ts +469 -0
- package/src/generators/generateUnifiedScalarUI.ts +1409 -0
- package/src/index.ts +100 -0
- package/src/utils/copyFiles.ts +134 -0
- package/src/utils/strings.ts +7 -0
- package/src/utils/writeFileSafely.ts +83 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generatorHandler,
|
|
3
|
+
GeneratorOptions,
|
|
4
|
+
DMMF,
|
|
5
|
+
} from '@prisma/generator-helper'
|
|
6
|
+
import { generateUnifiedHandler } from './generators/generateUnifiedHandler'
|
|
7
|
+
import { generateRouterFunction } from './generators/generateRouter'
|
|
8
|
+
import { generateScalarUIHandler } from './generators/generateUnifiedScalarUI'
|
|
9
|
+
import { generateUnifiedDocs } from './generators/generateUnifiedDocs'
|
|
10
|
+
import { generateQueryBuilderHelper } from './generators/generateQueryBuilderHelper'
|
|
11
|
+
import {
|
|
12
|
+
generateImportPrismaStatement,
|
|
13
|
+
getRelativeClientPath,
|
|
14
|
+
} from './generators/generateImportPrismaStatement'
|
|
15
|
+
import { writeFileSafely } from './utils/writeFileSafely'
|
|
16
|
+
import { copyFiles } from './utils/copyFiles'
|
|
17
|
+
import { GENERATOR_NAME } from './constants'
|
|
18
|
+
|
|
19
|
+
generatorHandler({
|
|
20
|
+
onManifest() {
|
|
21
|
+
return {
|
|
22
|
+
version: require('../package.json').version,
|
|
23
|
+
defaultOutput: '../generated',
|
|
24
|
+
prettyName: GENERATOR_NAME,
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async onGenerate(options: GeneratorOptions) {
|
|
29
|
+
const prismaImportStatement = generateImportPrismaStatement(options)
|
|
30
|
+
|
|
31
|
+
console.log('\n═══ Prisma Generator Express ═══')
|
|
32
|
+
|
|
33
|
+
await copyFiles(options)
|
|
34
|
+
|
|
35
|
+
const modelNames: string[] = []
|
|
36
|
+
|
|
37
|
+
for (const model of options.dmmf.datamodel.models) {
|
|
38
|
+
if (
|
|
39
|
+
model.documentation &&
|
|
40
|
+
model.documentation.includes('generator off')
|
|
41
|
+
) {
|
|
42
|
+
console.log(` Skipping: ${model.name} (generator off)`)
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
modelNames.push(model.name)
|
|
47
|
+
|
|
48
|
+
const relativeClientPath = getRelativeClientPath(options, model.name)
|
|
49
|
+
|
|
50
|
+
await writeFileSafely({
|
|
51
|
+
content: generateUnifiedHandler({
|
|
52
|
+
model: model as DMMF.Model,
|
|
53
|
+
prismaImportStatement,
|
|
54
|
+
}),
|
|
55
|
+
options,
|
|
56
|
+
model: model as DMMF.Model,
|
|
57
|
+
operation: 'Handlers',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await writeFileSafely({
|
|
61
|
+
content: generateRouterFunction({
|
|
62
|
+
model: model as DMMF.Model,
|
|
63
|
+
enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
|
|
64
|
+
relativeClientPath,
|
|
65
|
+
}),
|
|
66
|
+
options,
|
|
67
|
+
model: model as DMMF.Model,
|
|
68
|
+
operation: 'Router',
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await writeFileSafely({
|
|
72
|
+
content: generateScalarUIHandler({
|
|
73
|
+
model: model as DMMF.Model,
|
|
74
|
+
enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
|
|
75
|
+
}),
|
|
76
|
+
options,
|
|
77
|
+
model: model as DMMF.Model,
|
|
78
|
+
operation: 'Docs',
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await writeFileSafely({
|
|
83
|
+
content: generateUnifiedDocs(modelNames),
|
|
84
|
+
options,
|
|
85
|
+
operation: 'combinedDocs',
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await writeFileSafely({
|
|
89
|
+
content: generateQueryBuilderHelper(options),
|
|
90
|
+
options,
|
|
91
|
+
operation: 'queryBuilder',
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
console.log('\n═══ Generation Complete ═══')
|
|
95
|
+
console.log(`✓ ${modelNames.length} models`)
|
|
96
|
+
console.log(`✓ OpenAPI documentation generated`)
|
|
97
|
+
console.log(`✓ Query builder helper generated`)
|
|
98
|
+
console.log('')
|
|
99
|
+
},
|
|
100
|
+
})
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { GeneratorOptions } from '@prisma/generator-helper'
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
|
|
5
|
+
export interface CopyFilesConfig {
|
|
6
|
+
includeCacheUtils?: boolean
|
|
7
|
+
includeValidatorUtils?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function copyFiles(
|
|
11
|
+
options: GeneratorOptions,
|
|
12
|
+
config?: CopyFilesConfig,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const copyConfig = config || {}
|
|
15
|
+
const outputPath = options.generator.output?.value
|
|
16
|
+
if (!outputPath) return
|
|
17
|
+
|
|
18
|
+
const srcDir = path.join(__dirname, '..', '..', 'src', 'copy')
|
|
19
|
+
|
|
20
|
+
const baseFiles = [
|
|
21
|
+
'routeConfig.ts',
|
|
22
|
+
'parseQueryParams.ts',
|
|
23
|
+
'buildModelOpenApi.ts',
|
|
24
|
+
'operationDefinitions.ts',
|
|
25
|
+
'misc.ts',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
let allCopied = true
|
|
29
|
+
|
|
30
|
+
console.log(`Copying utility files to: ${outputPath}`)
|
|
31
|
+
|
|
32
|
+
for (const file of baseFiles) {
|
|
33
|
+
const ok = await copyFile(srcDir, outputPath, file, { required: true })
|
|
34
|
+
if (!ok) allCopied = false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (copyConfig.includeCacheUtils) {
|
|
38
|
+
const ok = await copyFile(srcDir, outputPath, 'cacheManager.ts', {
|
|
39
|
+
required: true,
|
|
40
|
+
})
|
|
41
|
+
if (!ok) allCopied = false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (copyConfig.includeValidatorUtils) {
|
|
45
|
+
const ok = await copyFile(srcDir, outputPath, 'inputValidator.ts', {
|
|
46
|
+
required: true,
|
|
47
|
+
})
|
|
48
|
+
if (!ok) allCopied = false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const clientDir = path.join(outputPath, 'client')
|
|
52
|
+
if (!fs.existsSync(clientDir)) {
|
|
53
|
+
fs.mkdirSync(clientDir, { recursive: true })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const clientSrcDir = path.join(__dirname, '..', '..', 'src', 'client')
|
|
57
|
+
const clientOk = await copyFile(
|
|
58
|
+
clientSrcDir,
|
|
59
|
+
clientDir,
|
|
60
|
+
'encodeQueryParams.ts',
|
|
61
|
+
{
|
|
62
|
+
required: true,
|
|
63
|
+
importRewrites: [{ from: '../copy/misc', to: '../misc' }],
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
if (!clientOk) allCopied = false
|
|
67
|
+
|
|
68
|
+
if (allCopied) {
|
|
69
|
+
console.log(`✓ Utility files copied successfully`)
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'One or more required utility files could not be copied. Generation aborted.',
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface CopyFileOptions {
|
|
78
|
+
required?: boolean
|
|
79
|
+
importRewrites?: Array<{ from: string; to: string }>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function copyFile(
|
|
83
|
+
srcDir: string,
|
|
84
|
+
destDir: string,
|
|
85
|
+
filename: string,
|
|
86
|
+
options?: CopyFileOptions,
|
|
87
|
+
): Promise<boolean> {
|
|
88
|
+
const srcPath = path.join(srcDir, filename)
|
|
89
|
+
const destPath = path.join(destDir, filename)
|
|
90
|
+
|
|
91
|
+
if (!fs.existsSync(srcPath)) {
|
|
92
|
+
if (options?.required) {
|
|
93
|
+
console.error(`✗ Required source file not found: ${srcPath}`)
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
console.warn(`⚠️ Source file not found: ${srcPath}`)
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const destDirPath = path.dirname(destPath)
|
|
101
|
+
if (!fs.existsSync(destDirPath)) {
|
|
102
|
+
fs.mkdirSync(destDirPath, { recursive: true })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let content = fs.readFileSync(srcPath, 'utf-8')
|
|
106
|
+
|
|
107
|
+
if (options?.importRewrites) {
|
|
108
|
+
for (const rewrite of options.importRewrites) {
|
|
109
|
+
content = content.replace(
|
|
110
|
+
new RegExp(`from ['"]${escapeRegex(rewrite.from)}['"]`, 'g'),
|
|
111
|
+
`from '${rewrite.to}'`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const header = `/**
|
|
117
|
+
* Auto-generated by prisma-generator-express
|
|
118
|
+
* DO NOT EDIT MANUALLY
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
`
|
|
122
|
+
|
|
123
|
+
if (!content.startsWith('/**')) {
|
|
124
|
+
content = header + content
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fs.writeFileSync(destPath, content)
|
|
128
|
+
console.log(` ✓ Copied: ${filename}`)
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function escapeRegex(str: string): string {
|
|
133
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
134
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { GeneratorOptions } from '@prisma/generator-helper'
|
|
2
|
+
import { DMMF } from '@prisma/generator-helper'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
import * as path from 'path'
|
|
5
|
+
import prettier from 'prettier'
|
|
6
|
+
|
|
7
|
+
interface WriteFileOptions {
|
|
8
|
+
content: string
|
|
9
|
+
options: GeneratorOptions
|
|
10
|
+
model?: DMMF.Model
|
|
11
|
+
operation: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let _prettierOptions: prettier.Options | null | undefined
|
|
15
|
+
|
|
16
|
+
async function getPrettierOptions(): Promise<prettier.Options | null> {
|
|
17
|
+
if (_prettierOptions !== undefined) return _prettierOptions
|
|
18
|
+
_prettierOptions = await prettier.resolveConfig(process.cwd())
|
|
19
|
+
return _prettierOptions
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function writeFileSafely({
|
|
23
|
+
content,
|
|
24
|
+
options,
|
|
25
|
+
model,
|
|
26
|
+
operation,
|
|
27
|
+
}: WriteFileOptions): Promise<void> {
|
|
28
|
+
const outputPath = options.generator.output?.value
|
|
29
|
+
if (!outputPath) {
|
|
30
|
+
throw new Error('Output path not defined')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let filePath: string
|
|
34
|
+
|
|
35
|
+
switch (operation) {
|
|
36
|
+
case 'cacheConfig':
|
|
37
|
+
filePath = path.join(outputPath, 'cacheConfig.ts')
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
case 'types/inputs':
|
|
41
|
+
filePath = path.join(outputPath, 'types', 'inputs.ts')
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
case 'combinedDocs':
|
|
45
|
+
filePath = path.join(outputPath, 'combinedDocs.ts')
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
case 'queryBuilder':
|
|
49
|
+
filePath = path.join(outputPath, 'queryBuilder.ts')
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
default:
|
|
53
|
+
if (!model) {
|
|
54
|
+
throw new Error(`Model required for operation: ${operation}`)
|
|
55
|
+
}
|
|
56
|
+
filePath = path.join(
|
|
57
|
+
outputPath,
|
|
58
|
+
model.name,
|
|
59
|
+
`${model.name}${operation}.ts`,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const dirPath = path.dirname(filePath)
|
|
64
|
+
if (!fs.existsSync(dirPath)) {
|
|
65
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let formattedContent: string
|
|
69
|
+
try {
|
|
70
|
+
const resolvedOptions = await getPrettierOptions()
|
|
71
|
+
formattedContent = await prettier.format(content, {
|
|
72
|
+
...resolvedOptions,
|
|
73
|
+
parser: 'typescript',
|
|
74
|
+
})
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.warn(
|
|
77
|
+
`⚠️ Prettier formatting failed for ${path.basename(filePath)}, writing unformatted`,
|
|
78
|
+
)
|
|
79
|
+
formattedContent = content
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(filePath, formattedContent)
|
|
83
|
+
}
|