prisma-generator-express 1.40.0 → 1.41.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/README.md +444 -12
- package/dist/generators/generateOperationCore.d.ts +3 -1
- package/dist/generators/generateOperationCore.js +266 -160
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +3 -1
- package/dist/generators/generateRouteConfigType.js +36 -31
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +4 -2
- package/dist/generators/generateRouter.js +130 -119
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +3 -1
- package/dist/generators/generateRouterFastify.js +12 -10
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +3 -1
- package/dist/generators/generateRouterHono.js +12 -9
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedDocs.d.ts +2 -1
- package/dist/generators/generateUnifiedDocs.js +6 -4
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/index.js +16 -21
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +2 -1
- package/dist/utils/copyFiles.js +39 -34
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +2 -0
- package/dist/utils/importExt.js +11 -0
- package/dist/utils/importExt.js.map +1 -0
- package/dist/utils/resolveImportStyle.d.ts +3 -0
- package/dist/utils/resolveImportStyle.js +211 -0
- package/dist/utils/resolveImportStyle.js.map +1 -0
- package/dist/utils/writeFileSafely.js +6 -9
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +1 -1
- package/src/copy/routeConfig.express.ts +39 -5
- package/src/copy/routeConfig.fastify.ts +8 -4
- package/src/copy/routeConfig.hono.ts +7 -3
- package/src/copy/routeConfig.ts +42 -2
- package/src/generators/generateOperationCore.ts +273 -169
- package/src/generators/generateRouteConfigType.ts +42 -35
- package/src/generators/generateRouter.ts +134 -121
- package/src/generators/generateRouterFastify.ts +14 -9
- package/src/generators/generateRouterHono.ts +14 -8
- package/src/generators/generateUnifiedDocs.ts +8 -3
- package/src/index.ts +25 -47
- package/src/utils/copyFiles.ts +45 -45
- package/src/utils/importExt.ts +7 -0
- package/src/utils/resolveImportStyle.ts +187 -0
- package/src/utils/writeFileSafely.ts +6 -22
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Target } from '../constants'
|
|
2
|
+
import { ImportStyle } from '../utils/resolveImportStyle'
|
|
3
|
+
import { importExt } from '../utils/importExt'
|
|
2
4
|
|
|
3
5
|
export function generateUnifiedDocs(
|
|
4
6
|
models: string[],
|
|
5
7
|
target: Target = 'express',
|
|
8
|
+
importStyle: ImportStyle = 'none',
|
|
6
9
|
): string {
|
|
10
|
+
const ext = importExt(importStyle)
|
|
11
|
+
|
|
7
12
|
const imports = models
|
|
8
|
-
.map((model) => `import { ${model}Docs } from './${model}/${model}Docs'`)
|
|
13
|
+
.map((model) => `import { ${model}Docs } from './${model}/${model}Docs${ext}'`)
|
|
9
14
|
.join('\n')
|
|
10
15
|
|
|
11
16
|
const handlersEntries = models
|
|
@@ -17,9 +22,9 @@ export function generateUnifiedDocs(
|
|
|
17
22
|
? `import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'`
|
|
18
23
|
: target === 'hono'
|
|
19
24
|
? `import type { Hono, Context } from 'hono'`
|
|
20
|
-
: `import { Request, Response } from 'express'`
|
|
25
|
+
: `import type { Request, Response } from 'express'`
|
|
21
26
|
|
|
22
|
-
const routeConfigImport = `import type { RouteConfig } from './routeConfig.target'`
|
|
27
|
+
const routeConfigImport = `import type { RouteConfig } from './routeConfig.target${ext}'`
|
|
23
28
|
|
|
24
29
|
const handlerType =
|
|
25
30
|
target === 'fastify'
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
generatorHandler,
|
|
3
|
-
GeneratorOptions,
|
|
4
|
-
DMMF,
|
|
5
|
-
} from '@prisma/generator-helper'
|
|
1
|
+
import { generatorHandler, GeneratorOptions, DMMF } from '@prisma/generator-helper'
|
|
6
2
|
import path from 'path'
|
|
7
3
|
import { generateUnifiedHandler } from './generators/generateUnifiedHandler'
|
|
8
4
|
import { generateFastifyHandler } from './generators/generateFastifyHandler'
|
|
@@ -13,26 +9,17 @@ import { generateHonoRouterFunction } from './generators/generateRouterHono'
|
|
|
13
9
|
import { generateScalarUIHandler } from './generators/generateUnifiedScalarUI'
|
|
14
10
|
import { generateUnifiedDocs } from './generators/generateUnifiedDocs'
|
|
15
11
|
import { generateQueryBuilderHelper } from './generators/generateQueryBuilderHelper'
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
generateModelCore,
|
|
19
|
-
} from './generators/generateOperationCore'
|
|
20
|
-
import {
|
|
21
|
-
getRelativeClientPath,
|
|
22
|
-
getGuardShapesImport,
|
|
23
|
-
} from './generators/generateImportPrismaStatement'
|
|
12
|
+
import { generateOperationRuntime, generateModelCore } from './generators/generateOperationCore'
|
|
13
|
+
import { getRelativeClientPath, getGuardShapesImport } from './generators/generateImportPrismaStatement'
|
|
24
14
|
import { writeFileSafely } from './utils/writeFileSafely'
|
|
25
15
|
import { copyFiles } from './utils/copyFiles'
|
|
16
|
+
import { resolveImportStyle } from './utils/resolveImportStyle'
|
|
26
17
|
import { GENERATOR_NAME, Target } from './constants'
|
|
27
18
|
|
|
28
19
|
function getTarget(options: GeneratorOptions): Target {
|
|
29
|
-
const raw = String(
|
|
30
|
-
(options.generator.config as Record<string, unknown>).target ?? 'express',
|
|
31
|
-
).toLowerCase()
|
|
20
|
+
const raw = String((options.generator.config as Record<string, unknown>).target ?? 'express').toLowerCase()
|
|
32
21
|
if (raw === 'express' || raw === 'fastify' || raw === 'hono') return raw
|
|
33
|
-
throw new Error(
|
|
34
|
-
`Invalid target "${raw}". Expected "express", "fastify", or "hono".`,
|
|
35
|
-
)
|
|
22
|
+
throw new Error(`Invalid target "${raw}". Expected "express", "fastify", or "hono".`)
|
|
36
23
|
}
|
|
37
24
|
|
|
38
25
|
generatorHandler({
|
|
@@ -46,7 +33,6 @@ generatorHandler({
|
|
|
46
33
|
|
|
47
34
|
async onGenerate(options: GeneratorOptions) {
|
|
48
35
|
const target = getTarget(options)
|
|
49
|
-
|
|
50
36
|
const hasExplicitOutput = !!options.generator.output?.fromEnvVar
|
|
51
37
|
|| (options.generator.config as Record<string, unknown>).output !== undefined
|
|
52
38
|
|
|
@@ -56,52 +42,46 @@ generatorHandler({
|
|
|
56
42
|
options.generator.output = { value: outputPath, fromEnvVar: null }
|
|
57
43
|
}
|
|
58
44
|
|
|
45
|
+
const importStyle = resolveImportStyle(options)
|
|
46
|
+
|
|
59
47
|
console.log(`\n═══ Prisma Generator (${target.toUpperCase()}) ═══`)
|
|
60
48
|
console.log(` Target: ${target}`)
|
|
61
49
|
console.log(` Output: ${options.generator.output?.value}`)
|
|
50
|
+
console.log(` Import style: ${importStyle}`)
|
|
62
51
|
|
|
63
|
-
await copyFiles(options, target)
|
|
52
|
+
await copyFiles(options, target, importStyle)
|
|
64
53
|
|
|
65
54
|
await writeFileSafely({
|
|
66
|
-
content: generateOperationRuntime(),
|
|
55
|
+
content: generateOperationRuntime(importStyle),
|
|
67
56
|
options,
|
|
68
57
|
operation: 'operationRuntime',
|
|
69
58
|
})
|
|
70
59
|
|
|
71
60
|
const modelNames: string[] = []
|
|
72
|
-
|
|
73
61
|
const generateHandler =
|
|
74
|
-
target === 'fastify'
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
? generateHonoHandler
|
|
78
|
-
: generateUnifiedHandler
|
|
62
|
+
target === 'fastify' ? generateFastifyHandler :
|
|
63
|
+
target === 'hono' ? generateHonoHandler :
|
|
64
|
+
generateUnifiedHandler
|
|
79
65
|
|
|
80
66
|
for (const model of options.dmmf.datamodel.models) {
|
|
81
|
-
if (
|
|
82
|
-
model.documentation &&
|
|
83
|
-
model.documentation.includes('generator off')
|
|
84
|
-
) {
|
|
67
|
+
if (model.documentation && model.documentation.includes('generator off')) {
|
|
85
68
|
console.log(` Skipping: ${model.name} (generator off)`)
|
|
86
69
|
continue
|
|
87
70
|
}
|
|
88
|
-
|
|
89
71
|
modelNames.push(model.name)
|
|
90
72
|
|
|
91
73
|
const relativeClientPath = getRelativeClientPath(options, model.name)
|
|
92
74
|
const guardShapesImport = getGuardShapesImport(options, model.name)
|
|
93
75
|
|
|
94
76
|
await writeFileSafely({
|
|
95
|
-
content: generateModelCore({ model: model as DMMF.Model }),
|
|
77
|
+
content: generateModelCore({ model: model as DMMF.Model, importStyle }),
|
|
96
78
|
options,
|
|
97
79
|
model: model as DMMF.Model,
|
|
98
80
|
operation: 'Core',
|
|
99
81
|
})
|
|
100
82
|
|
|
101
83
|
await writeFileSafely({
|
|
102
|
-
content: generateHandler({
|
|
103
|
-
model: model as DMMF.Model,
|
|
104
|
-
}),
|
|
84
|
+
content: generateHandler({ model: model as DMMF.Model, importStyle } as any),
|
|
105
85
|
options,
|
|
106
86
|
model: model as DMMF.Model,
|
|
107
87
|
operation: 'Handlers',
|
|
@@ -113,18 +93,21 @@ generatorHandler({
|
|
|
113
93
|
model: model as DMMF.Model,
|
|
114
94
|
enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
|
|
115
95
|
guardShapesImport,
|
|
116
|
-
|
|
96
|
+
importStyle,
|
|
97
|
+
} as any)
|
|
117
98
|
: target === 'hono'
|
|
118
99
|
? generateHonoRouterFunction({
|
|
119
100
|
model: model as DMMF.Model,
|
|
120
101
|
enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
|
|
121
102
|
guardShapesImport,
|
|
122
|
-
|
|
103
|
+
importStyle,
|
|
104
|
+
} as any)
|
|
123
105
|
: generateRouterFunction({
|
|
124
106
|
model: model as DMMF.Model,
|
|
125
107
|
enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
|
|
126
108
|
relativeClientPath,
|
|
127
109
|
guardShapesImport,
|
|
110
|
+
importStyle,
|
|
128
111
|
})
|
|
129
112
|
|
|
130
113
|
await writeFileSafely({
|
|
@@ -139,7 +122,8 @@ generatorHandler({
|
|
|
139
122
|
model: model as DMMF.Model,
|
|
140
123
|
enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
|
|
141
124
|
target,
|
|
142
|
-
|
|
125
|
+
importStyle,
|
|
126
|
+
} as any),
|
|
143
127
|
options,
|
|
144
128
|
model: model as DMMF.Model,
|
|
145
129
|
operation: 'Docs',
|
|
@@ -147,7 +131,7 @@ generatorHandler({
|
|
|
147
131
|
}
|
|
148
132
|
|
|
149
133
|
await writeFileSafely({
|
|
150
|
-
content: generateUnifiedDocs(modelNames, target),
|
|
134
|
+
content: generateUnifiedDocs(modelNames, target, importStyle),
|
|
151
135
|
options,
|
|
152
136
|
operation: 'combinedDocs',
|
|
153
137
|
})
|
|
@@ -160,12 +144,6 @@ generatorHandler({
|
|
|
160
144
|
|
|
161
145
|
console.log('\n═══ Generation Complete ═══')
|
|
162
146
|
console.log(`✓ ${modelNames.length} models (${target})`)
|
|
163
|
-
console.log(`✓ OpenAPI documentation generated`)
|
|
164
|
-
if (target === 'hono') {
|
|
165
|
-
console.log(`✓ Query builder helper generated (not auto-started for Hono)`)
|
|
166
|
-
} else {
|
|
167
|
-
console.log(`✓ Query builder helper generated`)
|
|
168
|
-
}
|
|
169
147
|
console.log('')
|
|
170
148
|
},
|
|
171
149
|
})
|
package/src/utils/copyFiles.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { GeneratorOptions } from '@prisma/generator-helper'
|
|
|
2
2
|
import * as fs from 'fs'
|
|
3
3
|
import * as path from 'path'
|
|
4
4
|
import { Target } from '../constants'
|
|
5
|
+
import { ImportStyle } from './resolveImportStyle'
|
|
6
|
+
import { importExt } from './importExt'
|
|
5
7
|
|
|
6
8
|
const SHARED_FILES = [
|
|
7
9
|
'parseQueryParams.ts',
|
|
@@ -21,17 +23,11 @@ interface CopyFileOptions {
|
|
|
21
23
|
function resolveTemplateDir(subpath: string): string {
|
|
22
24
|
const fromSrc = path.join(__dirname, '..', '..', 'src', subpath)
|
|
23
25
|
if (fs.existsSync(fromSrc)) return fromSrc
|
|
24
|
-
|
|
25
26
|
const fromDist = path.join(__dirname, '..', subpath)
|
|
26
27
|
if (fs.existsSync(fromDist)) return fromDist
|
|
27
|
-
|
|
28
28
|
throw new Error(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
` ${fromSrc}\n` +
|
|
32
|
-
` ${fromDist}\n` +
|
|
33
|
-
` __dirname: ${__dirname}\n` +
|
|
34
|
-
` Ensure template files are included in the published package.`,
|
|
29
|
+
'Template directory "' + subpath + '" not found.\n Searched:\n ' +
|
|
30
|
+
fromSrc + '\n ' + fromDist + '\n __dirname: ' + __dirname,
|
|
35
31
|
)
|
|
36
32
|
}
|
|
37
33
|
|
|
@@ -39,10 +35,26 @@ function escapeRegex(str: string): string {
|
|
|
39
35
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
40
36
|
}
|
|
41
37
|
|
|
38
|
+
const RELATIVE_IMPORT_RE =
|
|
39
|
+
/(\b(?:import|export)\b[^'"\n;]*?\bfrom\s+|\bimport\s+|\bimport\s*\(\s*)(['"])(\.{1,2}\/[^'"\n]+)\2/g
|
|
40
|
+
|
|
41
|
+
const SKIP_EXTENSION_RE = /\.(m?[jt]sx?|json|css|svg|png|jpe?g|gif|webp|wasm)$/i
|
|
42
|
+
|
|
43
|
+
function applyExtension(content: string, style: ImportStyle): string {
|
|
44
|
+
if (style === 'none') return content
|
|
45
|
+
const ext = importExt(style)
|
|
46
|
+
if (!ext) return content
|
|
47
|
+
return content.replace(RELATIVE_IMPORT_RE, (_match, prefix, quote, spec) => {
|
|
48
|
+
if (SKIP_EXTENSION_RE.test(spec)) return prefix + quote + spec + quote
|
|
49
|
+
return prefix + quote + spec + ext + quote
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
function copyFileSync(
|
|
43
54
|
srcDir: string,
|
|
44
55
|
destDir: string,
|
|
45
56
|
filename: string,
|
|
57
|
+
importStyle: ImportStyle,
|
|
46
58
|
options?: CopyFileOptions,
|
|
47
59
|
): string | null {
|
|
48
60
|
const srcPath = path.join(srcDir, filename)
|
|
@@ -50,38 +62,33 @@ function copyFileSync(
|
|
|
50
62
|
const destPath = path.join(destDir, destFilename)
|
|
51
63
|
|
|
52
64
|
if (!fs.existsSync(srcPath)) {
|
|
53
|
-
if (options?.required)
|
|
54
|
-
return `Required file not found: ${srcPath}`
|
|
55
|
-
}
|
|
65
|
+
if (options?.required) return 'Required file not found: ' + srcPath
|
|
56
66
|
return null
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
const destDirPath = path.dirname(destPath)
|
|
60
|
-
if (!fs.existsSync(destDirPath)) {
|
|
61
|
-
fs.mkdirSync(destDirPath, { recursive: true })
|
|
62
|
-
}
|
|
70
|
+
if (!fs.existsSync(destDirPath)) fs.mkdirSync(destDirPath, { recursive: true })
|
|
63
71
|
|
|
64
72
|
let content = fs.readFileSync(srcPath, 'utf-8')
|
|
65
73
|
|
|
66
74
|
if (options?.importRewrites) {
|
|
67
75
|
for (const rewrite of options.importRewrites) {
|
|
68
76
|
content = content.replace(
|
|
69
|
-
new RegExp(
|
|
70
|
-
|
|
77
|
+
new RegExp('from [\'"]' + escapeRegex(rewrite.from) + '[\'"]', 'g'),
|
|
78
|
+
"from '" + rewrite.to + "'",
|
|
71
79
|
)
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
const header =
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
|
|
83
|
+
const header =
|
|
84
|
+
'/**\n' +
|
|
85
|
+
' * Auto-generated by prisma-generator-express\n' +
|
|
86
|
+
' * DO NOT EDIT MANUALLY\n' +
|
|
87
|
+
' */\n\n'
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
if (!content.startsWith('/**')) content = header + content
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
content = header + content
|
|
84
|
-
}
|
|
91
|
+
content = applyExtension(content, importStyle)
|
|
85
92
|
|
|
86
93
|
fs.writeFileSync(destPath, content)
|
|
87
94
|
return null
|
|
@@ -90,6 +97,7 @@ function copyFileSync(
|
|
|
90
97
|
export async function copyFiles(
|
|
91
98
|
options: GeneratorOptions,
|
|
92
99
|
target: Target,
|
|
100
|
+
importStyle: ImportStyle,
|
|
93
101
|
): Promise<void> {
|
|
94
102
|
const outputPath = options.generator.output?.value
|
|
95
103
|
if (!outputPath) return
|
|
@@ -97,15 +105,15 @@ export async function copyFiles(
|
|
|
97
105
|
const copyBase = resolveTemplateDir('copy')
|
|
98
106
|
const errors: string[] = []
|
|
99
107
|
|
|
100
|
-
console.log(
|
|
108
|
+
console.log(' Copying utility files to: ' + outputPath + ' (target: ' + target + ')')
|
|
101
109
|
|
|
102
110
|
for (const file of SHARED_FILES) {
|
|
103
|
-
const err = copyFileSync(copyBase, outputPath, file, { required: true })
|
|
111
|
+
const err = copyFileSync(copyBase, outputPath, file, importStyle, { required: true })
|
|
104
112
|
if (err) errors.push(err)
|
|
105
113
|
}
|
|
106
114
|
|
|
107
|
-
const targetConfigFile =
|
|
108
|
-
const err = copyFileSync(copyBase, outputPath, targetConfigFile, {
|
|
115
|
+
const targetConfigFile = 'routeConfig.' + target + '.ts'
|
|
116
|
+
const err = copyFileSync(copyBase, outputPath, targetConfigFile, importStyle, {
|
|
109
117
|
required: true,
|
|
110
118
|
destFilename: 'routeConfig.target.ts',
|
|
111
119
|
importRewrites: [{ from: './routeConfig', to: './routeConfig' }],
|
|
@@ -113,30 +121,22 @@ export async function copyFiles(
|
|
|
113
121
|
if (err) errors.push(err)
|
|
114
122
|
|
|
115
123
|
const clientDir = path.join(outputPath, 'client')
|
|
116
|
-
if (!fs.existsSync(clientDir)) {
|
|
117
|
-
fs.mkdirSync(clientDir, { recursive: true })
|
|
118
|
-
}
|
|
124
|
+
if (!fs.existsSync(clientDir)) fs.mkdirSync(clientDir, { recursive: true })
|
|
119
125
|
|
|
120
126
|
const clientSrcDir = resolveTemplateDir('client')
|
|
121
|
-
const clientErr = copyFileSync(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{
|
|
126
|
-
required: true,
|
|
127
|
-
importRewrites: [{ from: '../copy/misc', to: '../misc' }],
|
|
128
|
-
},
|
|
129
|
-
)
|
|
127
|
+
const clientErr = copyFileSync(clientSrcDir, clientDir, 'encodeQueryParams.ts', importStyle, {
|
|
128
|
+
required: true,
|
|
129
|
+
importRewrites: [{ from: '../copy/misc', to: '../misc' }],
|
|
130
|
+
})
|
|
130
131
|
if (clientErr) errors.push(clientErr)
|
|
131
132
|
|
|
132
133
|
if (errors.length > 0) {
|
|
133
134
|
throw new Error(
|
|
134
|
-
|
|
135
|
-
errors.map((e) =>
|
|
136
|
-
|
|
137
|
-
`\n __dirname: ${__dirname}`,
|
|
135
|
+
'Failed to copy ' + errors.length + ' required file(s):\n' +
|
|
136
|
+
errors.map((e) => ' - ' + e).join('\n') +
|
|
137
|
+
'\n copyBase: ' + copyBase + '\n __dirname: ' + __dirname,
|
|
138
138
|
)
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
console.log(
|
|
141
|
+
console.log(' ✓ Utility files copied successfully')
|
|
142
142
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import type { GeneratorOptions } from '@prisma/generator-helper'
|
|
4
|
+
|
|
5
|
+
export type ImportStyle = 'none' | 'js' | 'ts'
|
|
6
|
+
|
|
7
|
+
const VALID_OVERRIDES = new Set(['auto', 'none', 'js', 'ts'])
|
|
8
|
+
const NODE_MODULE_KEYWORDS = new Set(['node16', 'node18', 'nodenext'])
|
|
9
|
+
|
|
10
|
+
function stripJsonComments(input: string): string {
|
|
11
|
+
let out = ''
|
|
12
|
+
let i = 0
|
|
13
|
+
let inString = false
|
|
14
|
+
let stringChar = ''
|
|
15
|
+
while (i < input.length) {
|
|
16
|
+
const c = input[i]
|
|
17
|
+
const next = input[i + 1]
|
|
18
|
+
if (inString) {
|
|
19
|
+
out += c
|
|
20
|
+
if (c === '\\' && i + 1 < input.length) {
|
|
21
|
+
out += input[i + 1]
|
|
22
|
+
i += 2
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
if (c === stringChar) inString = false
|
|
26
|
+
i++
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
if (c === '"' || c === "'") {
|
|
30
|
+
inString = true
|
|
31
|
+
stringChar = c
|
|
32
|
+
out += c
|
|
33
|
+
i++
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
if (c === '/' && next === '/') {
|
|
37
|
+
while (i < input.length && input[i] !== '\n') i++
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if (c === '/' && next === '*') {
|
|
41
|
+
i += 2
|
|
42
|
+
while (i < input.length && !(input[i] === '*' && input[i + 1] === '/')) i++
|
|
43
|
+
i += 2
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
out += c
|
|
47
|
+
i++
|
|
48
|
+
}
|
|
49
|
+
return out.replace(/,\s*(?=[\]}])/g, '')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function findUpwards(startDir: string, filename: string): string | null {
|
|
53
|
+
let dir = path.resolve(startDir)
|
|
54
|
+
const root = path.parse(dir).root
|
|
55
|
+
let result: string | null = null
|
|
56
|
+
let stop = false
|
|
57
|
+
while (!stop) {
|
|
58
|
+
const candidate = path.join(dir, filename)
|
|
59
|
+
if (fs.existsSync(candidate)) {
|
|
60
|
+
result = candidate
|
|
61
|
+
stop = true
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
if (dir === root) {
|
|
65
|
+
stop = true
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
const parent = path.dirname(dir)
|
|
69
|
+
if (parent === dir) {
|
|
70
|
+
stop = true
|
|
71
|
+
break
|
|
72
|
+
}
|
|
73
|
+
dir = parent
|
|
74
|
+
}
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveExtendsTarget(extendsValue: string, fromFile: string): string | null {
|
|
79
|
+
const fromDir = path.dirname(fromFile)
|
|
80
|
+
if (extendsValue.startsWith('.') || path.isAbsolute(extendsValue)) {
|
|
81
|
+
const direct = path.resolve(fromDir, extendsValue)
|
|
82
|
+
if (fs.existsSync(direct) && fs.statSync(direct).isFile()) return direct
|
|
83
|
+
const withExt = direct + '.json'
|
|
84
|
+
if (fs.existsSync(withExt)) return withExt
|
|
85
|
+
const asIndex = path.join(direct, 'tsconfig.json')
|
|
86
|
+
if (fs.existsSync(asIndex)) return asIndex
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return require.resolve(extendsValue, { paths: [fromDir] })
|
|
91
|
+
} catch {
|
|
92
|
+
try {
|
|
93
|
+
return require.resolve(extendsValue + '/tsconfig.json', { paths: [fromDir] })
|
|
94
|
+
} catch {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type TsConfigRaw = {
|
|
101
|
+
extends?: string | string[]
|
|
102
|
+
compilerOptions?: Record<string, unknown>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readTsConfig(filePath: string, seen: Set<string> = new Set()): Record<string, unknown> {
|
|
106
|
+
const abs = path.resolve(filePath)
|
|
107
|
+
if (seen.has(abs)) return {}
|
|
108
|
+
seen.add(abs)
|
|
109
|
+
|
|
110
|
+
let parsed: TsConfigRaw
|
|
111
|
+
try {
|
|
112
|
+
const raw = fs.readFileSync(abs, 'utf-8')
|
|
113
|
+
parsed = JSON.parse(stripJsonComments(raw)) as TsConfigRaw
|
|
114
|
+
} catch {
|
|
115
|
+
return {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const extendsList: string[] = Array.isArray(parsed.extends)
|
|
119
|
+
? parsed.extends
|
|
120
|
+
: parsed.extends
|
|
121
|
+
? [parsed.extends]
|
|
122
|
+
: []
|
|
123
|
+
|
|
124
|
+
let merged: Record<string, unknown> = {}
|
|
125
|
+
for (const ext of extendsList) {
|
|
126
|
+
const target = resolveExtendsTarget(ext, abs)
|
|
127
|
+
if (target) merged = { ...merged, ...readTsConfig(target, seen) }
|
|
128
|
+
}
|
|
129
|
+
if (parsed.compilerOptions) merged = { ...merged, ...parsed.compilerOptions }
|
|
130
|
+
return merged
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function readPackageType(startDir: string): string | null {
|
|
134
|
+
const pkgPath = findUpwards(startDir, 'package.json')
|
|
135
|
+
if (!pkgPath) return null
|
|
136
|
+
try {
|
|
137
|
+
const json = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
138
|
+
return typeof json.type === 'string' ? json.type : null
|
|
139
|
+
} catch {
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function resolveImportStyle(options: GeneratorOptions): ImportStyle {
|
|
145
|
+
const config = (options.generator.config || {}) as Record<string, string>
|
|
146
|
+
const raw = typeof config.importStyle === 'string'
|
|
147
|
+
? config.importStyle.toLowerCase()
|
|
148
|
+
: 'auto'
|
|
149
|
+
|
|
150
|
+
if (!VALID_OVERRIDES.has(raw)) {
|
|
151
|
+
console.warn(
|
|
152
|
+
'[prisma-generator-express] Invalid importStyle "' + config.importStyle +
|
|
153
|
+
'". Expected one of: auto, none, js, ts. Falling back to auto.',
|
|
154
|
+
)
|
|
155
|
+
} else if (raw !== 'auto') {
|
|
156
|
+
return raw as ImportStyle
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const outputPath = options.generator.output?.value
|
|
160
|
+
const schemaDir = options.schemaPath ? path.dirname(options.schemaPath) : process.cwd()
|
|
161
|
+
const startDir = outputPath ? path.dirname(outputPath) : schemaDir
|
|
162
|
+
|
|
163
|
+
const tsconfigPath =
|
|
164
|
+
findUpwards(startDir, 'tsconfig.json') ||
|
|
165
|
+
findUpwards(schemaDir, 'tsconfig.json')
|
|
166
|
+
|
|
167
|
+
const compilerOptions: Record<string, unknown> = tsconfigPath
|
|
168
|
+
? readTsConfig(tsconfigPath)
|
|
169
|
+
: {}
|
|
170
|
+
|
|
171
|
+
if (compilerOptions.allowImportingTsExtensions === true) return 'ts'
|
|
172
|
+
|
|
173
|
+
const moduleVal = String(compilerOptions.module ?? '').toLowerCase()
|
|
174
|
+
const moduleResolution = String(compilerOptions.moduleResolution ?? '').toLowerCase()
|
|
175
|
+
|
|
176
|
+
if (NODE_MODULE_KEYWORDS.has(moduleVal) || NODE_MODULE_KEYWORDS.has(moduleResolution)) {
|
|
177
|
+
return 'js'
|
|
178
|
+
}
|
|
179
|
+
if (moduleResolution === 'bundler' || moduleResolution === 'classic' || moduleResolution === 'node') {
|
|
180
|
+
return 'none'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const pkgType = readPackageType(startDir) || readPackageType(schemaDir)
|
|
184
|
+
if (pkgType === 'module') return 'js'
|
|
185
|
+
|
|
186
|
+
return 'none'
|
|
187
|
+
}
|
|
@@ -26,40 +26,26 @@ export async function writeFileSafely({
|
|
|
26
26
|
operation,
|
|
27
27
|
}: WriteFileOptions): Promise<void> {
|
|
28
28
|
const outputPath = options.generator.output?.value
|
|
29
|
-
if (!outputPath)
|
|
30
|
-
throw new Error('Output path not defined')
|
|
31
|
-
}
|
|
29
|
+
if (!outputPath) throw new Error('Output path not defined')
|
|
32
30
|
|
|
33
31
|
let filePath: string
|
|
34
|
-
|
|
35
32
|
switch (operation) {
|
|
36
33
|
case 'combinedDocs':
|
|
37
34
|
filePath = path.join(outputPath, 'combinedDocs.ts')
|
|
38
35
|
break
|
|
39
|
-
|
|
40
36
|
case 'queryBuilder':
|
|
41
37
|
filePath = path.join(outputPath, 'queryBuilder.ts')
|
|
42
38
|
break
|
|
43
|
-
|
|
44
39
|
case 'operationRuntime':
|
|
45
40
|
filePath = path.join(outputPath, 'operationRuntime.ts')
|
|
46
41
|
break
|
|
47
|
-
|
|
48
42
|
default:
|
|
49
|
-
if (!model)
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
filePath = path.join(
|
|
53
|
-
outputPath,
|
|
54
|
-
model.name,
|
|
55
|
-
`${model.name}${operation}.ts`,
|
|
56
|
-
)
|
|
43
|
+
if (!model) throw new Error('Model required for operation: ' + operation)
|
|
44
|
+
filePath = path.join(outputPath, model.name, `${model.name}${operation}.ts`)
|
|
57
45
|
}
|
|
58
46
|
|
|
59
47
|
const dirPath = path.dirname(filePath)
|
|
60
|
-
if (!fs.existsSync(dirPath)) {
|
|
61
|
-
fs.mkdirSync(dirPath, { recursive: true })
|
|
62
|
-
}
|
|
48
|
+
if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true })
|
|
63
49
|
|
|
64
50
|
let formattedContent: string
|
|
65
51
|
try {
|
|
@@ -68,10 +54,8 @@ export async function writeFileSafely({
|
|
|
68
54
|
...resolvedOptions,
|
|
69
55
|
parser: 'typescript',
|
|
70
56
|
})
|
|
71
|
-
} catch
|
|
72
|
-
console.warn(
|
|
73
|
-
`⚠️ Prettier formatting failed for ${path.basename(filePath)}, writing unformatted`,
|
|
74
|
-
)
|
|
57
|
+
} catch {
|
|
58
|
+
console.warn('⚠️ Prettier formatting failed for ' + path.basename(filePath) + ', writing unformatted')
|
|
75
59
|
formattedContent = content
|
|
76
60
|
}
|
|
77
61
|
|