@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.
Files changed (102) hide show
  1. package/package.json +2 -2
  2. package/src/account/generator.d.ts +3 -0
  3. package/src/account/generator.js +19 -0
  4. package/src/account/generator.js.map +1 -0
  5. package/src/app/generator.d.ts +6 -0
  6. package/src/app/generator.js +96 -0
  7. package/src/app/generator.js.map +1 -0
  8. package/src/config/generator.d.ts +3 -0
  9. package/src/config/generator.js +18 -0
  10. package/src/config/generator.js.map +1 -0
  11. package/src/core/generator.d.ts +3 -0
  12. package/src/core/generator.js +31 -0
  13. package/src/core/generator.js.map +1 -0
  14. package/src/custom/generator.d.ts +24 -0
  15. package/src/custom/generator.js +199 -0
  16. package/src/custom/generator.js.map +1 -0
  17. package/src/custom/schema.d.ts +5 -0
  18. package/src/custom/schema.js +3 -0
  19. package/src/custom/schema.js.map +1 -0
  20. package/src/extended/generator.d.ts +3 -0
  21. package/src/extended/generator.js +136 -0
  22. package/src/extended/generator.js.map +1 -0
  23. package/src/extended/index.d.ts +1 -0
  24. package/src/extended/index.js +7 -0
  25. package/src/extended/index.js.map +1 -0
  26. package/src/extended/{schema.ts → schema.d.ts} +2 -2
  27. package/src/extended/schema.js +3 -0
  28. package/src/extended/schema.js.map +1 -0
  29. package/src/generate-crud/generator.d.ts +50 -0
  30. package/src/generate-crud/generator.js +296 -0
  31. package/src/generate-crud/generator.js.map +1 -0
  32. package/src/generate-crud/schema.d.ts +8 -0
  33. package/src/generate-crud/schema.js +3 -0
  34. package/src/generate-crud/schema.js.map +1 -0
  35. package/src/index.d.ts +13 -0
  36. package/src/index.js +17 -0
  37. package/src/index.js.map +1 -0
  38. package/src/plugin/generator.d.ts +3 -0
  39. package/src/plugin/generator.js +74 -0
  40. package/src/plugin/generator.js.map +1 -0
  41. package/src/plugin/{schema.ts → schema.d.ts} +3 -3
  42. package/src/plugin/schema.js +3 -0
  43. package/src/plugin/schema.js.map +1 -0
  44. package/src/prisma/generator.d.ts +3 -0
  45. package/src/prisma/generator.js +61 -0
  46. package/src/prisma/generator.js.map +1 -0
  47. package/src/setup/generator.d.ts +3 -0
  48. package/src/setup/generator.js +104 -0
  49. package/src/setup/generator.js.map +1 -0
  50. package/src/smtp-mailer/generator.d.ts +2 -0
  51. package/src/smtp-mailer/generator.js +17 -0
  52. package/src/smtp-mailer/generator.js.map +1 -0
  53. package/src/user/generator.d.ts +2 -0
  54. package/src/user/generator.js +18 -0
  55. package/src/user/generator.js.map +1 -0
  56. package/src/utils/generator.d.ts +2 -0
  57. package/src/utils/generator.js +17 -0
  58. package/src/utils/generator.js.map +1 -0
  59. package/src/workspace-setup/generator.d.ts +1 -0
  60. package/src/workspace-setup/generator.js +42 -0
  61. package/src/workspace-setup/generator.js.map +1 -0
  62. package/src/workspace-setup/lib/helpers.d.ts +16 -0
  63. package/src/workspace-setup/lib/helpers.js +164 -0
  64. package/src/workspace-setup/lib/helpers.js.map +1 -0
  65. package/eslint.config.cjs +0 -28
  66. package/project.json +0 -47
  67. package/src/account/generator.spec.ts +0 -71
  68. package/src/account/generator.ts +0 -20
  69. package/src/app/generator.spec.ts +0 -112
  70. package/src/app/generator.ts +0 -105
  71. package/src/config/generator.spec.ts +0 -47
  72. package/src/config/generator.ts +0 -16
  73. package/src/core/generator.spec.ts +0 -85
  74. package/src/core/generator.ts +0 -35
  75. package/src/custom/generator.spec.ts +0 -75
  76. package/src/custom/generator.ts +0 -239
  77. package/src/custom/schema.ts +0 -5
  78. package/src/extended/generator.spec.ts +0 -95
  79. package/src/extended/generator.ts +0 -161
  80. package/src/extended/index.ts +0 -1
  81. package/src/generate-crud/generator.spec.ts +0 -84
  82. package/src/generate-crud/generator.ts +0 -354
  83. package/src/generate-crud/schema.ts +0 -8
  84. package/src/index.ts +0 -13
  85. package/src/plugin/generator.spec.ts +0 -18
  86. package/src/plugin/generator.ts +0 -74
  87. package/src/prisma/generator.spec.ts +0 -60
  88. package/src/prisma/generator.ts +0 -61
  89. package/src/setup/generator.spec.ts +0 -18
  90. package/src/setup/generator.ts +0 -107
  91. package/src/smtp-mailer/generator.spec.ts +0 -41
  92. package/src/smtp-mailer/generator.ts +0 -14
  93. package/src/user/generator.spec.ts +0 -41
  94. package/src/user/generator.ts +0 -15
  95. package/src/utils/generator.ts +0 -14
  96. package/src/workspace-setup/generator.spec.ts +0 -85
  97. package/src/workspace-setup/generator.ts +0 -57
  98. package/src/workspace-setup/lib/helpers.ts +0 -149
  99. package/tsconfig.json +0 -16
  100. package/tsconfig.lib.json +0 -23
  101. package/tsconfig.spec.json +0 -22
  102. 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
- }
@@ -1,8 +0,0 @@
1
- export interface GenerateCrudGeneratorSchema {
2
- name: string;
3
- directory: string;
4
- model: string;
5
- plural: string;
6
- description?: string;
7
- overwrite?: boolean;
8
- }
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
- })
@@ -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
- })
@@ -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
- })