prisma-generator-express 1.66.0 → 1.68.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 +188 -3432
- package/dist/client/encodeQueryParams.js +3 -1
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/misc.d.ts +1 -0
- package/dist/copy/misc.js +14 -0
- package/dist/copy/misc.js.map +1 -1
- package/dist/copy/operationDefinitions.d.ts +4 -1
- package/dist/copy/operationDefinitions.js +133 -9
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +68 -12
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +53 -6
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateHonoOpenApi.d.ts +13 -0
- package/dist/generators/generateHonoOpenApi.js +70 -0
- package/dist/generators/generateHonoOpenApi.js.map +1 -0
- package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
- package/dist/generators/generateImportPrismaStatement.js +6 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateMcpApp.d.ts +10 -0
- package/dist/generators/generateMcpApp.js +113 -0
- package/dist/generators/generateMcpApp.js.map +1 -0
- package/dist/generators/generateModelMcp.d.ts +9 -0
- package/dist/generators/generateModelMcp.js +145 -0
- package/dist/generators/generateModelMcp.js.map +1 -0
- package/dist/generators/generateModelMetadata.js +29 -15
- package/dist/generators/generateModelMetadata.js.map +1 -1
- package/dist/generators/generateOperationCore.d.ts +3 -3
- package/dist/generators/generateOperationCore.js +51 -16
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRelationMeta.js +2 -2
- package/dist/generators/generateRelationMeta.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +2 -2
- package/dist/generators/generateRouteConfigType.js +29 -11
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +7 -4
- package/dist/generators/generateRouter.js +164 -65
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +106 -86
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +59 -414
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateRouterPartsHono.d.ts +14 -0
- package/dist/generators/generateRouterPartsHono.js +180 -0
- package/dist/generators/generateRouterPartsHono.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.d.ts +1 -1
- package/dist/generators/generateUnifiedDocs.js +15 -8
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +2 -2
- package/dist/generators/generateUnifiedHandler.js +42 -18
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/guardOptions.js +12 -7
- package/dist/guardOptions.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +123 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.d.ts +1 -1
- package/dist/utils/copyFiles.js +42 -7
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- package/dist/utils/mcpGate.d.ts +10 -0
- package/dist/utils/mcpGate.js +118 -0
- package/dist/utils/mcpGate.js.map +1 -0
- package/dist/utils/pathCasing.d.ts +4 -0
- package/dist/utils/pathCasing.js +40 -0
- package/dist/utils/pathCasing.js.map +1 -0
- package/dist/utils/resolveImportStyle.js +13 -5
- package/dist/utils/resolveImportStyle.js.map +1 -1
- package/dist/utils/writeFileSafely.js +6 -0
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +22 -5
- package/src/bin.ts +1 -1
- package/src/client/encodeQueryParams.ts +39 -35
- package/src/constants.ts +1 -1
- package/src/copy/autoIncludePlanner.ts +93 -27
- package/src/copy/autoIncludePlannerGuarded.ts +171 -48
- package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
- package/src/copy/buildModelOpenApi.ts +229 -176
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/docsRenderer.ts +14 -15
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/mcpRuntime.ts +1017 -0
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +177 -25
- package/src/copy/operationPipeline.ts +267 -0
- package/src/copy/operationRuntime.ts +124 -26
- package/src/copy/operationSchemas.ts +2773 -0
- package/src/copy/pagination.ts +35 -13
- package/src/copy/parseQueryParams.ts +47 -48
- package/src/copy/projectionDefaults.ts +6 -4
- package/src/copy/routeConfig.express.ts +16 -4
- package/src/copy/routeConfig.fastify.ts +16 -2
- package/src/copy/routeConfig.hono.ts +46 -14
- package/src/copy/routeConfig.ts +175 -40
- package/src/copy/routerParts.ts +524 -0
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +77 -20
- package/src/generators/generateHonoHandler.ts +62 -14
- package/src/generators/generateHonoOpenApi.ts +93 -0
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateMcpApp.ts +133 -0
- package/src/generators/generateModelMcp.ts +208 -0
- package/src/generators/generateModelMetadata.ts +40 -22
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +171 -68
- package/src/generators/generateRouterFastify.ts +117 -89
- package/src/generators/generateRouterHono.ts +84 -464
- package/src/generators/generateRouterPartsHono.ts +220 -0
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +47 -24
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +161 -6
- package/src/utils/copyFiles.ts +235 -168
- package/src/utils/importExt.ts +2 -2
- package/src/utils/mcpGate.ts +157 -0
- package/src/utils/pathCasing.ts +42 -0
- package/src/utils/resolveImportStyle.ts +37 -15
- package/src/utils/strings.ts +2 -2
- package/src/utils/writeFileSafely.ts +12 -2
package/src/utils/copyFiles.ts
CHANGED
|
@@ -1,168 +1,235 @@
|
|
|
1
|
-
import { GeneratorOptions } from '@prisma/generator-helper'
|
|
2
|
-
import * as fs from 'fs'
|
|
3
|
-
import * as path from 'path'
|
|
4
|
-
import { Target } from '../constants'
|
|
5
|
-
import { ImportStyle } from './resolveImportStyle'
|
|
6
|
-
import { importExt } from './importExt'
|
|
7
|
-
|
|
8
|
-
const SHARED_FILES = [
|
|
9
|
-
'parseQueryParams.ts',
|
|
10
|
-
'buildModelOpenApi.ts',
|
|
11
|
-
'operationDefinitions.ts',
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
1
|
+
import { GeneratorOptions } from '@prisma/generator-helper'
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
import { Target } from '../constants'
|
|
5
|
+
import { ImportStyle } from './resolveImportStyle'
|
|
6
|
+
import { importExt } from './importExt'
|
|
7
|
+
|
|
8
|
+
const SHARED_FILES = [
|
|
9
|
+
'parseQueryParams.ts',
|
|
10
|
+
'buildModelOpenApi.ts',
|
|
11
|
+
'operationDefinitions.ts',
|
|
12
|
+
'operationSchemas.ts',
|
|
13
|
+
'misc.ts',
|
|
14
|
+
'routeConfig.ts',
|
|
15
|
+
'docsRenderer.ts',
|
|
16
|
+
'operationRuntime.ts',
|
|
17
|
+
'operationPipeline.ts',
|
|
18
|
+
'errorMapper.ts',
|
|
19
|
+
'sse.ts',
|
|
20
|
+
'pagination.ts',
|
|
21
|
+
'materializedCount.ts',
|
|
22
|
+
'guardHelpers.ts',
|
|
23
|
+
'scalarTypes.ts',
|
|
24
|
+
'concurrency.ts',
|
|
25
|
+
'projectionDefaults.ts',
|
|
26
|
+
'guardVariantRouting.ts',
|
|
27
|
+
'guardVariantError.ts',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const EXPRESS_ONLY_FILES = [
|
|
31
|
+
'autoIncludePlanner.ts',
|
|
32
|
+
'autoIncludePlannerGuarded.ts',
|
|
33
|
+
'autoIncludeRuntime.ts',
|
|
34
|
+
'autoIncludeRuntimeGuarded.ts',
|
|
35
|
+
'materializedRouter.ts',
|
|
36
|
+
'ndjson.ts',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
interface CopyFileOptions {
|
|
40
|
+
required?: boolean
|
|
41
|
+
importRewrites?: Array<{ from: string; to: string }>
|
|
42
|
+
destFilename?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveTemplateDir(subpath: string): string {
|
|
46
|
+
const fromSrc = path.join(__dirname, '..', '..', 'src', subpath)
|
|
47
|
+
if (fs.existsSync(fromSrc)) return fromSrc
|
|
48
|
+
const fromDist = path.join(__dirname, '..', subpath)
|
|
49
|
+
if (fs.existsSync(fromDist)) return fromDist
|
|
50
|
+
throw new Error(
|
|
51
|
+
'Template directory "' +
|
|
52
|
+
subpath +
|
|
53
|
+
'" not found.\n Searched:\n ' +
|
|
54
|
+
fromSrc +
|
|
55
|
+
'\n ' +
|
|
56
|
+
fromDist +
|
|
57
|
+
'\n __dirname: ' +
|
|
58
|
+
__dirname,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function escapeRegex(str: string): string {
|
|
63
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const RELATIVE_IMPORT_RE =
|
|
67
|
+
/(\b(?:import|export)\b[^'";]*?\bfrom\s+|\bimport\s+|\bimport\s*\(\s*)(['"])(\.{1,2}\/[^'"\n]+)\2/g
|
|
68
|
+
|
|
69
|
+
const SKIP_EXTENSION_RE = /\.(m?[jt]sx?|json|css|svg|png|jpe?g|gif|webp|wasm)$/i
|
|
70
|
+
|
|
71
|
+
function applyExtension(content: string, style: ImportStyle): string {
|
|
72
|
+
if (style === 'none') return content
|
|
73
|
+
const ext = importExt(style)
|
|
74
|
+
if (!ext) return content
|
|
75
|
+
return content.replace(RELATIVE_IMPORT_RE, (_match, prefix, quote, spec) => {
|
|
76
|
+
if (SKIP_EXTENSION_RE.test(spec)) return prefix + quote + spec + quote
|
|
77
|
+
return prefix + quote + spec + ext + quote
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function copyFileSync(
|
|
82
|
+
srcDir: string,
|
|
83
|
+
destDir: string,
|
|
84
|
+
filename: string,
|
|
85
|
+
importStyle: ImportStyle,
|
|
86
|
+
options?: CopyFileOptions,
|
|
87
|
+
): string | null {
|
|
88
|
+
const srcPath = path.join(srcDir, filename)
|
|
89
|
+
const destFilename = options?.destFilename || filename
|
|
90
|
+
const destPath = path.join(destDir, destFilename)
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(srcPath)) {
|
|
93
|
+
if (options?.required) return 'Required file not found: ' + srcPath
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const destDirPath = path.dirname(destPath)
|
|
98
|
+
if (!fs.existsSync(destDirPath))
|
|
99
|
+
fs.mkdirSync(destDirPath, { recursive: true })
|
|
100
|
+
|
|
101
|
+
let content = fs.readFileSync(srcPath, 'utf-8')
|
|
102
|
+
|
|
103
|
+
if (options?.importRewrites) {
|
|
104
|
+
for (const rewrite of options.importRewrites) {
|
|
105
|
+
if (rewrite.from === rewrite.to) continue
|
|
106
|
+
content = content.replace(
|
|
107
|
+
new RegExp('from [\'"]' + escapeRegex(rewrite.from) + '[\'"]', 'g'),
|
|
108
|
+
"from '" + rewrite.to + "'",
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const header =
|
|
114
|
+
'/**\n' +
|
|
115
|
+
' * Auto-generated by prisma-generator-express\n' +
|
|
116
|
+
' * DO NOT EDIT MANUALLY\n' +
|
|
117
|
+
' */\n\n'
|
|
118
|
+
|
|
119
|
+
if (!content.startsWith('/**')) content = header + content
|
|
120
|
+
|
|
121
|
+
content = applyExtension(content, importStyle)
|
|
122
|
+
|
|
123
|
+
fs.writeFileSync(destPath, content)
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function copyFiles(
|
|
128
|
+
options: GeneratorOptions,
|
|
129
|
+
target: Target,
|
|
130
|
+
importStyle: ImportStyle,
|
|
131
|
+
mcp = false,
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const outputPath = options.generator.output?.value
|
|
134
|
+
if (!outputPath) return
|
|
135
|
+
|
|
136
|
+
const copyBase = resolveTemplateDir('copy')
|
|
137
|
+
const errors: string[] = []
|
|
138
|
+
|
|
139
|
+
console.log(
|
|
140
|
+
' Copying utility files to: ' + outputPath + ' (target: ' + target + ')',
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
for (const file of SHARED_FILES) {
|
|
144
|
+
const err = copyFileSync(copyBase, outputPath, file, importStyle, {
|
|
145
|
+
required: true,
|
|
146
|
+
})
|
|
147
|
+
if (err) errors.push(err)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (target === 'express') {
|
|
151
|
+
for (const file of EXPRESS_ONLY_FILES) {
|
|
152
|
+
const err = copyFileSync(copyBase, outputPath, file, importStyle, {
|
|
153
|
+
required: true,
|
|
154
|
+
})
|
|
155
|
+
if (err) errors.push(err)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (target === 'hono') {
|
|
160
|
+
// The phase 9 assembly boundary. Imports the Hono route-config types by
|
|
161
|
+
// their source name; emitted as routeConfig.target.ts like every target.
|
|
162
|
+
const err = copyFileSync(
|
|
163
|
+
copyBase,
|
|
164
|
+
outputPath,
|
|
165
|
+
'routerParts.ts',
|
|
166
|
+
importStyle,
|
|
167
|
+
{
|
|
168
|
+
required: true,
|
|
169
|
+
importRewrites: [
|
|
170
|
+
{ from: './routeConfig.hono', to: './routeConfig.target' },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
if (err) errors.push(err)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (mcp) {
|
|
178
|
+
// MCP runtime is copied ONLY for mcp=true generations: an mcp=false
|
|
179
|
+
// output contains no MCP files, imports or dependencies at all.
|
|
180
|
+
const err = copyFileSync(
|
|
181
|
+
copyBase,
|
|
182
|
+
outputPath,
|
|
183
|
+
'mcpRuntime.ts',
|
|
184
|
+
importStyle,
|
|
185
|
+
{
|
|
186
|
+
required: true,
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
if (err) errors.push(err)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const targetConfigFile = 'routeConfig.' + target + '.ts'
|
|
193
|
+
const err = copyFileSync(
|
|
194
|
+
copyBase,
|
|
195
|
+
outputPath,
|
|
196
|
+
targetConfigFile,
|
|
197
|
+
importStyle,
|
|
198
|
+
{
|
|
199
|
+
required: true,
|
|
200
|
+
destFilename: 'routeConfig.target.ts',
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
if (err) errors.push(err)
|
|
204
|
+
|
|
205
|
+
const clientDir = path.join(outputPath, 'client')
|
|
206
|
+
if (!fs.existsSync(clientDir)) fs.mkdirSync(clientDir, { recursive: true })
|
|
207
|
+
|
|
208
|
+
const clientSrcDir = resolveTemplateDir('client')
|
|
209
|
+
const clientErr = copyFileSync(
|
|
210
|
+
clientSrcDir,
|
|
211
|
+
clientDir,
|
|
212
|
+
'encodeQueryParams.ts',
|
|
213
|
+
importStyle,
|
|
214
|
+
{
|
|
215
|
+
required: true,
|
|
216
|
+
importRewrites: [{ from: '../copy/misc', to: '../misc' }],
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
if (clientErr) errors.push(clientErr)
|
|
220
|
+
|
|
221
|
+
if (errors.length > 0) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
'Failed to copy ' +
|
|
224
|
+
errors.length +
|
|
225
|
+
' required file(s):\n' +
|
|
226
|
+
errors.map((e) => ' - ' + e).join('\n') +
|
|
227
|
+
'\n copyBase: ' +
|
|
228
|
+
copyBase +
|
|
229
|
+
'\n __dirname: ' +
|
|
230
|
+
__dirname,
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log(' ✓ Utility files copied successfully')
|
|
235
|
+
}
|
package/src/utils/importExt.ts
CHANGED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MCP is fail-closed at GENERATION time: a guard-dropped artifact must not
|
|
6
|
+
* carry MCP support, because every MCP tool enforces a guard shape and a
|
|
7
|
+
* dropped guard would turn reads and writes into unguarded operations.
|
|
8
|
+
*
|
|
9
|
+
* MCP tool schemas are built against prisma-guard 1.33 semantics (unique
|
|
10
|
+
* selectors, operator configs, forced-value merging). A consumer running an
|
|
11
|
+
* older guard would accept shapes the tool schemas misrepresent, so an
|
|
12
|
+
* mcp=true generation REFUSES to emit MCP support unless prisma-guard
|
|
13
|
+
* >= 1.33.0 is resolvable from the schema's project.
|
|
14
|
+
*/
|
|
15
|
+
export function assertMcpGuardCompatibility(
|
|
16
|
+
mcp: boolean,
|
|
17
|
+
dropGuard: boolean,
|
|
18
|
+
): void {
|
|
19
|
+
if (mcp && dropGuard) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'Generator option mcp=true cannot be combined with dropGuard=true. ' +
|
|
22
|
+
'MCP is fail-closed: every tool enforces a guard shape, so a ' +
|
|
23
|
+
'guard-dropped generation refuses to emit MCP support.',
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MCP_MIN_GUARD = '1.33.0'
|
|
29
|
+
|
|
30
|
+
type Semver = {
|
|
31
|
+
core: [number, number, number]
|
|
32
|
+
pre: string[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Semver 2.0.0 ordering, including prerelease precedence. */
|
|
36
|
+
export function parseSemver(v: string): Semver | null {
|
|
37
|
+
// strict semver 2.0.0: no leading-zero numeric identifiers, no empty
|
|
38
|
+
// dot-separated prerelease/build identifiers; build metadata is parsed
|
|
39
|
+
// and ignored for precedence
|
|
40
|
+
const numeric = '0|[1-9]' + '\\d*'
|
|
41
|
+
const ident = '[0-9A-Za-z-]+'
|
|
42
|
+
const dotted = ident + '(?:\\.' + ident + ')*'
|
|
43
|
+
const m = new RegExp(
|
|
44
|
+
`^(${numeric})\\.(${numeric})\\.(${numeric})(?:-(${dotted}))?(?:\\+(${dotted}))?$`,
|
|
45
|
+
).exec(v.trim())
|
|
46
|
+
if (!m) return null
|
|
47
|
+
const pre = m[4] ? m[4].split('.') : []
|
|
48
|
+
// semver 2.0.0: numeric PRERELEASE identifiers must not carry leading
|
|
49
|
+
// zeros (build-metadata identifiers are exempt)
|
|
50
|
+
for (const ident of pre) {
|
|
51
|
+
if (/^[0-9]+$/.test(ident) && ident.length > 1 && ident.startsWith('0')) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
core: [Number(m[1]), Number(m[2]), Number(m[3])],
|
|
57
|
+
pre,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `a < b` returns negative, `a > b` positive, equal 0 (semver 2.0.0). */
|
|
62
|
+
export function compareSemver(a: string, b: string): number {
|
|
63
|
+
const va = parseSemver(a)
|
|
64
|
+
const vb = parseSemver(b)
|
|
65
|
+
if (!va || !vb) {
|
|
66
|
+
// unparsable strings sort last so they never satisfy a minimum
|
|
67
|
+
return va ? 1 : vb ? -1 : 0
|
|
68
|
+
}
|
|
69
|
+
for (let i = 0; i < 3; i++) {
|
|
70
|
+
if (va.core[i] !== vb.core[i]) return va.core[i] - vb.core[i]
|
|
71
|
+
}
|
|
72
|
+
// no prerelease > any prerelease
|
|
73
|
+
if (va.pre.length === 0 && vb.pre.length === 0) return 0
|
|
74
|
+
if (va.pre.length === 0) return 1
|
|
75
|
+
if (vb.pre.length === 0) return -1
|
|
76
|
+
const len = Math.max(va.pre.length, vb.pre.length)
|
|
77
|
+
for (let i = 0; i < len; i++) {
|
|
78
|
+
const x = va.pre[i]
|
|
79
|
+
const y = vb.pre[i]
|
|
80
|
+
if (x === undefined) return -1 // shorter set is lower
|
|
81
|
+
if (y === undefined) return 1
|
|
82
|
+
const xn = /^\d+$/.test(x)
|
|
83
|
+
const yn = /^\d+$/.test(y)
|
|
84
|
+
if (xn && yn) {
|
|
85
|
+
if (Number(x) !== Number(y)) return Number(x) - Number(y)
|
|
86
|
+
} else if (xn)
|
|
87
|
+
return -1 // numeric identifiers are lower
|
|
88
|
+
else if (yn) return 1
|
|
89
|
+
else if (x !== y) return x < y ? -1 : 1
|
|
90
|
+
}
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolve prisma-guard's installed version the way Node resolves it for the
|
|
96
|
+
* generated artifact: walk UP from the schema's directory, checking
|
|
97
|
+
* `<ancestor>/node_modules/prisma-guard` at each level, and stop at the
|
|
98
|
+
* FIRST hit. Returns null when no ancestor carries an install.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveInstalledGuardVersion(
|
|
101
|
+
schemaPath: string,
|
|
102
|
+
): string | null {
|
|
103
|
+
const schemaDir = path.dirname(path.resolve(schemaPath))
|
|
104
|
+
let dir: string | undefined = schemaDir
|
|
105
|
+
while (dir) {
|
|
106
|
+
const pkgPath = path.join(
|
|
107
|
+
dir,
|
|
108
|
+
'node_modules',
|
|
109
|
+
'prisma-guard',
|
|
110
|
+
'package.json',
|
|
111
|
+
)
|
|
112
|
+
try {
|
|
113
|
+
const version = (
|
|
114
|
+
JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as {
|
|
115
|
+
version?: string
|
|
116
|
+
}
|
|
117
|
+
).version
|
|
118
|
+
if (typeof version === 'string') return version
|
|
119
|
+
} catch {
|
|
120
|
+
// not installed at this ancestor — keep walking
|
|
121
|
+
}
|
|
122
|
+
const parent = path.dirname(dir)
|
|
123
|
+
dir = parent !== dir ? parent : undefined
|
|
124
|
+
}
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function assertMcpGuardVersion(
|
|
129
|
+
mcp: boolean,
|
|
130
|
+
schemaPath: string,
|
|
131
|
+
resolve: (schemaPath: string) => string | null = resolveInstalledGuardVersion,
|
|
132
|
+
): void {
|
|
133
|
+
if (!mcp) return
|
|
134
|
+
const version = resolve(schemaPath)
|
|
135
|
+
if (version === null) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'mcp=true requires prisma-guard >= ' +
|
|
138
|
+
MCP_MIN_GUARD +
|
|
139
|
+
', but prisma-guard could not be resolved from the schema project. ' +
|
|
140
|
+
'Install it (npm install prisma-guard@^' +
|
|
141
|
+
MCP_MIN_GUARD +
|
|
142
|
+
') or turn mcp off.',
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
if (compareSemver(version, MCP_MIN_GUARD) < 0) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'mcp=true requires prisma-guard >= ' +
|
|
148
|
+
MCP_MIN_GUARD +
|
|
149
|
+
' (MCP tool schemas mirror 1.33 semantics: unique-selector where, ' +
|
|
150
|
+
'operator configs, forced-value merging). Found prisma-guard ' +
|
|
151
|
+
version +
|
|
152
|
+
'. Upgrade it (npm install prisma-guard@^' +
|
|
153
|
+
MCP_MIN_GUARD +
|
|
154
|
+
') or turn mcp off.',
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type PathCase = 'lower' | 'kebab' | 'raw'
|
|
2
|
+
|
|
3
|
+
export function parsePathCase(raw: unknown): PathCase {
|
|
4
|
+
const value = String(raw ?? 'lower').toLowerCase()
|
|
5
|
+
if (value === 'lower' || value === 'kebab' || value === 'raw') return value
|
|
6
|
+
throw new Error(
|
|
7
|
+
`Invalid pathCase "${String(raw)}". Expected "lower", "kebab", or "raw".`,
|
|
8
|
+
)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function toKebabCase(name: string): string {
|
|
12
|
+
let out = ''
|
|
13
|
+
for (let i = 0; i < name.length; i++) {
|
|
14
|
+
const ch = name[i]
|
|
15
|
+
const prev = i > 0 ? name[i - 1] : undefined
|
|
16
|
+
const next = name[i + 1]
|
|
17
|
+
const isUpper = ch >= 'A' && ch <= 'Z'
|
|
18
|
+
const prevIsLower = prev !== undefined && prev >= 'a' && prev <= 'z'
|
|
19
|
+
const prevIsDigit = prev !== undefined && prev >= '0' && prev <= '9'
|
|
20
|
+
const nextIsLower = next !== undefined && next >= 'a' && next <= 'z'
|
|
21
|
+
if (
|
|
22
|
+
isUpper &&
|
|
23
|
+
i > 0 &&
|
|
24
|
+
(prevIsLower ||
|
|
25
|
+
prevIsDigit ||
|
|
26
|
+
(prev !== undefined && prev >= 'A' && prev <= 'Z' && nextIsLower))
|
|
27
|
+
) {
|
|
28
|
+
out += '-'
|
|
29
|
+
}
|
|
30
|
+
out += ch.toLowerCase()
|
|
31
|
+
}
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function modelPathSegment(
|
|
36
|
+
modelName: string,
|
|
37
|
+
pathCase: PathCase,
|
|
38
|
+
): string {
|
|
39
|
+
if (pathCase === 'raw') return modelName
|
|
40
|
+
if (pathCase === 'kebab') return toKebabCase(modelName)
|
|
41
|
+
return modelName.toLowerCase()
|
|
42
|
+
}
|