Skip to content

Commit fc8d03d

Browse files
authored
fix: compatibility with pnpm monorepo (#1393)
1 parent ef22b70 commit fc8d03d

File tree

4 files changed

+88
-34
lines changed

4 files changed

+88
-34
lines changed

packages/schema/src/plugins/enhancer/enhance/index.ts

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getAttributeArg,
66
getAuthModel,
77
getDataModels,
8+
getLiteral,
89
isDelegateModel,
910
type PluginOptions,
1011
} from '@zenstackhq/sdk';
@@ -14,12 +15,14 @@ import {
1415
ReferenceExpr,
1516
isArrayExpr,
1617
isDataModel,
18+
isGeneratorDecl,
1719
isReferenceExpr,
1820
type Model,
1921
} from '@zenstackhq/sdk/ast';
20-
import { getDMMF, getPrismaClientImportSpec, type DMMF } from '@zenstackhq/sdk/prisma';
22+
import { getDMMF, getPrismaClientImportSpec, getPrismaVersion, type DMMF } from '@zenstackhq/sdk/prisma';
2123
import fs from 'fs';
2224
import path from 'path';
25+
import semver from 'semver';
2326
import {
2427
FunctionDeclarationStructure,
2528
InterfaceDeclaration,
@@ -42,6 +45,8 @@ import { generateAuthType } from './auth-type-generator';
4245
// information of delegate models and their sub models
4346
type DelegateInfo = [DataModel, DataModel[]][];
4447

48+
const LOGICAL_CLIENT_GENERATION_PATH = './.logical-prisma-client';
49+
4550
export class EnhancerGenerator {
4651
constructor(
4752
private readonly model: Model,
@@ -60,7 +65,7 @@ export class EnhancerGenerator {
6065
// schema contains delegate models, need to generate a logical prisma schema
6166
const result = await this.generateLogicalPrisma();
6267

63-
logicalPrismaClientDir = './.logical-prisma-client';
68+
logicalPrismaClientDir = LOGICAL_CLIENT_GENERATION_PATH;
6469
dmmf = result.dmmf;
6570

6671
// create a reexport of the logical prisma client
@@ -190,40 +195,76 @@ export function enhance(prisma: any, context?: EnhancementContext<${authTypePara
190195

191196
private async generateLogicalPrisma() {
192197
const prismaGenerator = new PrismaSchemaGenerator(this.model);
193-
const prismaClientOutDir = './.logical-prisma-client';
194-
const logicalPrismaFile = path.join(this.outDir, 'logical.prisma');
195-
await prismaGenerator.generate({
196-
provider: '@internal', // doesn't matter
197-
schemaPath: this.options.schemaPath,
198-
output: logicalPrismaFile,
199-
overrideClientGenerationPath: prismaClientOutDir,
200-
mode: 'logical',
201-
});
202198

203-
// generate the prisma client
204-
const generateCmd = `prisma generate --schema "${logicalPrismaFile}" --no-engine`;
199+
// dir of the zmodel file
200+
const zmodelDir = path.dirname(this.options.schemaPath);
201+
202+
// generate a temp logical prisma schema in zmodel's dir
203+
const logicalPrismaFile = path.join(zmodelDir, `logical-${Date.now()}.prisma`);
204+
205+
// calculate a relative output path to output the logical prisma client into enhancer's output dir
206+
const prismaClientOutDir = path.join(path.relative(zmodelDir, this.outDir), LOGICAL_CLIENT_GENERATION_PATH);
205207
try {
206-
// run 'prisma generate'
207-
await execPackage(generateCmd, { stdio: 'ignore' });
208-
} catch {
209-
await trackPrismaSchemaError(logicalPrismaFile);
208+
await prismaGenerator.generate({
209+
provider: '@internal', // doesn't matter
210+
schemaPath: this.options.schemaPath,
211+
output: logicalPrismaFile,
212+
overrideClientGenerationPath: prismaClientOutDir,
213+
mode: 'logical',
214+
});
215+
216+
// generate the prisma client
217+
218+
// only run prisma client generator for the logical schema
219+
const prismaClientGeneratorName = this.getPrismaClientGeneratorName(this.model);
220+
let generateCmd = `prisma generate --schema "${logicalPrismaFile}" --generator=${prismaClientGeneratorName}`;
221+
222+
const prismaVersion = getPrismaVersion();
223+
if (!prismaVersion || semver.gte(prismaVersion, '5.2.0')) {
224+
// add --no-engine to reduce generation size if the prisma version supports
225+
generateCmd += ' --no-engine';
226+
}
227+
210228
try {
211-
// run 'prisma generate' again with output to the console
212-
await execPackage(generateCmd);
229+
// run 'prisma generate'
230+
await execPackage(generateCmd, { stdio: 'ignore' });
213231
} catch {
214-
// noop
232+
await trackPrismaSchemaError(logicalPrismaFile);
233+
try {
234+
// run 'prisma generate' again with output to the console
235+
await execPackage(generateCmd);
236+
} catch {
237+
// noop
238+
}
239+
throw new PluginError(name, `Failed to run "prisma generate" on logical schema: ${logicalPrismaFile}`);
215240
}
216-
throw new PluginError(name, `Failed to run "prisma generate" on logical schema: ${logicalPrismaFile}`);
217-
}
218241

219-
// make a bunch of typing fixes to the generated prisma client
220-
await this.processClientTypes(path.join(this.outDir, prismaClientOutDir));
242+
// make a bunch of typing fixes to the generated prisma client
243+
await this.processClientTypes(path.join(this.outDir, LOGICAL_CLIENT_GENERATION_PATH));
244+
245+
return {
246+
prismaSchema: logicalPrismaFile,
247+
// load the dmmf of the logical prisma schema
248+
dmmf: await getDMMF({ datamodel: fs.readFileSync(logicalPrismaFile, { encoding: 'utf-8' }) }),
249+
};
250+
} finally {
251+
if (fs.existsSync(logicalPrismaFile)) {
252+
fs.rmSync(logicalPrismaFile);
253+
}
254+
}
255+
}
221256

222-
return {
223-
prismaSchema: logicalPrismaFile,
224-
// load the dmmf of the logical prisma schema
225-
dmmf: await getDMMF({ datamodel: fs.readFileSync(logicalPrismaFile, { encoding: 'utf-8' }) }),
226-
};
257+
private getPrismaClientGeneratorName(model: Model) {
258+
for (const generator of model.declarations.filter(isGeneratorDecl)) {
259+
if (
260+
generator.fields.some(
261+
(f) => f.name === 'provider' && getLiteral<string>(f.value) === 'prisma-client-js'
262+
)
263+
) {
264+
return generator.name;
265+
}
266+
}
267+
throw new PluginError(name, `Cannot find prisma-client-js generator in the schema`);
227268
}
228269

229270
private async processClientTypes(prismaClientDir: string) {

packages/schema/src/plugins/enhancer/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PluginError, createProject, resolvePath, type PluginFunction } from '@zenstackhq/sdk';
1+
import { PluginError, RUNTIME_PACKAGE, createProject, resolvePath, type PluginFunction } from '@zenstackhq/sdk';
22
import path from 'path';
33
import { getDefaultOutputFolder } from '../plugin-utils';
44
import { EnhancerGenerator } from './enhance';
@@ -31,7 +31,7 @@ const run: PluginFunction = async (model, options, _dmmf, globalOptions) => {
3131
// resolve it relative to the schema path
3232
prismaClientPath = path.relative(path.dirname(options.schemaPath), prismaClientPathAbs);
3333
} else {
34-
prismaClientPath = `.zenstack/models`;
34+
prismaClientPath = `${RUNTIME_PACKAGE}/models`;
3535
}
3636
}
3737

packages/schema/src/plugins/zod/generator.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
PluginGlobalOptions,
33
PluginOptions,
4+
RUNTIME_PACKAGE,
45
ensureEmptyDir,
56
getDataModels,
67
hasAttribute,
@@ -279,7 +280,7 @@ export class ZodSchemaGenerator {
279280
}
280281
}
281282
if (importEnums.size > 0) {
282-
const prismaImport = getPrismaClientImportSpec(path.join(output, 'models'), this.options);
283+
const prismaImport = computePrismaClientImport(path.join(output, 'models'), this.options);
283284
writer.writeLine(`import { ${[...importEnums].join(', ')} } from '${prismaImport}';`);
284285
}
285286

@@ -581,3 +582,14 @@ export const ${upperCaseFirst(model.name)}UpdateSchema = ${updateSchema};
581582
return `${schema}.passthrough()`;
582583
}
583584
}
585+
586+
export function computePrismaClientImport(importingFrom: string, options: PluginOptions) {
587+
let importPath = getPrismaClientImportSpec(importingFrom, options);
588+
if (importPath.startsWith(RUNTIME_PACKAGE) && !options.output) {
589+
// default import from `@zenstackhq/runtime` and this plugin is generating
590+
// into default location, we should correct the prisma client import into a
591+
// importing from `.zenstack` to avoid cyclic dependencies with runtime
592+
importPath = importPath.replace(RUNTIME_PACKAGE, '.zenstack');
593+
}
594+
return importPath;
595+
}

packages/schema/src/plugins/zod/transformer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* eslint-disable @typescript-eslint/ban-ts-comment */
22
import { indentString, type PluginOptions } from '@zenstackhq/sdk';
33
import { checkModelHasModelRelation, findModelByName, isAggregateInputType } from '@zenstackhq/sdk/dmmf-helpers';
4-
import { getPrismaClientImportSpec, type DMMF as PrismaDMMF } from '@zenstackhq/sdk/prisma';
4+
import { type DMMF as PrismaDMMF } from '@zenstackhq/sdk/prisma';
55
import path from 'path';
66
import type { Project, SourceFile } from 'ts-morph';
77
import { upperCaseFirst } from 'upper-case-first';
8+
import { computePrismaClientImport } from './generator';
89
import { AggregateOperationSupport, TransformerParams } from './types';
910

1011
export default class Transformer {
@@ -290,7 +291,7 @@ export const ${this.name}ObjectSchema: SchemaType = ${schema} as SchemaType;`;
290291
}
291292

292293
generateImportPrismaStatement(options: PluginOptions) {
293-
const prismaClientImportPath = getPrismaClientImportSpec(
294+
const prismaClientImportPath = computePrismaClientImport(
294295
path.resolve(Transformer.outputPath, './objects'),
295296
options
296297
);

0 commit comments

Comments
 (0)