diff --git a/packages/schema/src/language-server/zmodel-formatter.ts b/packages/schema/src/language-server/zmodel-formatter.ts index c9300fa7a..3e3d1f018 100644 --- a/packages/schema/src/language-server/zmodel-formatter.ts +++ b/packages/schema/src/language-server/zmodel-formatter.ts @@ -25,8 +25,8 @@ export class ZModelFormatter extends AbstractFormatter { } else if (ast.isAbstractDeclaration(node)) { const bracesOpen = formatter.keyword('{'); const bracesClose = formatter.keyword('}'); - // this line decide the indent count return by this.getIndent() - formatter.interior(bracesOpen, bracesClose).prepend(Formatting.indent()); + // allow extra blank lines between declarations + formatter.interior(bracesOpen, bracesClose).prepend(Formatting.indent({ allowMore: true })); bracesOpen.prepend(Formatting.oneSpace()); bracesClose.prepend(Formatting.newLine()); } else if (ast.isModel(node)) { diff --git a/packages/schema/src/plugins/prisma/prisma-builder.ts b/packages/schema/src/plugins/prisma/prisma-builder.ts index 3ec0a47ca..ae77b6948 100644 --- a/packages/schema/src/plugins/prisma/prisma-builder.ts +++ b/packages/schema/src/plugins/prisma/prisma-builder.ts @@ -1,3 +1,4 @@ +import { AUXILIARY_FIELDS } from '@zenstackhq/sdk'; import indentString from './indent-string'; /** @@ -145,10 +146,23 @@ export class Model extends ContainerDeclaration { } toString(): string { + const auxiliaryFields = this.fields.filter((f) => AUXILIARY_FIELDS.includes(f.name)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const result: any[] = this.fields.filter((f) => !AUXILIARY_FIELDS.includes(f.name)); + + if (auxiliaryFields.length > 0) { + // Add a blank line before the auxiliary fields + result.push('', ...auxiliaryFields); + if (this.attributes.length > 0) { + // Add a blank line before the attributes + result.push(''); + } + } + result.push(...this.attributes); return ( super.toString() + `model ${this.name} {\n` + - indentString([...this.fields, ...this.attributes].map((d) => d.toString()).join('\n')) + + indentString(result.map((d) => d.toString()).join('\n')) + `\n}` ); } diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 5ddc9bd27..17a594377 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -100,6 +100,11 @@ export default class PrismaSchemaGenerator { } await writeFile(outFile, this.PRELUDE + prisma.toString()); + if (options.format === true) { + // run 'prisma format' + await execSync(`npx prisma format --schema ${outFile}`); + } + const generateClient = options.generateClient !== false; if (generateClient) { diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index fc4bfe8de..890fdf519 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -300,6 +300,10 @@ describe('Prisma generator test', () => { await getDMMF({ datamodel: content }); expect(content).toContain('@map("myGuardField")'); expect(content).toContain('@map("myTransactionField")'); + expect(content).toContain('value Int\n\n zenstack_guard'); + expect(content).toContain( + 'zenstack_transaction String? @map("myTransactionField")\n\n @@index([zenstack_transaction])' + ); }); it('abstract multi files', async () => { @@ -328,4 +332,34 @@ describe('Prisma generator test', () => { const todo = dmmf.datamodel.models.find((m) => m.name === 'Todo'); expect(todo?.documentation?.replace(/\s/g, '')).toBe(`@@allow('read', owner == auth())`.replace(/\s/g, '')); }); + + it('format prisma', async () => { + const model = await loadModel(` + datasource db { + provider = 'postgresql' + url = env('URL') + } + + model Post { + id Int @id() @default(autoincrement()) + title String + content String? + published Boolean @default(false) + @@allow('read', published) + } + `); + + const { name } = tmp.fileSync({ postfix: '.prisma' }); + await new PrismaSchemaGenerator().generate(model, { + provider: '@core/prisma', + schemaPath: 'schema.zmodel', + output: name, + format: true, + }); + + const content = fs.readFileSync(name, 'utf-8'); + const expected = fs.readFileSync(path.join(__dirname, './prisma/format.prisma'), 'utf-8'); + + expect(content).toBe(expected); + }); }); diff --git a/packages/schema/tests/generator/prisma/format.prisma b/packages/schema/tests/generator/prisma/format.prisma new file mode 100644 index 000000000..1eecb4978 --- /dev/null +++ b/packages/schema/tests/generator/prisma/format.prisma @@ -0,0 +1,22 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +datasource db { + provider = "postgresql" + url = env("URL") +} + +/// @@allow('read', published) +model Post { + id Int @id() @default(autoincrement()) + title String + content String? + published Boolean @default(false) + + zenstack_guard Boolean @default(true) + zenstack_transaction String? + + @@index([zenstack_transaction]) +}