@nestledjs/api 0.0.6 → 0.0.8
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 +2 -2
- package/src/account/generator.d.ts +3 -0
- package/src/account/generator.js +19 -0
- package/src/account/generator.js.map +1 -0
- package/src/app/generator.d.ts +6 -0
- package/src/app/generator.js +96 -0
- package/src/app/generator.js.map +1 -0
- package/src/config/generator.d.ts +3 -0
- package/src/config/generator.js +18 -0
- package/src/config/generator.js.map +1 -0
- package/src/core/generator.d.ts +3 -0
- package/src/core/generator.js +31 -0
- package/src/core/generator.js.map +1 -0
- package/src/custom/generator.d.ts +24 -0
- package/src/custom/generator.js +199 -0
- package/src/custom/generator.js.map +1 -0
- package/src/custom/schema.d.ts +5 -0
- package/src/custom/schema.js +3 -0
- package/src/custom/schema.js.map +1 -0
- package/src/extended/generator.d.ts +3 -0
- package/src/extended/generator.js +136 -0
- package/src/extended/generator.js.map +1 -0
- package/src/extended/index.d.ts +1 -0
- package/src/extended/index.js +7 -0
- package/src/extended/index.js.map +1 -0
- package/src/extended/{schema.ts → schema.d.ts} +2 -2
- package/src/extended/schema.js +3 -0
- package/src/extended/schema.js.map +1 -0
- package/src/generate-crud/generator.d.ts +50 -0
- package/src/generate-crud/generator.js +296 -0
- package/src/generate-crud/generator.js.map +1 -0
- package/src/generate-crud/schema.d.ts +8 -0
- package/src/generate-crud/schema.js +3 -0
- package/src/generate-crud/schema.js.map +1 -0
- package/src/index.d.ts +13 -0
- package/src/index.js +17 -0
- package/src/index.js.map +1 -0
- package/src/plugin/generator.d.ts +3 -0
- package/src/plugin/generator.js +74 -0
- package/src/plugin/generator.js.map +1 -0
- package/src/plugin/{schema.ts → schema.d.ts} +3 -3
- package/src/plugin/schema.js +3 -0
- package/src/plugin/schema.js.map +1 -0
- package/src/prisma/generator.d.ts +3 -0
- package/src/prisma/generator.js +61 -0
- package/src/prisma/generator.js.map +1 -0
- package/src/setup/generator.d.ts +3 -0
- package/src/setup/generator.js +104 -0
- package/src/setup/generator.js.map +1 -0
- package/src/smtp-mailer/generator.d.ts +2 -0
- package/src/smtp-mailer/generator.js +17 -0
- package/src/smtp-mailer/generator.js.map +1 -0
- package/src/user/generator.d.ts +2 -0
- package/src/user/generator.js +18 -0
- package/src/user/generator.js.map +1 -0
- package/src/utils/generator.d.ts +2 -0
- package/src/utils/generator.js +17 -0
- package/src/utils/generator.js.map +1 -0
- package/src/workspace-setup/generator.d.ts +1 -0
- package/src/workspace-setup/generator.js +42 -0
- package/src/workspace-setup/generator.js.map +1 -0
- package/src/workspace-setup/lib/helpers.d.ts +16 -0
- package/src/workspace-setup/lib/helpers.js +164 -0
- package/src/workspace-setup/lib/helpers.js.map +1 -0
- package/eslint.config.cjs +0 -28
- package/project.json +0 -47
- package/src/account/generator.spec.ts +0 -71
- package/src/account/generator.ts +0 -20
- package/src/app/generator.spec.ts +0 -112
- package/src/app/generator.ts +0 -105
- package/src/config/generator.spec.ts +0 -47
- package/src/config/generator.ts +0 -16
- package/src/core/generator.spec.ts +0 -85
- package/src/core/generator.ts +0 -35
- package/src/custom/generator.spec.ts +0 -75
- package/src/custom/generator.ts +0 -239
- package/src/custom/schema.ts +0 -5
- package/src/extended/generator.spec.ts +0 -95
- package/src/extended/generator.ts +0 -161
- package/src/extended/index.ts +0 -1
- package/src/generate-crud/generator.spec.ts +0 -84
- package/src/generate-crud/generator.ts +0 -354
- package/src/generate-crud/schema.ts +0 -8
- package/src/index.ts +0 -13
- package/src/plugin/generator.spec.ts +0 -18
- package/src/plugin/generator.ts +0 -74
- package/src/prisma/generator.spec.ts +0 -60
- package/src/prisma/generator.ts +0 -61
- package/src/setup/generator.spec.ts +0 -18
- package/src/setup/generator.ts +0 -107
- package/src/smtp-mailer/generator.spec.ts +0 -41
- package/src/smtp-mailer/generator.ts +0 -14
- package/src/user/generator.spec.ts +0 -41
- package/src/user/generator.ts +0 -15
- package/src/utils/generator.ts +0 -14
- package/src/workspace-setup/generator.spec.ts +0 -85
- package/src/workspace-setup/generator.ts +0 -57
- package/src/workspace-setup/lib/helpers.ts +0 -149
- package/tsconfig.json +0 -16
- package/tsconfig.lib.json +0 -23
- package/tsconfig.spec.json +0 -22
- package/vite.config.mts +0 -31
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
import { formatFiles, generateFiles, installPackagesTask, joinPathFragments, names, Tree } from '@nx/devkit'
|
|
2
|
-
import { getDMMF } from '@prisma/internals'
|
|
3
|
-
import { apiLibraryGenerator, getPrismaSchemaPath, readPrismaSchema } from '@nestledjs/utils'
|
|
4
|
-
import { GenerateCrudGeneratorSchema } from './schema'
|
|
5
|
-
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope'
|
|
6
|
-
import pluralize from 'pluralize'
|
|
7
|
-
|
|
8
|
-
// STEP 1: DEFINE INTERFACES FOR DATA AND DEPENDENCIES
|
|
9
|
-
interface CrudAuthConfig {
|
|
10
|
-
readOne?: string
|
|
11
|
-
readMany?: string
|
|
12
|
-
count?: string
|
|
13
|
-
create?: string
|
|
14
|
-
update?: string
|
|
15
|
-
delete?: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ModelType {
|
|
19
|
-
name: string
|
|
20
|
-
pluralName: string
|
|
21
|
-
fields: ReadonlyArray<Record<string, unknown> & { name: string; type: string }>
|
|
22
|
-
primaryField: string
|
|
23
|
-
modelName: string
|
|
24
|
-
modelPropertyName: string
|
|
25
|
-
pluralModelName: string
|
|
26
|
-
pluralModelPropertyName: string
|
|
27
|
-
auth?: CrudAuthConfig
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// This interface makes the generator logic testable by defining its external dependencies.
|
|
31
|
-
export interface GenerateCrudGeneratorDependencies {
|
|
32
|
-
formatFiles: typeof formatFiles
|
|
33
|
-
generateFiles: typeof generateFiles
|
|
34
|
-
installPackagesTask: typeof installPackagesTask
|
|
35
|
-
joinPathFragments: typeof joinPathFragments
|
|
36
|
-
names: typeof names
|
|
37
|
-
getDMMF: typeof getDMMF
|
|
38
|
-
apiLibraryGenerator: typeof apiLibraryGenerator
|
|
39
|
-
getPrismaSchemaPath: typeof getPrismaSchemaPath
|
|
40
|
-
readPrismaSchema: typeof readPrismaSchema
|
|
41
|
-
getNpmScope: typeof getNpmScope
|
|
42
|
-
pluralize: typeof pluralize
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// STEP 2: DEFINE PURE HELPER & CONTENT GENERATION FUNCTIONS
|
|
46
|
-
// These functions are side-effect free and can be tested independently.
|
|
47
|
-
|
|
48
|
-
export function parseCrudAuth(comment: string): CrudAuthConfig | null {
|
|
49
|
-
try {
|
|
50
|
-
const match = comment.match(/@crudAuth:\s*(\{.*\})/)
|
|
51
|
-
if (!match) return null
|
|
52
|
-
return JSON.parse(match[1])
|
|
53
|
-
} catch (e) {
|
|
54
|
-
console.error('Error parsing @crudAuth:', e)
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function getCrudAuthForModel(schema: string, modelName: string): CrudAuthConfig {
|
|
60
|
-
const defaultConfig: CrudAuthConfig = {
|
|
61
|
-
readOne: 'admin',
|
|
62
|
-
readMany: 'admin',
|
|
63
|
-
count: 'admin',
|
|
64
|
-
create: 'admin',
|
|
65
|
-
update: 'admin',
|
|
66
|
-
delete: 'admin',
|
|
67
|
-
}
|
|
68
|
-
const lines = schema.split('\n')
|
|
69
|
-
let modelDoc: string[] = []
|
|
70
|
-
let foundModel = false
|
|
71
|
-
for (const line of lines) {
|
|
72
|
-
const trimmedLine = line.trim()
|
|
73
|
-
if (
|
|
74
|
-
trimmedLine.startsWith(`model ${modelName}`) ||
|
|
75
|
-
trimmedLine.startsWith(`model ${modelName} `) ||
|
|
76
|
-
trimmedLine.startsWith(`model ${modelName}{`)
|
|
77
|
-
) {
|
|
78
|
-
foundModel = true
|
|
79
|
-
break
|
|
80
|
-
} else if (trimmedLine.startsWith('model ')) {
|
|
81
|
-
modelDoc = []
|
|
82
|
-
} else if (trimmedLine.startsWith('///') && !foundModel) {
|
|
83
|
-
modelDoc.push(trimmedLine)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (!foundModel) return defaultConfig
|
|
87
|
-
const authLine = modelDoc.find((line) => line.includes('@crudAuth:'))
|
|
88
|
-
if (!authLine) return defaultConfig
|
|
89
|
-
const config = parseCrudAuth(authLine)
|
|
90
|
-
return config ? { ...defaultConfig, ...config } : defaultConfig
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function getGuardForAuthLevel(level: string): string | null {
|
|
94
|
-
if (!level) return 'GqlAuthAdminGuard'
|
|
95
|
-
level = level.toLowerCase()
|
|
96
|
-
if (level === 'public') return null
|
|
97
|
-
if (level === 'user') return 'GqlAuthGuard'
|
|
98
|
-
if (level === 'admin') return 'GqlAuthAdminGuard'
|
|
99
|
-
const pascalCase = level.charAt(0).toUpperCase() + level.slice(1).toLowerCase()
|
|
100
|
-
return `GqlAuth${pascalCase}Guard`
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function toKebabCase(str: string): string {
|
|
104
|
-
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function generateResolverContent(model: ModelType, npmScope: string): string {
|
|
108
|
-
const usedGuards = new Set<string>()
|
|
109
|
-
if (model.auth) {
|
|
110
|
-
Object.values(model.auth).forEach((level) => {
|
|
111
|
-
if (level === 'public') return
|
|
112
|
-
const guard = getGuardForAuthLevel(level)
|
|
113
|
-
if (guard) usedGuards.add(guard)
|
|
114
|
-
})
|
|
115
|
-
} else {
|
|
116
|
-
usedGuards.add('GqlAuthAdminGuard')
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const guardImports =
|
|
120
|
-
usedGuards.size > 0 ? `import { ${Array.from(usedGuards).sort().join(', ')} } from '@${npmScope}/api/utils'` : ''
|
|
121
|
-
|
|
122
|
-
const readManyGuardDecorator = model.auth?.readMany ? getGuardForAuthLevel(model.auth.readMany) : 'GqlAuthAdminGuard'
|
|
123
|
-
const countGuardDecorator = model.auth?.count ? getGuardForAuthLevel(model.auth.count) : 'GqlAuthAdminGuard'
|
|
124
|
-
const readOneGuardDecorator = model.auth?.readOne ? getGuardForAuthLevel(model.auth.readOne) : 'GqlAuthAdminGuard'
|
|
125
|
-
const createGuardDecorator = model.auth?.create ? getGuardForAuthLevel(model.auth.create) : 'GqlAuthAdminGuard'
|
|
126
|
-
const updateGuardDecorator = model.auth?.update ? getGuardForAuthLevel(model.auth.update) : 'GqlAuthAdminGuard'
|
|
127
|
-
const deleteGuardDecorator = model.auth?.delete ? getGuardForAuthLevel(model.auth.delete) : 'GqlAuthAdminGuard'
|
|
128
|
-
|
|
129
|
-
const readManyMethodName = model.pluralModelPropertyName
|
|
130
|
-
const countMethodName = `${model.pluralModelPropertyName}Count`
|
|
131
|
-
const readOneMethodName = model.modelPropertyName
|
|
132
|
-
|
|
133
|
-
return `import { Args, Mutation, Query, Resolver, Info } from '@nestjs/graphql'
|
|
134
|
-
import { UseGuards } from '@nestjs/common'
|
|
135
|
-
import type { GraphQLResolveInfo } from 'graphql'
|
|
136
|
-
import { CorePaging } from '@${npmScope}/api/core/data-access'
|
|
137
|
-
import { ApiCrudDataAccessService } from '@${npmScope}/api/generated-crud/data-access'
|
|
138
|
-
import { ${model.modelName} } from '@${npmScope}/api/core/models'
|
|
139
|
-
import { Create${model.modelName}Input, List${model.modelName}Input, Update${
|
|
140
|
-
model.modelName
|
|
141
|
-
}Input } from '@${npmScope}/api/generated-crud/data-access'
|
|
142
|
-
${guardImports}
|
|
143
|
-
|
|
144
|
-
@Resolver(() => ${model.modelName})
|
|
145
|
-
export class Generated${model.modelName}Resolver {
|
|
146
|
-
constructor(private readonly service: ApiCrudDataAccessService) {}
|
|
147
|
-
|
|
148
|
-
@Query(() => [${model.modelName}], { nullable: true })
|
|
149
|
-
${readManyGuardDecorator ? `@UseGuards(${readManyGuardDecorator})` : ''}
|
|
150
|
-
${readManyMethodName}(
|
|
151
|
-
@Info() info: GraphQLResolveInfo,
|
|
152
|
-
@Args({ name: 'input', type: () => List${model.modelName}Input, nullable: true }) input?: List${
|
|
153
|
-
model.modelName
|
|
154
|
-
}Input,
|
|
155
|
-
) {
|
|
156
|
-
return this.service.${readManyMethodName}(info, input)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
@Query(() => CorePaging, { nullable: true })
|
|
160
|
-
${countGuardDecorator ? `@UseGuards(${countGuardDecorator})` : ''}
|
|
161
|
-
${countMethodName}(
|
|
162
|
-
@Args({ name: 'input', type: () => List${model.modelName}Input, nullable: true }) input?: List${
|
|
163
|
-
model.modelName
|
|
164
|
-
}Input,
|
|
165
|
-
) {
|
|
166
|
-
return this.service.${countMethodName}(input)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
@Query(() => ${model.modelName}, { nullable: true })
|
|
170
|
-
${readOneGuardDecorator ? `@UseGuards(${readOneGuardDecorator})` : ''}
|
|
171
|
-
${readOneMethodName}(
|
|
172
|
-
@Info() info: GraphQLResolveInfo,
|
|
173
|
-
@Args('${model.modelPropertyName}Id') ${model.modelPropertyName}Id: string
|
|
174
|
-
) {
|
|
175
|
-
return this.service.${readOneMethodName}(info, ${model.modelPropertyName}Id)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@Mutation(() => ${model.modelName}, { nullable: true })
|
|
179
|
-
${createGuardDecorator ? `@UseGuards(${createGuardDecorator})` : ''}
|
|
180
|
-
create${model.modelName}(
|
|
181
|
-
@Info() info: GraphQLResolveInfo,
|
|
182
|
-
@Args('input') input: Create${model.modelName}Input,
|
|
183
|
-
) {
|
|
184
|
-
return this.service.create${model.modelName}(info, input)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
@Mutation(() => ${model.modelName}, { nullable: true })
|
|
188
|
-
${updateGuardDecorator ? `@UseGuards(${updateGuardDecorator})` : ''}
|
|
189
|
-
update${model.modelName}(
|
|
190
|
-
@Info() info: GraphQLResolveInfo,
|
|
191
|
-
@Args('${model.modelPropertyName}Id') ${model.modelPropertyName}Id: string,
|
|
192
|
-
@Args('input') input: Update${model.modelName}Input,
|
|
193
|
-
) {
|
|
194
|
-
return this.service.update${model.modelName}(info, ${model.modelPropertyName}Id, input)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
@Mutation(() => ${model.modelName}, { nullable: true })
|
|
198
|
-
${deleteGuardDecorator ? `@UseGuards(${deleteGuardDecorator})` : ''}
|
|
199
|
-
delete${model.modelName}(
|
|
200
|
-
@Args('${model.modelPropertyName}Id') ${model.modelPropertyName}Id: string,
|
|
201
|
-
) {
|
|
202
|
-
return this.service.delete${model.modelName}(${model.modelPropertyName}Id)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
`
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export function generateFeatureModuleContent(models: ModelType[], npmScope: string): string {
|
|
209
|
-
return `import { Module } from '@nestjs/common'\nimport { ApiCrudDataAccessModule } from '@${npmScope}/api/generated-crud/data-access'\n${models
|
|
210
|
-
.map((model) => `import { Generated${model.modelName}Resolver } from './${toKebabCase(model.modelName)}.resolver'`)
|
|
211
|
-
.join('\n')}\n\n@Module({\n imports: [ApiCrudDataAccessModule],\n providers: [${models
|
|
212
|
-
.map((model) => `Generated${model.modelName}Resolver`)
|
|
213
|
-
.join(', ')}],\n})\nexport class ApiGeneratedCrudFeatureModule {}\n`
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function generateFeatureIndexContent(models: ModelType[]): string {
|
|
217
|
-
return `export * from './lib/api-admin-crud-feature.module'\n${models
|
|
218
|
-
.map((model) => `export * from './lib/${toKebabCase(model.modelName)}.resolver'`)
|
|
219
|
-
.join('\n')}\n`
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// STEP 3: DEFINE THE CORE LOGIC FUNCTION
|
|
223
|
-
// This function contains all the generator's logic but uses injected dependencies, making it testable.
|
|
224
|
-
export async function generateCrudLogic(
|
|
225
|
-
tree: Tree,
|
|
226
|
-
schema: GenerateCrudGeneratorSchema,
|
|
227
|
-
dependencies: GenerateCrudGeneratorDependencies,
|
|
228
|
-
) {
|
|
229
|
-
// Helper functions that now use injected dependencies
|
|
230
|
-
async function getAllPrismaModels(tree: Tree): Promise<ModelType[]> {
|
|
231
|
-
const prismaPath = dependencies.getPrismaSchemaPath(tree)
|
|
232
|
-
const prismaSchema = dependencies.readPrismaSchema(tree, prismaPath)
|
|
233
|
-
if (!prismaSchema) {
|
|
234
|
-
console.error(`No Prisma schema found at ${prismaPath}`)
|
|
235
|
-
return []
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
const dmmf = await dependencies.getDMMF({ datamodel: prismaSchema })
|
|
239
|
-
return dmmf.datamodel.models.map((model) => {
|
|
240
|
-
const singularPropertyName = model.name.charAt(0).toLowerCase() + model.name.slice(1)
|
|
241
|
-
const pluralPropertyName = dependencies.pluralize(singularPropertyName)
|
|
242
|
-
const authConfig = getCrudAuthForModel(prismaSchema, model.name)
|
|
243
|
-
return {
|
|
244
|
-
name: model.name,
|
|
245
|
-
pluralName: dependencies.pluralize(model.name),
|
|
246
|
-
fields: model.fields.map((field) => ({ ...field })),
|
|
247
|
-
primaryField: model.fields.find((f) => !f.isId && f.type === 'String')?.name || 'name',
|
|
248
|
-
modelName: model.name,
|
|
249
|
-
modelPropertyName: singularPropertyName,
|
|
250
|
-
pluralModelName: dependencies.pluralize(model.name),
|
|
251
|
-
pluralModelPropertyName: pluralPropertyName,
|
|
252
|
-
auth: authConfig,
|
|
253
|
-
}
|
|
254
|
-
})
|
|
255
|
-
} catch (error) {
|
|
256
|
-
console.error('Error parsing Prisma schema:', error)
|
|
257
|
-
return []
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async function createLibraries(tree: Tree, name: string) {
|
|
262
|
-
const dataAccessLibraryRoot = `libs/api/${name}/data-access`
|
|
263
|
-
const featureLibraryRoot = `libs/api/${name}/feature`
|
|
264
|
-
const dataAccessTemplatePath = dependencies.joinPathFragments(__dirname, './files/data-access')
|
|
265
|
-
const featureTemplatePath = dependencies.joinPathFragments(__dirname, './files/feature')
|
|
266
|
-
await dependencies.apiLibraryGenerator(tree, { name }, dataAccessTemplatePath, 'data-access')
|
|
267
|
-
await dependencies.apiLibraryGenerator(tree, { name }, featureTemplatePath, 'feature')
|
|
268
|
-
return { dataAccessLibraryRoot, featureLibraryRoot }
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async function generateModelFiles(
|
|
272
|
-
tree: Tree,
|
|
273
|
-
dataAccessLibraryRoot: string,
|
|
274
|
-
featureLibraryRoot: string,
|
|
275
|
-
models: ModelType[],
|
|
276
|
-
name: string,
|
|
277
|
-
) {
|
|
278
|
-
const npmScope = dependencies.getNpmScope(tree)
|
|
279
|
-
const nameObj = dependencies.names(name)
|
|
280
|
-
const substitutions = { ...nameObj, name, models, npmScope: `@${npmScope}`, apiClassName: 'PrismaCrud', tmpl: '' }
|
|
281
|
-
|
|
282
|
-
dependencies.generateFiles(
|
|
283
|
-
tree,
|
|
284
|
-
dependencies.joinPathFragments(__dirname, './files/data-access/src/lib'),
|
|
285
|
-
dependencies.joinPathFragments(dataAccessLibraryRoot, 'src/lib'),
|
|
286
|
-
{ ...substitutions, type: 'data-access' },
|
|
287
|
-
)
|
|
288
|
-
dependencies.generateFiles(
|
|
289
|
-
tree,
|
|
290
|
-
dependencies.joinPathFragments(__dirname, './files/data-access/src'),
|
|
291
|
-
dependencies.joinPathFragments(dataAccessLibraryRoot, 'src'),
|
|
292
|
-
{ ...substitutions, type: 'data-access' },
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
const featureModuleContent = generateFeatureModuleContent(models, npmScope)
|
|
296
|
-
tree.write(
|
|
297
|
-
dependencies.joinPathFragments(featureLibraryRoot, 'src/lib/api-admin-crud-feature.module.ts'),
|
|
298
|
-
featureModuleContent,
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
const featureIndexContent = generateFeatureIndexContent(models)
|
|
302
|
-
tree.write(dependencies.joinPathFragments(featureLibraryRoot, 'src/index.ts'), featureIndexContent)
|
|
303
|
-
|
|
304
|
-
for (const model of models) {
|
|
305
|
-
const resolverFilePath = dependencies.joinPathFragments(
|
|
306
|
-
featureLibraryRoot,
|
|
307
|
-
`src/lib/${toKebabCase(model.modelName)}.resolver.ts`,
|
|
308
|
-
)
|
|
309
|
-
const resolverContent = generateResolverContent(model, npmScope)
|
|
310
|
-
tree.write(resolverFilePath, resolverContent)
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Main Orchestration Logic
|
|
315
|
-
const name = schema.name || 'generated-crud'
|
|
316
|
-
const models = await getAllPrismaModels(tree)
|
|
317
|
-
if (models.length === 0) {
|
|
318
|
-
console.error('No Prisma models found')
|
|
319
|
-
return // Return early for the test case
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const { dataAccessLibraryRoot, featureLibraryRoot } = await createLibraries(tree, name)
|
|
323
|
-
await generateModelFiles(tree, dataAccessLibraryRoot, featureLibraryRoot, models, name)
|
|
324
|
-
await dependencies.formatFiles(tree)
|
|
325
|
-
|
|
326
|
-
return () => {
|
|
327
|
-
dependencies.installPackagesTask(tree)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// STEP 4: DEFINE THE DEFAULT EXPORT
|
|
332
|
-
// This is what Nx CLI executes. It's a simple wrapper that provides the *real* dependencies to the logic function.
|
|
333
|
-
export default async function (tree: Tree, schema: GenerateCrudGeneratorSchema) {
|
|
334
|
-
const dependencies: GenerateCrudGeneratorDependencies = {
|
|
335
|
-
formatFiles,
|
|
336
|
-
generateFiles,
|
|
337
|
-
installPackagesTask,
|
|
338
|
-
joinPathFragments,
|
|
339
|
-
names,
|
|
340
|
-
getDMMF,
|
|
341
|
-
apiLibraryGenerator,
|
|
342
|
-
getPrismaSchemaPath,
|
|
343
|
-
readPrismaSchema,
|
|
344
|
-
getNpmScope,
|
|
345
|
-
pluralize,
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
return await generateCrudLogic(tree, schema, dependencies)
|
|
350
|
-
} catch (error) {
|
|
351
|
-
console.error('Error in CRUD generator:', error)
|
|
352
|
-
throw error
|
|
353
|
-
}
|
|
354
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export * from './account/generator'
|
|
2
|
-
export * from './app/generator'
|
|
3
|
-
export * from './config/generator'
|
|
4
|
-
export * from './core/generator'
|
|
5
|
-
export * from './custom/generator'
|
|
6
|
-
export * from './generate-crud/generator'
|
|
7
|
-
export * from './smtp-mailer/generator'
|
|
8
|
-
export * from './prisma/generator'
|
|
9
|
-
export * from './setup/generator'
|
|
10
|
-
export * from './user/generator'
|
|
11
|
-
export * from './workspace-setup/generator'
|
|
12
|
-
export * from './plugin/generator'
|
|
13
|
-
export * from './utils/generator'
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Tree } from '@nx/devkit'
|
|
2
|
-
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
|
|
3
|
-
import generator from './generator'
|
|
4
|
-
|
|
5
|
-
describe('plugin-generator', () => {
|
|
6
|
-
let tree: Tree
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
tree = createTreeWithEmptyWorkspace()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('should generate plugin files in the plugins folder', async () => {
|
|
13
|
-
await generator(tree, { name: 'TestPlugin' })
|
|
14
|
-
expect(tree.exists('libs/api/custom/src/lib/plugins/test-plugin/test-plugin.service.ts')).toBe(true)
|
|
15
|
-
expect(tree.exists('libs/api/custom/src/lib/plugins/test-plugin/test-plugin.resolver.ts')).toBe(true)
|
|
16
|
-
expect(tree.exists('libs/api/custom/src/lib/plugins/test-plugin/test-plugin.module.ts')).toBe(true)
|
|
17
|
-
})
|
|
18
|
-
})
|
package/src/plugin/generator.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { formatFiles, installPackagesTask, Tree } from '@nx/devkit'
|
|
2
|
-
import { join } from 'path'
|
|
3
|
-
import { GeneratePluginGeneratorSchema } from './schema'
|
|
4
|
-
|
|
5
|
-
function toKebabCase(str: string): string {
|
|
6
|
-
return str
|
|
7
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
8
|
-
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
9
|
-
.toLowerCase()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function toPascalCase(str: string): string {
|
|
13
|
-
return str
|
|
14
|
-
.replace(/(^\w|[-_\s]\w)/g, (match) => match.replace(/[-_\s]/, '').toUpperCase())
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function ensureDirExists(tree: Tree, path: string) {
|
|
18
|
-
if (!tree.exists(path)) {
|
|
19
|
-
// Directory will be created when a file is written into it
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default async function (tree: Tree, schema: GeneratePluginGeneratorSchema) {
|
|
24
|
-
const name = schema.name
|
|
25
|
-
if (!name) throw new Error('Name is required')
|
|
26
|
-
const kebabName = toKebabCase(name)
|
|
27
|
-
const className = toPascalCase(name)
|
|
28
|
-
const customLibraryRoot = schema.directory ? `libs/api/${schema.directory}/custom` : `libs/api/custom`
|
|
29
|
-
const pluginsDir = join(customLibraryRoot, 'src/lib/plugins')
|
|
30
|
-
const pluginFolder = join(pluginsDir, kebabName)
|
|
31
|
-
|
|
32
|
-
await ensureDirExists(tree, pluginsDir)
|
|
33
|
-
await ensureDirExists(tree, pluginFolder)
|
|
34
|
-
|
|
35
|
-
// Service
|
|
36
|
-
const serviceContent = `import { Injectable } from '@nestjs/common'
|
|
37
|
-
|
|
38
|
-
@Injectable()
|
|
39
|
-
export class ${className}Service {
|
|
40
|
-
// Add your service logic here
|
|
41
|
-
}
|
|
42
|
-
`
|
|
43
|
-
tree.write(join(pluginFolder, `${kebabName}.service.ts`), serviceContent)
|
|
44
|
-
|
|
45
|
-
// Resolver
|
|
46
|
-
const resolverContent = `import { Resolver } from '@nestjs/graphql'
|
|
47
|
-
import { Injectable } from '@nestjs/common'
|
|
48
|
-
|
|
49
|
-
@Resolver()
|
|
50
|
-
@Injectable()
|
|
51
|
-
export class ${className}Resolver {
|
|
52
|
-
// Add your resolver logic here
|
|
53
|
-
}
|
|
54
|
-
`
|
|
55
|
-
tree.write(join(pluginFolder, `${kebabName}.resolver.ts`), resolverContent)
|
|
56
|
-
|
|
57
|
-
// Module
|
|
58
|
-
const moduleContent = `import { Module } from '@nestjs/common'
|
|
59
|
-
import { ${className}Service } from './${kebabName}.service'
|
|
60
|
-
import { ${className}Resolver } from './${kebabName}.resolver'
|
|
61
|
-
|
|
62
|
-
@Module({
|
|
63
|
-
providers: [${className}Service, ${className}Resolver],
|
|
64
|
-
exports: [${className}Service, ${className}Resolver],
|
|
65
|
-
})
|
|
66
|
-
export class ${className}Module {}
|
|
67
|
-
`
|
|
68
|
-
tree.write(join(pluginFolder, `${kebabName}.module.ts`), moduleContent)
|
|
69
|
-
|
|
70
|
-
await formatFiles(tree)
|
|
71
|
-
return () => {
|
|
72
|
-
installPackagesTask(tree)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { formatFiles, installPackagesTask, Tree } from '@nx/devkit'
|
|
2
|
-
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
|
|
3
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
-
import { apiLibraryGenerator } from '@nestledjs/utils'
|
|
5
|
-
|
|
6
|
-
import generator from './generator'
|
|
7
|
-
|
|
8
|
-
vi.mock('@nestledjs/utils', async () => {
|
|
9
|
-
const actual = await vi.importActual('@nestledjs/utils')
|
|
10
|
-
return {
|
|
11
|
-
...actual,
|
|
12
|
-
apiLibraryGenerator: vi.fn(),
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
vi.mock('@nx/devkit', async () => {
|
|
17
|
-
const actual = await vi.importActual('@nx/devkit')
|
|
18
|
-
return {
|
|
19
|
-
...actual,
|
|
20
|
-
formatFiles: vi.fn(),
|
|
21
|
-
installPackagesTask: vi.fn(),
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
describe('prisma generator', () => {
|
|
26
|
-
let tree: Tree
|
|
27
|
-
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
tree = createTreeWithEmptyWorkspace()
|
|
30
|
-
tree.write('package.json', JSON.stringify({ name: 'test' }))
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('should run successfully', async () => {
|
|
34
|
-
const callback = await generator(tree)
|
|
35
|
-
callback()
|
|
36
|
-
|
|
37
|
-
const packageJson = JSON.parse(tree.read('package.json', 'utf-8'))
|
|
38
|
-
|
|
39
|
-
expect(packageJson.prisma).toEqual({
|
|
40
|
-
schema: 'libs/api/prisma/src/lib/schemas',
|
|
41
|
-
seed: 'ts-node --project libs/api/core/models/tsconfig.lib.json libs/api/prisma/src/lib/seed/seed.ts',
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
expect(packageJson.scripts['generate:models']).toBe(
|
|
45
|
-
'ts-node --project libs/api/core/models/tsconfig.lib.json libs/api/core/models/src/lib/generate-models.ts',
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
expect(apiLibraryGenerator).toHaveBeenCalledWith(tree, { name: 'prisma', overwrite: false }, expect.any(String))
|
|
49
|
-
expect(formatFiles).toHaveBeenCalledWith(tree)
|
|
50
|
-
expect(installPackagesTask).toHaveBeenCalledWith(tree)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('should pass overwrite flag to apiLibraryGenerator when overwrite is true', async () => {
|
|
54
|
-
tree.write('package.json', JSON.stringify({ name: 'test' }))
|
|
55
|
-
const callback = await generator(tree, { overwrite: true })
|
|
56
|
-
callback()
|
|
57
|
-
|
|
58
|
-
expect(apiLibraryGenerator).toHaveBeenCalledWith(tree, { name: 'prisma', overwrite: true }, expect.any(String))
|
|
59
|
-
})
|
|
60
|
-
})
|
package/src/prisma/generator.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { formatFiles, installPackagesTask, joinPathFragments, Tree, updateJson } from '@nx/devkit'
|
|
2
|
-
import { apiLibraryGenerator } from '@nestledjs/utils'
|
|
3
|
-
import { ApiPrismaGeneratorSchema } from './schema'
|
|
4
|
-
|
|
5
|
-
export default async function generateLibraries(tree: Tree, options: ApiPrismaGeneratorSchema = {}) {
|
|
6
|
-
const templateRootPath = joinPathFragments(__dirname, './files')
|
|
7
|
-
const overwrite = options.overwrite === true
|
|
8
|
-
|
|
9
|
-
// Update package.json
|
|
10
|
-
updateJson(tree, 'package.json', (json) => {
|
|
11
|
-
// Add prisma schema path
|
|
12
|
-
json.prisma = {
|
|
13
|
-
schema: 'libs/api/prisma/src/lib/schemas',
|
|
14
|
-
seed: 'ts-node --project libs/api/core/models/tsconfig.lib.json libs/api/prisma/src/lib/seed/seed.ts',
|
|
15
|
-
}
|
|
16
|
-
// Add GraphQL model generation script for the 'core' library
|
|
17
|
-
if (!json.scripts) {
|
|
18
|
-
json.scripts = {}
|
|
19
|
-
}
|
|
20
|
-
if (!json.scripts['generate:models']) {
|
|
21
|
-
json.scripts['generate:models'] =
|
|
22
|
-
'ts-node --project libs/api/core/models/tsconfig.lib.json libs/api/core/models/src/lib/generate-models.ts'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Add all requested prisma scripts if not already present
|
|
26
|
-
if (!json.scripts['prisma:apply']) {
|
|
27
|
-
json.scripts['prisma:apply'] = 'pnpm prisma:format && pnpm prisma db push'
|
|
28
|
-
}
|
|
29
|
-
if (!json.scripts['prisma:db-push']) {
|
|
30
|
-
json.scripts['prisma:db-push'] = 'pnpm prisma db push'
|
|
31
|
-
}
|
|
32
|
-
if (!json.scripts['prisma:format']) {
|
|
33
|
-
json.scripts['prisma:format'] = 'pnpm prisma format'
|
|
34
|
-
}
|
|
35
|
-
if (!json.scripts['prisma:generate']) {
|
|
36
|
-
json.scripts['prisma:generate'] = 'pnpm prisma generate'
|
|
37
|
-
}
|
|
38
|
-
if (!json.scripts['prisma:migrate']) {
|
|
39
|
-
json.scripts['prisma:migrate'] = 'pnpm prisma migrate save && pnpm prisma migrate up'
|
|
40
|
-
}
|
|
41
|
-
if (!json.scripts['prisma:seed']) {
|
|
42
|
-
json.scripts['prisma:seed'] =
|
|
43
|
-
'ts-node --project libs/api/prisma/tsconfig.lib.json libs/api/prisma/src/lib/seed/seed.ts'
|
|
44
|
-
}
|
|
45
|
-
if (!json.scripts['prisma:studio']) {
|
|
46
|
-
json.scripts['prisma:studio'] = 'pnpm nx prisma:studio api'
|
|
47
|
-
}
|
|
48
|
-
if (!json.scripts['prisma:reset']) {
|
|
49
|
-
json.scripts['prisma:reset'] = 'pnpm prisma migrate reset && pnpm prisma:seed'
|
|
50
|
-
}
|
|
51
|
-
return json
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
await apiLibraryGenerator(tree, { name: 'prisma', overwrite }, templateRootPath)
|
|
55
|
-
|
|
56
|
-
await formatFiles(tree)
|
|
57
|
-
|
|
58
|
-
return () => {
|
|
59
|
-
installPackagesTask(tree)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
|
|
2
|
-
import { Tree } from '@nx/devkit'
|
|
3
|
-
import { apiSetupGenerator } from './generator'
|
|
4
|
-
|
|
5
|
-
describe('api-dependencies generator', () => {
|
|
6
|
-
let tree: Tree
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
tree = createTreeWithEmptyWorkspace()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('should add required dependencies to package.json', async () => {
|
|
13
|
-
await apiSetupGenerator(tree)
|
|
14
|
-
// Note: We can't easily test package.json modifications in the tree
|
|
15
|
-
// since addDependenciesToPackageJson is mocked in tests
|
|
16
|
-
expect(tree).toBeDefined()
|
|
17
|
-
})
|
|
18
|
-
})
|