-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
300 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
libs/tools/src/generators/prisma-sync/files/src/index.ts.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
const variable = "<%= name %>"; |
20 changes: 20 additions & 0 deletions
20
libs/tools/src/generators/prisma-sync/prisma-sync-generator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Tree } from '@nx/devkit' | ||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing' | ||
import { normalizePrismaSyncSchema } from '../../lib/prisma/normalize-prisma-sync-schema' | ||
|
||
import { prismaSyncGenerator } from './prisma-sync-generator' | ||
import { PrismaSyncGeneratorSchema } from './prisma-sync-schema' | ||
|
||
describe('prisma-sync generator', () => { | ||
let tree: Tree | ||
const options: PrismaSyncGeneratorSchema = normalizePrismaSyncSchema({ app: 'test' }) | ||
|
||
beforeEach(() => { | ||
tree = createTreeWithEmptyWorkspace() | ||
}) | ||
|
||
it('should run successfully', async () => { | ||
tree.write('prisma/schema.prisma', '') | ||
await prismaSyncGenerator(tree, options) | ||
}) | ||
}) |
16 changes: 16 additions & 0 deletions
16
libs/tools/src/generators/prisma-sync/prisma-sync-generator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Tree } from '@nx/devkit' | ||
import { normalizePrismaSyncSchema } from '../../lib/prisma/normalize-prisma-sync-schema' | ||
import { syncPrismaEntities } from '../../lib/prisma/sync-prisma-entities' | ||
import { PrismaSyncGeneratorSchema } from './prisma-sync-schema' | ||
|
||
export async function prismaSyncGenerator(tree: Tree, rawOptions: PrismaSyncGeneratorSchema) { | ||
const options = normalizePrismaSyncSchema(rawOptions) | ||
const prisma = tree.exists(options.schemaFile) | ||
if (!prisma) { | ||
throw new Error('Prisma schema not found') | ||
} | ||
|
||
syncPrismaEntities(tree, options) | ||
} | ||
|
||
export default prismaSyncGenerator |
5 changes: 5 additions & 0 deletions
5
libs/tools/src/generators/prisma-sync/prisma-sync-schema.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface PrismaSyncGeneratorSchema { | ||
app: string | ||
schemaFile?: string | ||
verbose?: boolean | ||
} |
24 changes: 24 additions & 0 deletions
24
libs/tools/src/generators/prisma-sync/prisma-sync-schema.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"$id": "PrismaSyncSchema", | ||
"title": "", | ||
"type": "object", | ||
"properties": { | ||
"app": { | ||
"type": "string", | ||
"description": "The name of the application to sync", | ||
"default": "api" | ||
}, | ||
"schemaFile": { | ||
"type": "string", | ||
"description": "The path to the schema file to sync", | ||
"default": "prisma/schema.prisma" | ||
}, | ||
"verbose": { | ||
"type": "boolean", | ||
"description": "Print more output", | ||
"default": false | ||
} | ||
}, | ||
"required": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export function getDomainFromProjectName(project: string, app: string) { | ||
return ( | ||
project | ||
// Remove the data-access suffix from the path | ||
.replace('/data-access', '') | ||
// Remove everything with ${app}/ and before it | ||
.replace(new RegExp(`.*${app}/`), '') | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type { Enum } from '@mrleebo/prisma-ast' | ||
import { Tree } from '@nx/devkit' | ||
import { getPrismaSchema } from './get-prisma-schema' | ||
|
||
export function getPrismaEnums(tree: Tree, schemaPath = 'prisma/schema.prisma'): Enum[] { | ||
const schema = getPrismaSchema(tree, schemaPath) | ||
|
||
return schema.list.filter((item) => item.type === 'enum') as Enum[] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { getProjects, names, Tree } from '@nx/devkit' | ||
import { getDomainFromProjectName } from './get-domain-from-project-name' | ||
|
||
export const suffixEnum = '.enum.ts' | ||
export const suffixModel = '.entity.ts' | ||
const filterModels = ['app-config', 'paging-meta', 'paging-response', '-paging'] | ||
|
||
// Get an array of all the data-access projects with their entity folder and entities | ||
export function getProjectEntities(tree: Tree, app: string) { | ||
// Get an array of all the projects | ||
const projects = Array.from(getProjects(tree).values()) | ||
|
||
// The path to the entity folder inside a data-access project | ||
const entityPath = 'src/lib/entity' | ||
|
||
// Get an array of all the data-access projects with their entity folder | ||
return ( | ||
projects | ||
// Filter to only include data-access projects | ||
.filter((project) => project.name.includes('data-access')) | ||
// Map the root path to the entity folder path | ||
.map(({ name, root }) => ({ | ||
name, | ||
root, | ||
entityRoot: `${root}/${entityPath}`, | ||
domain: getDomainFromProjectName(root, app), | ||
})) | ||
// Filter to only include projects that have an entity folder | ||
.filter((root) => tree.exists(root.entityRoot)) | ||
.map((project) => ({ | ||
...project, | ||
enums: getProjectEnums(tree, project.entityRoot), | ||
models: getProjectModels(tree, project.entityRoot), | ||
})) | ||
// Filter out projects that don't have models or enums | ||
.filter((project) => project.enums.length > 0 || project.models.length > 0) | ||
) | ||
} | ||
|
||
// Get an array of all the enums in the entity folder | ||
function getProjectEnums(tree: Tree, entityRoot: string) { | ||
return ( | ||
tree | ||
// Get all the files in the entity folder | ||
.children(entityRoot) | ||
// Filter to only include files that match the enum suffix | ||
.filter((item) => item.endsWith(suffixEnum)) | ||
// Remove the suffix from the file name | ||
.map((item) => item.replace(`${suffixEnum}`, '')) | ||
// Convert the file name to the class name | ||
.map((item) => names(item).className) | ||
) | ||
} | ||
|
||
// Get an array of all the models in the entity folder | ||
function getProjectModels(tree: Tree, entityRoot: string) { | ||
return ( | ||
tree | ||
// Get all the files in the entity folder | ||
.children(entityRoot) | ||
// Filter to only include files that match the model suffix | ||
.filter((child) => child.endsWith(suffixModel)) | ||
// Filter to remove the -paging.${suffixModel} files | ||
.filter((child) => !filterModels.some((filter) => child.includes(filter))) | ||
// Remove the suffix from the file name | ||
.map((child) => child.replace(`${suffixModel}`, '')) | ||
// Convert the file name to the class name | ||
.map((child) => names(child).className) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { PrismaSyncGeneratorSchema } from '../../generators/prisma-sync/prisma-sync-schema' | ||
|
||
export function normalizePrismaSyncSchema(schema: PrismaSyncGeneratorSchema): PrismaSyncGeneratorSchema { | ||
return { | ||
app: schema.app ?? 'api', | ||
schemaFile: schema.schemaFile ?? 'prisma/schema.prisma', | ||
verbose: schema.verbose ?? false, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { names, Tree } from '@nx/devkit' | ||
import { PrismaSyncGeneratorSchema } from '../../generators/prisma-sync/prisma-sync-schema' | ||
import { getPrismaEnums } from './get-prisma-enums' | ||
import { getPrismaModels } from './get-prisma-models' | ||
import { getProjectEntities, suffixEnum, suffixModel } from './get-project-entities' | ||
|
||
export function syncPrismaEntities(tree: Tree, { app, verbose }: PrismaSyncGeneratorSchema) { | ||
// Get all the projects with entities | ||
const projects = getProjectEntities(tree, app) | ||
// Get a list of all the domains in the workspace | ||
const domains = projects.map((i) => i.domain) | ||
|
||
// Loop through all the enums in the prisma schema | ||
for (const prismaEnum of getPrismaEnums(tree)) { | ||
// Check if the enum exists in the workspace | ||
|
||
const found = projects | ||
// Loop through all the projects in the workspace and find the one that has the enum | ||
.find((entity) => entity.enums.find((entityEnum) => entityEnum === prismaEnum.name)) | ||
|
||
if (found) { | ||
log(`SKIP: Enum ${prismaEnum.name} => found in project ${found.name}`) | ||
continue | ||
} | ||
|
||
const domain = domains.find((domain) => prismaEnum.name.toLowerCase().startsWith(domain.toLowerCase())) | ||
|
||
if (!domain) { | ||
log(`SKIP: Enum ${prismaEnum.name} => can't find domain`) | ||
continue | ||
} | ||
// Get the project to add the enum to | ||
const project = projects.find((project) => project.domain === domain) | ||
|
||
// Get the file name for the enum | ||
const filename = names(prismaEnum.name + suffixEnum).fileName | ||
|
||
// Get the target file path | ||
const file = `${project.entityRoot}/${filename}` | ||
|
||
// Get the template for the enum | ||
const content = getEnumTemplate(prismaEnum.name) | ||
|
||
tree.write(file, content) | ||
if (verbose) { | ||
log(`ADD Enum ${prismaEnum.name} to project ${project.name}`) | ||
} | ||
|
||
const exportLine = `export * from './lib/entity/${filename}'`.replace('.ts', '') | ||
const indexFile = `${project.root}/src/index.ts` | ||
const indexContent = tree.read(indexFile).toString() | ||
|
||
if (!indexContent.includes(exportLine)) { | ||
tree.write(indexFile, `${indexContent}\n${exportLine}`) | ||
} | ||
} | ||
|
||
// Loop through all the models in the prisma schema | ||
for (const prismaModel of getPrismaModels(tree)) { | ||
// Check if the model exists in the workspace | ||
const found = projects | ||
// Loop through all the projects in the workspace and find the one that has the model | ||
.find((entity) => entity.models.find((entityModel) => entityModel === prismaModel.name)) | ||
|
||
if (found) { | ||
log(`SKIP: Model ${prismaModel.name} => found in project ${found.name}`) | ||
continue | ||
} | ||
|
||
const domain = domains.find((domain) => prismaModel.name.toLowerCase().startsWith(domain.toLowerCase())) | ||
|
||
if (!domain) { | ||
log(`SKIP: Model ${prismaModel.name} => can't find domain`) | ||
continue | ||
} | ||
|
||
// Get the project to add the model to | ||
const project = projects.find((project) => project.domain === domain) | ||
|
||
// Get the file name for the model | ||
const filename = names(prismaModel.name + suffixModel).fileName | ||
|
||
// Get the target file path | ||
const file = `${project.entityRoot}/${filename}` | ||
|
||
// Get the template for the model | ||
const content = getModelTemplate(prismaModel.name) | ||
|
||
tree.write(file, content) | ||
log(`ADD Model ${prismaModel.name} to project ${project.name}`) | ||
|
||
const exportLine = `export * from './lib/entity/${filename}'`.replace('.ts', '') | ||
const indexFile = `${project.root}/src/index.ts` | ||
const indexContent = tree.read(indexFile).toString() | ||
|
||
if (!indexContent.includes(exportLine)) { | ||
tree.write(indexFile, `${indexContent}\n${exportLine}`) | ||
} | ||
} | ||
|
||
function log(...args: string[]) { | ||
if (!verbose) return | ||
console.log(...args) | ||
} | ||
} | ||
|
||
function getEnumTemplate(name: string): string { | ||
return [ | ||
`import { registerEnumType } from '@nestjs/graphql'`, | ||
`import { ${name} } from '@prisma/client'`, | ||
`export { ${name} }`, | ||
``, | ||
`registerEnumType(${name}, { name: '${name}' })`, | ||
].join('\n') | ||
} | ||
|
||
function getModelTemplate(name: string): string { | ||
return [ | ||
`import { Field, HideField, ObjectType } from '@nestjs/graphql'`, | ||
``, | ||
`@ObjectType()`, | ||
`export class ${name} {`, | ||
`@Field()`, | ||
`id!: string`, | ||
`@Field({ nullable: true })`, | ||
`createdAt?: Date`, | ||
`@Field({ nullable: true })`, | ||
`updatedAt?: Date`, | ||
].join('\n') | ||
} |