@nestledjs/api 0.0.1

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 (132) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +11 -0
  3. package/eslint.config.cjs +28 -0
  4. package/generators.json +69 -0
  5. package/package.json +21 -0
  6. package/project.json +47 -0
  7. package/src/account/files/data-access/src/index.ts__tmpl__ +5 -0
  8. package/src/account/files/data-access/src/lib/api-account-data-access.module.ts__tmpl__ +10 -0
  9. package/src/account/files/data-access/src/lib/api-account-data-access.service.ts__tmpl__ +152 -0
  10. package/src/account/files/data-access/src/lib/dto/account-create-email.input.ts__tmpl__ +9 -0
  11. package/src/account/files/data-access/src/lib/dto/account-update-password.input.ts__tmpl__ +16 -0
  12. package/src/account/files/data-access/src/lib/dto/account-update-profile.input.ts__tmpl__ +25 -0
  13. package/src/account/files/feature/src/index.ts__tmpl__ +1 -0
  14. package/src/account/files/feature/src/lib/api-account-feature.module.ts__tmpl__ +9 -0
  15. package/src/account/files/feature/src/lib/api-account-feature.resolver.ts__tmpl__ +83 -0
  16. package/src/account/generator.spec.ts +71 -0
  17. package/src/account/generator.ts +20 -0
  18. package/src/account/schema.d.ts +3 -0
  19. package/src/account/schema.json +13 -0
  20. package/src/app/files/src/app.config.ts__tmpl__ +66 -0
  21. package/src/app/files/src/app.module.ts__tmpl__ +43 -0
  22. package/src/app/files/src/applogger.middleware.ts__tmpl__ +21 -0
  23. package/src/app/files/src/main.ts__tmpl__ +33 -0
  24. package/src/app/files/webpack.config.js__tmpl__ +54 -0
  25. package/src/app/generator.spec.ts +112 -0
  26. package/src/app/generator.ts +105 -0
  27. package/src/app/schema.d.ts +1 -0
  28. package/src/app/schema.json +9 -0
  29. package/src/config/files/src/index.ts__tmpl__ +3 -0
  30. package/src/config/files/src/lib/config.service.ts__tmpl__ +51 -0
  31. package/src/config/files/src/lib/configuration.ts__tmpl__ +32 -0
  32. package/src/config/files/src/lib/validation.ts__tmpl__ +21 -0
  33. package/src/config/generator.spec.ts +47 -0
  34. package/src/config/generator.ts +16 -0
  35. package/src/config/schema.d.ts +3 -0
  36. package/src/config/schema.json +13 -0
  37. package/src/core/files/data-access/src/index.ts__tmpl__ +5 -0
  38. package/src/core/files/data-access/src/lib/api-core-data-access.module.ts__tmpl__ +9 -0
  39. package/src/core/files/data-access/src/lib/api-core-data-access.service.ts__tmpl__ +97 -0
  40. package/src/core/files/data-access/src/lib/api-core-pub-sub.ts__tmpl__ +37 -0
  41. package/src/core/files/data-access/src/lib/dto/core-paging.input.ts__tmpl__ +26 -0
  42. package/src/core/files/data-access/src/lib/dto/multi-select-input.ts__tmpl__ +7 -0
  43. package/src/core/files/data-access/src/lib/models/core-paging.ts__tmpl__ +19 -0
  44. package/src/core/files/feature/src/index.ts__tmpl__ +2 -0
  45. package/src/core/files/feature/src/lib/api-core-feature.controller.ts__tmpl__ +12 -0
  46. package/src/core/files/feature/src/lib/api-core-feature.module.ts__tmpl__ +86 -0
  47. package/src/core/files/feature/src/lib/api-core-feature.resolver.ts__tmpl__ +12 -0
  48. package/src/core/files/feature/src/lib/api-core-feature.service.ts__tmpl__ +55 -0
  49. package/src/core/files/feature/src/lib/config/configuration.ts__tmpl__ +32 -0
  50. package/src/core/files/feature/src/lib/config/validation.ts__tmpl__ +25 -0
  51. package/src/core/files/feature/src/lib/plugins/complexity.plugin.ts__tmpl__ +51 -0
  52. package/src/core/files/feature/src/lib/plugins/logging.plugin.ts__tmpl__ +17 -0
  53. package/src/core/files/models/src/index.ts__tmpl__ +1 -0
  54. package/src/core/files/models/src/lib/generate-models.ts__tmpl__ +294 -0
  55. package/src/core/files/models/src/lib/models/core-paging.model.ts__tmpl__ +25 -0
  56. package/src/core/generator.spec.ts +85 -0
  57. package/src/core/generator.ts +35 -0
  58. package/src/core/schema.d.ts +3 -0
  59. package/src/core/schema.json +13 -0
  60. package/src/custom/generator.spec.ts +75 -0
  61. package/src/custom/generator.ts +239 -0
  62. package/src/custom/schema.json +21 -0
  63. package/src/custom/schema.ts +5 -0
  64. package/src/extended/generator.spec.ts +95 -0
  65. package/src/extended/generator.ts +161 -0
  66. package/src/extended/index.ts +1 -0
  67. package/src/extended/schema.json +12 -0
  68. package/src/extended/schema.ts +3 -0
  69. package/src/generate-crud/files/data-access/src/index.ts__tmpl__ +3 -0
  70. package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.module.ts__tmpl__ +11 -0
  71. package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.service.ts__tmpl__ +72 -0
  72. package/src/generate-crud/files/data-access/src/lib/dto/index.ts__tmpl__ +224 -0
  73. package/src/generate-crud/files/feature/.gitkeep +0 -0
  74. package/src/generate-crud/generator.spec.ts +84 -0
  75. package/src/generate-crud/generator.ts +354 -0
  76. package/src/generate-crud/schema.json +32 -0
  77. package/src/generate-crud/schema.ts +8 -0
  78. package/src/index.ts +13 -0
  79. package/src/plugin/generator.spec.ts +18 -0
  80. package/src/plugin/generator.ts +74 -0
  81. package/src/plugin/schema.json +14 -0
  82. package/src/plugin/schema.ts +4 -0
  83. package/src/prisma/files/src/index.ts__tmpl__ +1 -0
  84. package/src/prisma/files/src/lib/.gitkeep +1 -0
  85. package/src/prisma/files/src/lib/schemas/schema.prisma__tmpl__ +402 -0
  86. package/src/prisma/files/src/lib/seed/seed-data/iso-3166-countries.ts__tmpl__ +3239 -0
  87. package/src/prisma/files/src/lib/seed/seed-data/seed-users.ts__tmpl__ +32 -0
  88. package/src/prisma/files/src/lib/seed/seed.ts__tmpl__ +64 -0
  89. package/src/prisma/generator.spec.ts +60 -0
  90. package/src/prisma/generator.ts +61 -0
  91. package/src/prisma/schema.d.ts +3 -0
  92. package/src/prisma/schema.json +13 -0
  93. package/src/setup/generator.md +49 -0
  94. package/src/setup/generator.spec.ts +18 -0
  95. package/src/setup/generator.ts +106 -0
  96. package/src/setup/schema.json +8 -0
  97. package/src/smtp-mailer/files/data-access/src/index.ts__tmpl__ +2 -0
  98. package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.module.ts__tmpl__ +10 -0
  99. package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.service.ts__tmpl__ +61 -0
  100. package/src/smtp-mailer/generator.spec.ts +41 -0
  101. package/src/smtp-mailer/generator.ts +14 -0
  102. package/src/smtp-mailer/schema.d.ts +0 -0
  103. package/src/smtp-mailer/schema.json +7 -0
  104. package/src/user/files/data-access/src/index.ts__tmpl__ +5 -0
  105. package/src/user/files/data-access/src/lib/api-user-data-access.module.ts__tmpl__ +10 -0
  106. package/src/user/files/data-access/src/lib/api-user-data-access.service.ts__tmpl__ +119 -0
  107. package/src/user/files/data-access/src/lib/dto/admin-create-user.input.ts__tmpl__ +20 -0
  108. package/src/user/files/data-access/src/lib/dto/admin-update-user.input.ts__tmpl__ +29 -0
  109. package/src/user/files/feature/src/index.ts__tmpl__ +1 -0
  110. package/src/user/files/feature/src/lib/api-user-feature-admin.resolver.ts__tmpl__ +57 -0
  111. package/src/user/files/feature/src/lib/api-user-feature.module.ts__tmpl__ +10 -0
  112. package/src/user/files/feature/src/lib/api-user-feature.resolver.ts__tmpl__ +17 -0
  113. package/src/user/generator.spec.ts +41 -0
  114. package/src/user/generator.ts +15 -0
  115. package/src/user/schema.d.ts +0 -0
  116. package/src/user/schema.json +7 -0
  117. package/src/utils/files/src/index.ts__tmpl__ +3 -0
  118. package/src/utils/files/src/lib/decorators/ctx-user.decorator.ts__tmpl__ +6 -0
  119. package/src/utils/files/src/lib/guards/gql-auth-admin.guard.ts__tmpl__ +39 -0
  120. package/src/utils/files/src/lib/guards/gql-auth.guard.ts__tmpl__ +11 -0
  121. package/src/utils/generator.ts +14 -0
  122. package/src/utils/schema.json +8 -0
  123. package/src/workspace-setup/generator.md +39 -0
  124. package/src/workspace-setup/generator.spec.ts +82 -0
  125. package/src/workspace-setup/generator.ts +49 -0
  126. package/src/workspace-setup/lib/helpers.ts +142 -0
  127. package/src/workspace-setup/schema.d.ts +3 -0
  128. package/src/workspace-setup/schema.json +7 -0
  129. package/tsconfig.json +16 -0
  130. package/tsconfig.lib.json +23 -0
  131. package/tsconfig.spec.json +22 -0
  132. package/vite.config.mts +37 -0
@@ -0,0 +1,294 @@
1
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs' // Added readdirSync, lstatSync
2
+ import { join } from 'path'
3
+ import { execSync } from 'child_process'
4
+ import { getDMMF } from '@prisma/internals'
5
+
6
+ // Find project root and get schema path from package.json
7
+ function findProjectRoot(startDir: string): string {
8
+ try {
9
+ // Try to use Nx's utility first if available
10
+ try {
11
+ const { findRootSync } = require('@nx/devkit')
12
+ return findRootSync(startDir)
13
+ } catch {
14
+ // Fallback to process.cwd() which should be project root when running scripts
15
+ return process.cwd()
16
+ }
17
+ } catch (error) {
18
+ console.error('Error finding project root:', error)
19
+ return process.cwd() // Fallback
20
+ }
21
+ }
22
+
23
+ // Determine the correct prisma import path based on package.json configuration
24
+ function getPrismaImportPath(): string {
25
+ try {
26
+ const projectRoot = findProjectRoot(__dirname)
27
+ const packageJsonPath = join(projectRoot, 'package.json')
28
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
29
+
30
+ const schemaPathSetting = packageJson.prisma?.schema || ''
31
+
32
+ // Updated logic: if the schema path includes 'libs/api/prisma' (or similar new structure)
33
+ if (schemaPathSetting.includes('libs/api/prisma') || schemaPathSetting.includes('prisma/src/lib')) {
34
+ return '@<%= npmScope %>/api/prisma'
35
+ }
36
+
37
+ // Fallback to the original path if not explicitly the new structure
38
+ return '@<%= npmScope %>/api/core/data-access'
39
+ } catch (error) {
40
+ console.error('Error determining Prisma import path:', error)
41
+ // Default to the new path as a sensible fallback
42
+ return '@<%= npmScope %>/api/prisma'
43
+ }
44
+ }
45
+
46
+ // Get schema directory path from package.json
47
+ function getPrismaSchemaDir(): string {
48
+ // Renamed
49
+ try {
50
+ const projectRoot = findProjectRoot(__dirname)
51
+ const packageJsonPath = join(projectRoot, 'package.json')
52
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
53
+
54
+ if (!packageJson.prisma?.schema) {
55
+ throw new Error('prisma.schema path not found in package.json')
56
+ }
57
+
58
+ // Join the project root with the schema path (which should be a directory)
59
+ return join(projectRoot, packageJson.prisma.schema)
60
+ } catch (error) {
61
+ console.error('Error getting Prisma schema directory path:', error)
62
+ // Fallback to the old directory path for backward compatibility
63
+ return join(__dirname, '../../../data-access/src/prisma/schemas') // Assuming this was the directory
64
+ }
65
+ }
66
+
67
+ // Paths
68
+ const schemaDir = getPrismaSchemaDir() // Renamed variable
69
+ const outputDir = join(__dirname, './models')
70
+ const prismaImportPath = getPrismaImportPath()
71
+
72
+ async function main() {
73
+ // Ensure output directory exists
74
+ if (!existsSync(outputDir)) {
75
+ mkdirSync(outputDir, { recursive: true })
76
+ }
77
+
78
+ // Verify schema directory exists and is a directory
79
+ if (!existsSync(schemaDir) || !lstatSync(schemaDir).isDirectory()) {
80
+ console.error(`Error: Prisma schema directory not found or is not a directory at ${schemaDir}`)
81
+ console.error('Please check your package.json prisma.schema configuration.')
82
+ process.exit(1)
83
+ }
84
+
85
+ // Generate Prisma client first (Prisma CLI handles directory schemas well)
86
+ console.log(`Generating Prisma client using schema files in ${schemaDir}...`)
87
+ // Note: Prisma CLI will look for a schema.prisma file in this dir,
88
+ // or process all .prisma files if it's configured for multi-file.
89
+ // For `prisma generate`, it's often best if there's a primary schema.prisma that imports others,
90
+ // or if all files are at the same level.
91
+ // If `prisma generate` itself fails, you might need to adjust how Prisma is set up for multi-file schemas.
92
+ // For now, we assume `prisma generate` works with the directory.
93
+ execSync(`npx prisma generate --schema "${schemaDir}"`, { stdio: 'inherit' })
94
+ // Alternatively, if your Prisma setup truly supports just passing the directory:
95
+ // execSync(`npx prisma generate --schema "${schemaDir}"`, { stdio: 'inherit' });
96
+ // Pick the one that works for your `prisma generate` setup. If you have a main `schema.prisma` that
97
+ // is just configuration and other files hold models/enums, pointing `prisma generate` to that
98
+ // `schema.prisma` specifically might be more robust, as it will then find the other files
99
+ // based on its own internal logic (like `previewFeatures = ["multiFileSchema"]`).
100
+ // For getDMMF, we will manually concatenate.
101
+
102
+ // Read and parse all schema files for DMMF
103
+ console.log('Reading Prisma schema files for DMMF generation...')
104
+ const schemaFiles = readdirSync(schemaDir).filter((file) => file.endsWith('.prisma'))
105
+
106
+ if (schemaFiles.length === 0) {
107
+ console.error(`Error: No .prisma files found in directory ${schemaDir}`)
108
+ process.exit(1)
109
+ }
110
+
111
+ let combinedSchemaContent = ''
112
+ console.log('Concatenating schema files:')
113
+ for (const file of schemaFiles) {
114
+ const filePath = join(schemaDir, file)
115
+ console.log(` - ${filePath}`)
116
+ combinedSchemaContent += readFileSync(filePath, 'utf-8') + '\n\n' // Add newlines between files
117
+ }
118
+
119
+ const dmmf = await getDMMF({ datamodel: combinedSchemaContent })
120
+
121
+ // Extract models and enums from parsed schema
122
+ const models = dmmf.datamodel.models
123
+ const enums = dmmf.datamodel.enums
124
+
125
+ // Generate models
126
+ console.log('Generating TypeScript models...')
127
+ const modelsOutput = generateModels(models, enums)
128
+ writeFileSync(join(outputDir, 'models.ts'), modelsOutput)
129
+
130
+ // Generate enums
131
+ console.log('Generating TypeScript enums...')
132
+ const enumsOutput = generateEnums(enums)
133
+ writeFileSync(join(outputDir, 'enums.ts'), enumsOutput)
134
+
135
+ // Generate index file
136
+ console.log('Generating index file...')
137
+ const indexOutput = generateIndex()
138
+ writeFileSync(join(outputDir, 'index.ts'), indexOutput)
139
+
140
+ console.log('Models and enums generated successfully!')
141
+ }
142
+
143
+ function generateModels(models: readonly any[], enums: readonly any[]): string {
144
+ // Check if any model field uses Float
145
+ const usesFloat = models.some(model => model.fields.some((field: { type: string }) => field.type === 'Float'));
146
+ let output = `import { Field, ObjectType${usesFloat ? ', Float' : ''}, Int } from '@nestjs/graphql';\n`
147
+ output += `import { GraphQLJSONObject } from 'graphql-type-json';\n`
148
+
149
+ // Check if any model field uses Decimal
150
+ const usesDecimal = models.some(model => model.fields.some((field: { type: string }) => field.type === 'Decimal'));
151
+ if (usesDecimal) {
152
+ output += `import { Decimal } from '@prisma/client/runtime/library';\n`;
153
+ output += `import { GraphQLDecimal } from 'prisma-graphql-type-decimal';\n`;
154
+ }
155
+ // Ensure Prisma and enums are imported correctly
156
+ const enumNames = enums.map((e) => e.name)
157
+ output += `import { Prisma } from '${prismaImportPath}';\n`
158
+ if (enumNames.length > 0) {
159
+ output += `import { ${enumNames.join(', ')} } from './enums';\n`;
160
+ }
161
+ output += `\n`
162
+
163
+ for (const model of models) {
164
+ output += `@ObjectType({ description: undefined })\nexport class ${model.name} {\n` // Added description: undefined for clarity if not set
165
+
166
+ for (const field of model.fields) {
167
+ const isList = field.isList
168
+ const isEnum = field.kind === 'enum'
169
+ const isRelation = field.kind === 'object'
170
+ const originalType = field.type
171
+ // const isRequired = field.isRequired // Natively supported by DMMF
172
+
173
+ // DEBUG LINE
174
+ // console.log(`DEBUG: Processing field: ${model.name}.${field.name}, Kind: ${field.kind}, OriginalType: ${originalType}, IsList: ${isList}, IsRequired: ${isRequired}`);
175
+
176
+ let tsType = originalType
177
+
178
+ if (field.kind === 'scalar') {
179
+ if (originalType === 'Int' || originalType === 'Float') {
180
+ tsType = 'number'
181
+ } else if (originalType === 'Decimal') {
182
+ tsType = 'Decimal'
183
+ } else if (originalType === 'String' || originalType === 'ID') {
184
+ tsType = 'string'
185
+ } else if (originalType === 'Boolean') {
186
+ tsType = 'boolean'
187
+ } else if (originalType === 'DateTime') {
188
+ tsType = 'Date'
189
+ } else if (originalType === 'Json') {
190
+ tsType = 'Prisma.JsonValue'
191
+ } else if (originalType === 'BigInt') {
192
+ tsType = 'bigint' // Prisma uses bigint for BigInt
193
+ } else if (originalType === 'Bytes') {
194
+ tsType = 'Buffer' // Prisma uses Buffer for Bytes
195
+ }
196
+ } else if (field.kind === 'enum') {
197
+ tsType = originalType
198
+ } else if (field.kind === 'object') {
199
+ tsType = originalType
200
+ }
201
+
202
+ let decorator = '@Field('
203
+ const options: string[] = []
204
+ // Always add nullable: true for all fields
205
+ options.push('nullable: true')
206
+
207
+ // Always declare the type in the decorator
208
+ let decoratorType = ''
209
+ if (isList) {
210
+ let listItemGraphQLType = originalType
211
+ if (originalType === 'Int') listItemGraphQLType = 'Int'
212
+ else if (originalType === 'Float') listItemGraphQLType = 'Float'
213
+ else if (originalType === 'Decimal') listItemGraphQLType = 'GraphQLDecimal'
214
+ else if (originalType === 'Json') listItemGraphQLType = 'GraphQLJSONObject'
215
+ else if (isEnum || isRelation)
216
+ listItemGraphQLType = originalType // Assumes enum/type is registered with GraphQL
217
+ else if (originalType === 'DateTime') listItemGraphQLType = 'Date'
218
+ else if (originalType === 'Boolean') listItemGraphQLType = 'Boolean'
219
+ else if (originalType.toLowerCase() === 'string' || originalType === 'ID') {
220
+ listItemGraphQLType = 'String'
221
+ } else {
222
+ listItemGraphQLType = originalType
223
+ }
224
+ decoratorType = `() => [${listItemGraphQLType}]`
225
+ } else {
226
+ // Always declare the type for non-list fields
227
+ if (originalType === 'Int') decoratorType = '() => Int'
228
+ else if (originalType === 'Float') decoratorType = '() => Float'
229
+ else if (originalType === 'Decimal') decoratorType = '() => GraphQLDecimal'
230
+ else if (originalType === 'Json') decoratorType = '() => GraphQLJSONObject'
231
+ else if (isEnum || isRelation) decoratorType = `() => ${originalType}`
232
+ else if (originalType === 'DateTime') decoratorType = '() => Date'
233
+ else if (originalType === 'Boolean') decoratorType = '() => Boolean'
234
+ else if (originalType.toLowerCase() === 'string' || originalType === 'ID') decoratorType = '() => String'
235
+ else decoratorType = `() => ${originalType}`
236
+ }
237
+
238
+ decorator += decoratorType
239
+ decorator += `, { ${options.join(', ')} }`
240
+ decorator += ')'
241
+
242
+ output += ` ${decorator}\n`
243
+ // Make all fields optional regardless of isRequired value
244
+ const optionalMarker = '?' // Always add a question mark
245
+
246
+ // For relations, use Partial<Type> only if it's not required and can be partially loaded
247
+ // Otherwise, if it's a relation, it's either the full Type or Type[] or null/undefined.
248
+ // For simplicity, let's assume if it's a relation object, it's the Type itself.
249
+ // Partial is useful for inputs or update DTOs, less so for GQL ObjectTypes unless specifically designed.
250
+ let finalTsType = tsType
251
+ if (isRelation && !isList) {
252
+ // Single related object
253
+ finalTsType = `${tsType}` // Not Partial<Type> by default for GQL object types
254
+ }
255
+
256
+ // Never add '| null' to any field
257
+ output += ` ${field.name}${optionalMarker}: ${finalTsType}${isList ? '[]' : ''} | null;\n\n`
258
+ }
259
+ output += `}\n\n`
260
+ }
261
+ return output
262
+ }
263
+
264
+ function generateEnums(enums: readonly any[]): string {
265
+ let output = '// Generated from Prisma schema\n\n'
266
+ output += "import { registerEnumType } from '@nestjs/graphql';\n"
267
+
268
+ if (enums.length > 0) {
269
+ const enumNames = enums.map((e) => e.name).join(', ')
270
+ output += `import { ${enumNames} } from '${prismaImportPath}';\n\n`
271
+ output += `export { ${enumNames} };\n\n`
272
+
273
+ enums.forEach((enumType) => {
274
+ output += `registerEnumType(${enumType.name}, { name: '${enumType.name}' });\n\n`
275
+ })
276
+ } else {
277
+ output += '// No enums found in schema to generate.\n'
278
+ }
279
+
280
+ return output
281
+ }
282
+
283
+ function generateIndex(): string {
284
+ return `// Generated from Prisma schema
285
+ export * from './models'
286
+ export * from './enums'
287
+ export * from './core-paging.model'
288
+ `
289
+ }
290
+
291
+ main().catch((e) => {
292
+ console.error('Error during generation:', e)
293
+ process.exit(1)
294
+ })
@@ -0,0 +1,25 @@
1
+ import { Field, ObjectType } from '@nestjs/graphql'
2
+
3
+ @ObjectType()
4
+ export class CorePaging {
5
+ @Field({ nullable: true })
6
+ total?: number
7
+
8
+ @Field({ nullable: true })
9
+ count?: number
10
+
11
+ @Field({ nullable: true })
12
+ take?: number
13
+
14
+ @Field({ nullable: true })
15
+ page?: number
16
+
17
+ @Field({ nullable: true })
18
+ skip?: number
19
+
20
+ @Field({ nullable: true })
21
+ sum?: number
22
+
23
+ @Field({ nullable: true })
24
+ totalSum?: number
25
+ }
@@ -0,0 +1,85 @@
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, installPlugins } 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
+ installPlugins: vi.fn(),
14
+ }
15
+ })
16
+
17
+ vi.mock('@nx/devkit', async () => {
18
+ const actual = await vi.importActual('@nx/devkit')
19
+ return {
20
+ ...actual,
21
+ formatFiles: vi.fn(),
22
+ installPackagesTask: vi.fn(),
23
+ }
24
+ })
25
+
26
+ describe('core generator', () => {
27
+ let tree: Tree
28
+
29
+ beforeEach(() => {
30
+ tree = createTreeWithEmptyWorkspace()
31
+ })
32
+
33
+ it('should run successfully', async () => {
34
+ const callback = await generator(tree)
35
+ callback()
36
+
37
+ expect(installPlugins).toHaveBeenCalled()
38
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(
39
+ tree,
40
+ { name: 'core', overwrite: false },
41
+ expect.any(String),
42
+ 'data-access',
43
+ )
44
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(
45
+ tree,
46
+ { name: 'core', overwrite: false },
47
+ expect.any(String),
48
+ 'feature',
49
+ true,
50
+ )
51
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(
52
+ tree,
53
+ { name: 'core', overwrite: false },
54
+ expect.any(String),
55
+ 'models',
56
+ )
57
+ expect(formatFiles).toHaveBeenCalledWith(tree)
58
+ expect(installPackagesTask).toHaveBeenCalledWith(tree)
59
+ })
60
+
61
+ it('should pass overwrite flag to apiLibraryGenerator when overwrite is true', async () => {
62
+ const callback = await generator(tree, { overwrite: true })
63
+ callback()
64
+
65
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(
66
+ tree,
67
+ { name: 'core', overwrite: true },
68
+ expect.any(String),
69
+ 'data-access',
70
+ )
71
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(
72
+ tree,
73
+ { name: 'core', overwrite: true },
74
+ expect.any(String),
75
+ 'feature',
76
+ true,
77
+ )
78
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(
79
+ tree,
80
+ { name: 'core', overwrite: true },
81
+ expect.any(String),
82
+ 'models',
83
+ )
84
+ })
85
+ })
@@ -0,0 +1,35 @@
1
+ import { formatFiles, GeneratorCallback, installPackagesTask, joinPathFragments, Tree } from '@nx/devkit'
2
+ import { apiLibraryGenerator, installPlugins } from '@nestledjs/utils'
3
+ import { ApiCoreGeneratorSchema } from './schema'
4
+
5
+ export default async function generateLibraries(
6
+ tree: Tree,
7
+ options: ApiCoreGeneratorSchema = {},
8
+ ): Promise<GeneratorCallback> {
9
+ const templateRootPath = joinPathFragments(__dirname, './files')
10
+ const overwrite = options.overwrite === true
11
+
12
+ const dependencies = {
13
+ 'graphql-type-json': '^0.3.2',
14
+ '@nestjs/graphql': '^12.0.0',
15
+ '@nestjs/common': '^10.0.0',
16
+ '@nestjs/passport': '^10.0.0',
17
+ '@nestjs/axios': '^3.0.0',
18
+ '@prisma/client': '^6.9.0',
19
+ '@apollo/server': '^4.9.0',
20
+ }
21
+
22
+ const devDependencies = {}
23
+
24
+ await installPlugins(tree, dependencies, devDependencies)
25
+
26
+ await apiLibraryGenerator(tree, { name: 'core', overwrite }, templateRootPath, 'data-access')
27
+ await apiLibraryGenerator(tree, { name: 'core', overwrite }, templateRootPath, 'feature', true)
28
+ await apiLibraryGenerator(tree, { name: 'core', overwrite }, templateRootPath, 'models')
29
+
30
+ await formatFiles(tree)
31
+
32
+ return () => {
33
+ installPackagesTask(tree)
34
+ }
35
+ }
@@ -0,0 +1,3 @@
1
+ export interface ApiCoreGeneratorSchema {
2
+ overwrite?: boolean
3
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "$id": "ApiCore",
4
+ "title": "Create the API Core library",
5
+ "type": "object",
6
+ "properties": {
7
+ "overwrite": {
8
+ "type": "boolean",
9
+ "description": "Whether to overwrite existing core libraries if they exist.",
10
+ "default": false
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,75 @@
1
+ import { Tree } from '@nx/devkit'
2
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
3
+ import { vi, describe, it, expect, beforeEach } from 'vitest'
4
+ import { customGeneratorLogic, CustomGeneratorDependencies } from './generator'
5
+
6
+ const dmmf = {
7
+ datamodel: {
8
+ models: [
9
+ {
10
+ name: 'User',
11
+ fields: [
12
+ { name: 'id', type: 'Int', isId: true },
13
+ { name: 'name', type: 'String' },
14
+ ],
15
+ },
16
+ ],
17
+ },
18
+ }
19
+
20
+ describe('custom-generator', () => {
21
+ let tree: Tree
22
+ let mockDependencies: CustomGeneratorDependencies
23
+
24
+ beforeEach(() => {
25
+ tree = createTreeWithEmptyWorkspace()
26
+ mockDependencies = {
27
+ formatFiles: vi.fn(),
28
+ installPackagesTask: vi.fn(),
29
+ getDMMF: vi.fn().mockResolvedValue(dmmf),
30
+ addToModules: vi.fn(),
31
+ apiLibraryGenerator: vi.fn(),
32
+ getPrismaSchemaPath: vi.fn(() => 'prisma/schema.prisma'),
33
+ readPrismaSchema: vi.fn(() => `model User {}`),
34
+ execSync: vi.fn(),
35
+ getNpmScope: vi.fn(() => 'test-scope'),
36
+ pluralize: vi.fn((name: string) => (name.endsWith('s') ? name : name + 's')) as any,
37
+ join: vi.fn((...args: string[]) => args.join('/')),
38
+ }
39
+ vi.clearAllMocks()
40
+ })
41
+
42
+ it('should generate files correctly', async () => {
43
+ await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
44
+ expect(mockDependencies.apiLibraryGenerator).toHaveBeenCalledWith(
45
+ tree,
46
+ { name: 'custom' },
47
+ '',
48
+ undefined,
49
+ false,
50
+ )
51
+ // Check if files are generated in the new structure
52
+ expect(tree.exists('libs/api/custom/src/lib/default/user/user.service.ts')).toBe(true)
53
+ expect(tree.exists('libs/api/custom/src/lib/default/user/user.resolver.ts')).toBe(true)
54
+ expect(tree.exists('libs/api/custom/src/lib/default/user/user.module.ts')).toBe(true)
55
+ expect(tree.exists('libs/api/custom/src/index.ts')).toBe(true)
56
+ expect(tree.exists('libs/api/custom/src/lib/plugins')).toBe(true)
57
+ })
58
+
59
+ it('should be idempotent and not overwrite existing model folders', async () => {
60
+ // First run
61
+ await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
62
+ // Write a marker file to simulate user customization
63
+ tree.write('libs/api/custom/src/lib/default/user/custom.txt', 'do not overwrite')
64
+ // Second run
65
+ await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
66
+ // The marker file should still exist
67
+ expect(tree.read('libs/api/custom/src/lib/default/user/custom.txt', 'utf-8')).toBe('do not overwrite')
68
+ })
69
+
70
+ it('should not generate files if no models are found', async () => {
71
+ mockDependencies.getDMMF = vi.fn().mockResolvedValue({ datamodel: { models: [] } })
72
+ await customGeneratorLogic(tree, { name: 'custom' }, mockDependencies)
73
+ expect(mockDependencies.execSync).not.toHaveBeenCalled()
74
+ })
75
+ })