diff --git a/.gitignore b/.gitignore index e4faa8489..9b5f3d7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist .env.local .npmcache coverage +.build diff --git a/packages/language/package.json b/packages/language/package.json index a3e00f11f..aa2e0acaa 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -9,7 +9,7 @@ "generate": "langium generate", "watch": "concurrently \"langium generate --watch\" \"tsc --watch\"", "lint": "eslint src --ext ts", - "build": "pnpm lint && pnpm clean && pnpm generate && tsc && copyfiles -F ./README.md ./LICENSE ./package.json dist", + "build": "pnpm lint && pnpm clean && pnpm generate && tsc && copyfiles -F ./README.md ./LICENSE ./package.json dist && pnpm pack dist --pack-destination '../../../.build'", "prepublishOnly": "pnpm build", "publish-dev": "pnpm build && pnpm publish --tag dev" }, diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index 796b74693..639d2f341 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -14,7 +14,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && copyfiles -u 1 ./src/plugin.zmodel dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && copyfiles -u 1 ./src/plugin.zmodel dist && pnpm pack dist --pack-destination '../../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index aa7e4c806..69e032f2b 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -10,7 +10,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist && pnpm pack dist --pack-destination '../../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 45e20b4cd..0de68f8ec 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -10,7 +10,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist && pnpm pack dist --pack-destination '../../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index 628d92dd6..5a65640b3 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -10,7 +10,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist && pnpm pack dist --pack-destination '../../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 612e57b5e..0f58dc5e3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -9,7 +9,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 'res/**/*' dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 'res/**/*' dist && pnpm pack dist --pack-destination '../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build", diff --git a/packages/schema/package.json b/packages/schema/package.json index 4fdc36d12..9558b6f59 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -69,7 +69,7 @@ "vscode:prepublish": "pnpm bundle", "vscode:package": "vsce package --no-dependencies", "clean": "rimraf bundle dist", - "build": "pnpm clean && pnpm lint && tsc && copyfiles -F \"bin/*\" dist && copyfiles ./README-global.md ./LICENSE ./package.json dist && renamer --replace \"README.md\" dist/README-global.md && copyfiles -u 1 \"src/res/*\" dist && node build/post-build.js", + "build": "pnpm clean && pnpm lint && tsc && copyfiles -F \"bin/*\" dist && copyfiles ./README-global.md ./LICENSE ./package.json dist && renamer --replace \"README.md\" dist/README-global.md && copyfiles -u 1 \"src/res/*\" dist && node build/post-build.js && pnpm pack dist --pack-destination '../../../.build'", "bundle": "pnpm clean && pnpm lint && node build/bundle.js --minify", "watch": "tsc --watch", "lint": "eslint src tests --ext ts", @@ -127,7 +127,6 @@ "@typescript-eslint/parser": "^5.42.0", "@vscode/vsce": "^2.19.0", "@zenstackhq/runtime": "workspace:*", - "@zenstackhq/testtools": "workspace:*", "concurrently": "^7.4.0", "copyfiles": "^2.4.1", "dotenv": "^16.0.3", diff --git a/packages/schema/src/plugins/plugin-utils.ts b/packages/schema/src/plugins/plugin-utils.ts index ad4b3d21a..9245eed3a 100644 --- a/packages/schema/src/plugins/plugin-utils.ts +++ b/packages/schema/src/plugins/plugin-utils.ts @@ -40,7 +40,7 @@ export function getDefaultOutputFolder() { // Find the real runtime module path, it might be a symlink in pnpm let runtimeModulePath = require.resolve('@zenstackhq/runtime'); - if (process.env.NODE_ENV === 'test') { + if (process.env.ZENSTACK_TEST === '1') { // handling the case when running as tests, resolve relative to CWD runtimeModulePath = path.resolve(path.join(process.cwd(), 'node_modules', '@zenstackhq', 'runtime')); } diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index 64bfa7049..5017adbb6 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -253,10 +253,9 @@ export default class Transformer { if (isAggregateInputType(name)) { name = `${name}Type`; } - const end = `export const ${this.name}ObjectSchema = Schema`; - return `const Schema: z.ZodType "'" + f + "'").join( - '|' - )}>> = ${schema};\n\n ${end}`; + return `export const ${this.name}ObjectSchema: z.ZodType "'" + f + "'" + ).join('|')}>> = ${schema};`; } addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }) { @@ -390,14 +389,21 @@ export default class Transformer { const { selectImport, includeImport, selectZodSchemaLineLazy, includeZodSchemaLineLazy } = this.resolveSelectIncludeImportAndZodSchemaLine(model); - let imports = [`import { z } from 'zod'`, selectImport, includeImport]; + let imports = [ + `import { z } from 'zod'`, + this.generateImportPrismaStatement(), + selectImport, + includeImport, + ]; let codeBody = ''; + const operations: [string, string][] = []; if (findUnique) { imports.push( `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'` ); codeBody += `findUnique: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; + operations.push(['findUnique', modelName]); } if (findFirst) { @@ -412,6 +418,7 @@ export default class Transformer { codeBody += `findFirst: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${upperCaseFirst( modelName )}ScalarFieldEnumSchema).optional() }),`; + operations.push(['findFirst', modelName]); } if (findMany) { @@ -426,6 +433,7 @@ export default class Transformer { codeBody += `findMany: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${upperCaseFirst( modelName )}ScalarFieldEnumSchema).optional() }),`; + operations.push(['findMany', modelName]); } if (createOne) { @@ -434,6 +442,7 @@ export default class Transformer { `import { ${modelName}UncheckedCreateInputObjectSchema } from '../objects/${modelName}UncheckedCreateInput.schema'` ); codeBody += `create: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: z.union([${modelName}CreateInputObjectSchema, ${modelName}UncheckedCreateInputObjectSchema]) }),`; + operations.push(['create', modelName]); } if (createMany) { @@ -441,6 +450,7 @@ export default class Transformer { `import { ${modelName}CreateManyInputObjectSchema } from '../objects/${modelName}CreateManyInput.schema'` ); codeBody += `createMany: z.object({ data: z.union([${modelName}CreateManyInputObjectSchema, z.array(${modelName}CreateManyInputObjectSchema)]) }),`; + operations.push(['createMany', modelName]); } if (deleteOne) { @@ -448,6 +458,7 @@ export default class Transformer { `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'` ); codeBody += `'delete': z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; + operations.push(['delete', modelName]); } if (deleteMany) { @@ -455,6 +466,7 @@ export default class Transformer { `import { ${modelName}WhereInputObjectSchema } from '../objects/${modelName}WhereInput.schema'` ); codeBody += `deleteMany: z.object({ where: ${modelName}WhereInputObjectSchema.optional() }),`; + operations.push(['deleteMany', modelName]); } if (updateOne) { @@ -464,6 +476,7 @@ export default class Transformer { `import { ${modelName}WhereUniqueInputObjectSchema } from '../objects/${modelName}WhereUniqueInput.schema'` ); codeBody += `update: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: z.union([${modelName}UpdateInputObjectSchema, ${modelName}UncheckedUpdateInputObjectSchema]), where: ${modelName}WhereUniqueInputObjectSchema }),`; + operations.push(['update', modelName]); } if (updateMany) { @@ -473,6 +486,7 @@ export default class Transformer { `import { ${modelName}WhereInputObjectSchema } from '../objects/${modelName}WhereInput.schema'` ); codeBody += `updateMany: z.object({ data: z.union([${modelName}UpdateManyMutationInputObjectSchema, ${modelName}UncheckedUpdateManyInputObjectSchema]), where: ${modelName}WhereInputObjectSchema.optional() }),`; + operations.push(['updateMany', modelName]); } if (upsertOne) { @@ -484,6 +498,7 @@ export default class Transformer { `import { ${modelName}UncheckedUpdateInputObjectSchema } from '../objects/${modelName}UncheckedUpdateInput.schema'` ); codeBody += `upsert: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema, create: z.union([${modelName}CreateInputObjectSchema, ${modelName}UncheckedCreateInputObjectSchema]), update: z.union([${modelName}UpdateInputObjectSchema, ${modelName}UncheckedUpdateInputObjectSchema]) }),`; + operations.push(['upsert', modelName]); } const aggregateOperations = []; @@ -532,6 +547,7 @@ export default class Transformer { codeBody += `aggregate: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), ${aggregateOperations.join( ', ' )} }),`; + operations.push(['aggregate', modelNameVar]); } if (groupBy) { @@ -546,6 +562,7 @@ export default class Transformer { codeBody += `groupBy: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithAggregationInputObjectSchema, ${modelName}OrderByWithAggregationInputObjectSchema.array()]).optional(), having: ${modelName}ScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(${upperCaseFirst( modelName )}ScalarFieldEnumSchema), ${aggregateOperations.join(', ')} }),`; + operations.push(['groupBy', modelNameVar]); } imports = [...new Set(imports)]; @@ -555,7 +572,13 @@ export default class Transformer { /* eslint-disable */ ${imports.join(';\n')} - export const ${modelName}InputSchema = { + type ${modelName}InputSchemaType = { +${operations + .map((op) => indentString(`${op[0]}: z.ZodType`, 4)) + .join(',\n')} + } + + export const ${modelName}InputSchema: ${modelName}InputSchemaType = { ${indentString(codeBody, 4)} }; `; diff --git a/packages/schema/src/utils/pkg-utils.ts b/packages/schema/src/utils/pkg-utils.ts index 4fe73d145..0d3633cd7 100644 --- a/packages/schema/src/utils/pkg-utils.ts +++ b/packages/schema/src/utils/pkg-utils.ts @@ -47,9 +47,7 @@ export function installPackage( switch (manager) { case 'yarn': execSync( - `yarn --cwd "${projectPath}" add ${exactVersion ? '--exact' : ''} ${pkg}@${tag} ${ - dev ? ' --dev' : '' - } --ignore-engines` + `yarn --cwd "${projectPath}" add ${exactVersion ? '--exact' : ''} ${pkg}@${tag} ${dev ? ' --dev' : ''}` ); break; diff --git a/packages/schema/tests/cli/plugins.test.ts b/packages/schema/tests/cli/plugins.test.ts deleted file mode 100644 index 6bd5d54d7..000000000 --- a/packages/schema/tests/cli/plugins.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -/// - -import { getWorkspaceNpmCacheFolder, run } from '@zenstackhq/testtools'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as tmp from 'tmp'; -import { createProgram } from '../../src/cli'; - -describe('CLI Plugins Tests', () => { - let origDir: string; - - beforeEach(() => { - origDir = process.cwd(); - const r = tmp.dirSync({ unsafeCleanup: true }); - console.log(`Project dir: ${r.name}`); - process.chdir(r.name); - }); - - afterEach(() => { - process.chdir(origDir); - }); - - function createNpmrc() { - fs.writeFileSync('.npmrc', `cache=${getWorkspaceNpmCacheFolder(__dirname)}`); - } - - async function initProject() { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); - createNpmrc(); - const program = createProgram(); - - // typescript - run('npm install -D typescript'); - run('npx tsc --init'); - - // deps - run('npm install react swr @tanstack/react-query @trpc/server @types/react'); - - await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); - return program; - } - - const plugins = [ - `plugin prisma { - provider = '@core/prisma' - output = 'prisma/my.prisma' - generateClient = true - }`, - `plugin meta { - provider = '@core/model-meta' - output = 'model-meta' - } - `, - `plugin policy { - provider = '@core/access-policy' - output = 'policy' - }`, - `plugin zod { - provider = '@core/zod' - output = 'zod' - }`, - `plugin tanstack { - provider = '${path.join(__dirname, '../../../plugins/tanstack-query/dist')}' - output = 'lib/tanstack-query' - target = 'react' - }`, - `plugin swr { - provider = '${path.join(__dirname, '../../../plugins/swr/dist')}' - output = 'lib/swr' - }`, - `plugin trpc { - provider = '${path.join(__dirname, '../../../plugins/trpc/dist')}' - output = 'lib/trpc' - zodSchemasImport = '../../../zod' - }`, - `plugin openapi { - provider = '${path.join(__dirname, '../../../plugins/openapi/dist')}' - output = 'myapi.yaml' - specVersion = '3.0.0' - title = 'My Awesome API' - version = '1.0.0' - description = 'awesome api' - prefix = '/myapi' - securitySchemes = { - myBasic: { type: 'http', scheme: 'basic' }, - myBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, - myApiKey: { type: 'apiKey', in: 'header', name: 'X-API-KEY' } - } - }`, - ]; - - const BASE_MODEL = ` - datasource db { - provider = 'postgresql' - url = env('DATABASE_URL') - } - - enum Role { - USER - ADMIN - } - - model User { - id String @id @default(cuid()) - email String @unique @email - role Role @default(USER) - posts Post[] - @@allow('create', true) - @@allow('all', auth() == this || role == ADMIN) - } - - model Post { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - published Boolean @default(false) - author User? @relation(fields: [authorId], references: [id]) - authorId String? - - @@allow('read', auth() != null && published) - @@allow('all', auth() == author) - } - `; - - it('all plugins standard prisma client output path', async () => { - const program = await initProject(); - - let schemaContent = ` -generator client { - provider = "prisma-client-js" -} - -${BASE_MODEL} - `; - for (const plugin of plugins) { - schemaContent += `\n${plugin}`; - } - fs.writeFileSync('schema.zmodel', schemaContent); - - await program.parseAsync(['generate', '--no-dependency-check'], { from: 'user' }); - - // compile - run('npx tsc'); - }); - - it('all plugins custom prisma client output path', async () => { - const program = await initProject(); - - let schemaContent = ` -generator client { - provider = "prisma-client-js" - output = "foo/bar" -} - -${BASE_MODEL} -`; - for (const plugin of plugins) { - schemaContent += `\n${plugin}`; - } - fs.writeFileSync('schema.zmodel', schemaContent); - - await program.parseAsync(['generate', '--no-dependency-check'], { from: 'user' }); - - // compile - run('npx tsc'); - }); - - it('all plugins absolute prisma client output path', async () => { - const { name: output } = tmp.dirSync({ unsafeCleanup: true }); - console.log('Output prisma client to:', output); - - const program = await initProject(); - - let schemaContent = ` -generator client { - provider = "prisma-client-js" - output = "${output}" -} - -${BASE_MODEL} -`; - for (const plugin of plugins) { - schemaContent += `\n${plugin}`; - } - fs.writeFileSync('schema.zmodel', schemaContent); - - await program.parseAsync(['generate', '--no-dependency-check'], { from: 'user' }); - - // compile - run('npx tsc'); - }); -}); diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6f10158ec..c91cc1fbb 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -6,7 +6,7 @@ "scripts": { "clean": "rimraf dist", "lint": "eslint src --ext ts", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./LICENSE ./README.md dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./LICENSE ./README.md dist && pnpm pack dist --pack-destination '../../../.build'", "watch": "tsc --watch", "prepublishOnly": "pnpm build", "publish-dev": "pnpm build && pnpm publish --tag dev" diff --git a/packages/server/package.json b/packages/server/package.json index 38e929bb7..33c249173 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -6,7 +6,7 @@ "homepage": "https://zenstack.dev", "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../.build'", "watch": "tsc --watch", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index b74d7c7bd..04d3f35cc 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -10,7 +10,7 @@ "scripts": { "clean": "rimraf dist", "lint": "eslint src --ext ts", - "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./LICENSE ./README.md dist && copyfiles -u 1 src/package.template.json src/.npmrc.template dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./LICENSE ./README.md dist && copyfiles -u 1 src/package.template.json src/.npmrc.template dist && pnpm pack dist --pack-destination '../../../.build'", "watch": "tsc --watch", "prepublishOnly": "pnpm build" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ab2d41cb..1d9fde4de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -561,9 +561,6 @@ importers: '@zenstackhq/runtime': specifier: workspace:* version: link:../runtime/dist - '@zenstackhq/testtools': - specifier: workspace:* - version: link:../testtools/dist concurrently: specifier: ^7.4.0 version: 7.4.0 @@ -796,6 +793,9 @@ importers: '@types/node': specifier: ^14.18.29 version: 14.18.29 + '@zenstackhq/sdk': + specifier: workspace:* + version: link:../../packages/sdk/dist '@zenstackhq/testtools': specifier: workspace:* version: link:../../packages/testtools/dist diff --git a/tests/integration/package.json b/tests/integration/package.json index 52221a2b4..333edde81 100644 --- a/tests/integration/package.json +++ b/tests/integration/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "lint": "eslint . --ext .ts", - "test": "jest" + "test": "ZENSTACK_TEST=1 jest" }, "keywords": [], "author": "", @@ -38,6 +38,7 @@ "dependencies": { "@prisma/client": "^4.7.0", "@types/node": "^14.18.29", + "@zenstackhq/sdk": "workspace:*", "@zenstackhq/testtools": "workspace:*", "bcryptjs": "^2.4.3", "decimal.js": "^10.4.2", diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index d0b7f99b5..afdbe9543 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -9,9 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/client": "^4.7.1", + "@prisma/client": "^4.16.2", "@zenstackhq/runtime": "file:../../../packages/runtime/dist", - "prisma": "~4.7.0", + "prisma": "^4.16.2", "react": "^18.2.0", "swr": "^1.3.0", "typescript": "^4.9.3", @@ -145,6 +145,7 @@ "zod-validation-error": "^0.2.1" }, "devDependencies": { + "@prisma/client": "^4.0.0", "@types/bcryptjs": "^2.4.2", "@types/jest": "^29.5.0", "@types/lower-case-first": "^1.0.1", @@ -153,9 +154,6 @@ "copyfiles": "^2.4.1", "rimraf": "^3.0.2", "typescript": "^4.9.3" - }, - "peerDependencies": { - "@prisma/client": "^4.0.0" } }, "../../../packages/schema/dist": { @@ -215,7 +213,6 @@ "@typescript-eslint/parser": "^5.42.0", "@vscode/vsce": "^2.19.0", "@zenstackhq/runtime": "workspace:*", - "@zenstackhq/testtools": "workspace:*", "concurrently": "^7.4.0", "copyfiles": "^2.4.1", "dotenv": "^16.0.3", @@ -235,17 +232,15 @@ }, "engines": { "vscode": "^1.56.0" - }, - "peerDependencies": { - "prisma": "^4.0.0" } }, "node_modules/@prisma/client": { - "version": "4.7.1", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz", + "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { - "@prisma/engines-version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" }, "engines": { "node": ">=14.17" @@ -260,13 +255,15 @@ } }, "node_modules/@prisma/engines": { - "version": "4.7.1", - "hasInstallScript": true, - "license": "Apache-2.0" + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz", + "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==", + "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c", - "license": "Apache-2.0" + "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz", + "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==" }, "node_modules/@zenstackhq/runtime": { "resolved": "../../../packages/runtime/dist", @@ -287,11 +284,12 @@ } }, "node_modules/prisma": { - "version": "4.7.1", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz", + "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "4.7.1" + "@prisma/engines": "4.16.2" }, "bin": { "prisma": "build/index.js", @@ -344,21 +342,28 @@ }, "dependencies": { "@prisma/client": { - "version": "4.7.1", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz", + "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==", "requires": { - "@prisma/engines-version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" } }, "@prisma/engines": { - "version": "4.7.1" + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz", + "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==" }, "@prisma/engines-version": { - "version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz", + "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==" }, "@zenstackhq/runtime": { "version": "file:../../../packages/runtime/dist", "requires": { "@paralleldrive/cuid2": "^2.2.0", + "@prisma/client": "^4.0.0", "@types/bcryptjs": "^2.4.2", "@types/jest": "^29.5.0", "@types/lower-case-first": "^1.0.1", @@ -391,9 +396,11 @@ } }, "prisma": { - "version": "4.7.1", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz", + "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==", "requires": { - "@prisma/engines": "4.7.1" + "@prisma/engines": "4.16.2" } }, "react": { @@ -432,7 +439,6 @@ "@zenstackhq/language": "workspace:*", "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", - "@zenstackhq/testtools": "workspace:*", "async-exit-hook": "^2.0.1", "change-case": "^4.1.2", "colors": "1.4.0", diff --git a/tests/integration/test-run/package.json b/tests/integration/test-run/package.json index 522921dae..44210853d 100644 --- a/tests/integration/test-run/package.json +++ b/tests/integration/test-run/package.json @@ -10,9 +10,9 @@ "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "^4.7.1", + "@prisma/client": "^4.16.2", "@zenstackhq/runtime": "file:../../../packages/runtime/dist", - "prisma": "~4.7.0", + "prisma": "^4.16.2", "react": "^18.2.0", "swr": "^1.3.0", "typescript": "^4.9.3", diff --git a/packages/schema/tests/cli/command.test.ts b/tests/integration/tests/cli/command.test.ts similarity index 97% rename from packages/schema/tests/cli/command.test.ts rename to tests/integration/tests/cli/command.test.ts index 9ef4de1d0..ca1e533f9 100644 --- a/packages/schema/tests/cli/command.test.ts +++ b/tests/integration/tests/cli/command.test.ts @@ -5,8 +5,8 @@ import { getWorkspaceNpmCacheFolder } from '@zenstackhq/testtools'; import * as fs from 'fs'; import * as path from 'path'; import * as tmp from 'tmp'; -import { createProgram } from '../../src/cli'; -import { execSync } from '../../src/utils/exec-utils'; +import { createProgram } from '../../../../packages/schema/src/cli'; +import { execSync } from '../../../../packages/schema/src/utils/exec-utils'; describe('CLI Command Tests', () => { let origDir: string; diff --git a/packages/schema/tests/cli/config.test.ts b/tests/integration/tests/cli/config.test.ts similarity index 94% rename from packages/schema/tests/cli/config.test.ts rename to tests/integration/tests/cli/config.test.ts index 3a8b22b75..d63bfc718 100644 --- a/packages/schema/tests/cli/config.test.ts +++ b/tests/integration/tests/cli/config.test.ts @@ -3,9 +3,9 @@ import * as fs from 'fs'; import * as tmp from 'tmp'; -import { createProgram } from '../../src/cli'; -import { CliError } from '../../src/cli/cli-error'; -import { config } from '../../src/cli/config'; +import { createProgram } from '../../../../packages/schema/src/cli'; +import { CliError } from '../../../../packages/schema/src/cli/cli-error'; +import { config } from '../../../../packages/schema/src/cli/config'; import { GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from '@zenstackhq/sdk'; describe('CLI Config Tests', () => { diff --git a/tests/integration/tests/cli/plugins.test.ts b/tests/integration/tests/cli/plugins.test.ts new file mode 100644 index 000000000..11a7ee87c --- /dev/null +++ b/tests/integration/tests/cli/plugins.test.ts @@ -0,0 +1,281 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/// + +import { getWorkspaceNpmCacheFolder, run } from '@zenstackhq/testtools'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as tmp from 'tmp'; +import { createProgram } from '../../../../packages/schema/src/cli'; + +describe('CLI Plugins Tests', () => { + let origDir: string; + + beforeEach(() => { + origDir = process.cwd(); + }); + + afterEach(() => { + process.chdir(origDir); + }); + + function createNpmrc() { + fs.writeFileSync('.npmrc', `cache=${getWorkspaceNpmCacheFolder(__dirname)}`); + } + + const PACKAGE_MANAGERS = ['npm', 'pnpm', 'pnpm-workspace'] as const; + + function zenstackGenerate(pm: (typeof PACKAGE_MANAGERS)[number]) { + switch (pm) { + case 'npm': + run(`ZENSTACK_TEST=0 npx zenstack generate`); + break; + case 'pnpm': + case 'pnpm-workspace': + run(`ZENSTACK_TEST=0 pnpm zenstack generate`); + break; + } + } + + async function initProject(pm: (typeof PACKAGE_MANAGERS)[number] = 'npm') { + const r = tmp.dirSync({ unsafeCleanup: true }); + console.log(`Project dir: ${r.name}`); + process.chdir(r.name); + + createNpmrc(); + + // init project + switch (pm) { + case 'npm': + run('npm init -y'); + break; + // case 'yarn': + // run('yarn init'); + // break; + case 'pnpm': + case 'pnpm-workspace': + run('pnpm init'); + break; + } + + if (pm === 'pnpm-workspace') { + // create a package + fs.writeFileSync('pnpm-workspace.yaml', JSON.stringify({ packages: ['db'] })); + fs.mkdirSync('db'); + process.chdir('db'); + run('pnpm init'); + } + + // deps + const ver = require('../../../../packages/schema/package.json').version; + const depPkgs = [ + 'zod@3.21.1', + 'react', + 'swr', + '@tanstack/react-query', + '@trpc/server', + '@prisma/client', + `${path.join(__dirname, '../../../../.build/zenstackhq-language-' + ver + '.tgz')}`, + `${path.join(__dirname, '../../../../.build/zenstackhq-sdk-' + ver + '.tgz')}`, + `${path.join(__dirname, '../../../../.build/zenstackhq-runtime-' + ver + '.tgz')}`, + ]; + const deps = depPkgs.join(' '); + + const devDepPkgs = [ + 'typescript', + '@types/react', + 'prisma', + `${path.join(__dirname, '../../../../.build/zenstack-' + ver + '.tgz')}`, + `${path.join(__dirname, '../../../../.build/zenstackhq-tanstack-query-' + ver + '.tgz')}`, + `${path.join(__dirname, '../../../../.build/zenstackhq-swr-' + ver + '.tgz')}`, + `${path.join(__dirname, '../../../../.build/zenstackhq-trpc-' + ver + '.tgz')}`, + `${path.join(__dirname, '../../../../.build/zenstackhq-openapi-' + ver + '.tgz')}`, + ]; + const devDeps = devDepPkgs.join(' '); + + switch (pm) { + case 'npm': + run('npm install ' + deps); + run('npm install -D ' + devDeps); + break; + // case 'yarn': + // run('yarn add ' + deps); + // run('yarn add --dev ' + devDeps); + // break; + case 'pnpm': + case 'pnpm-workspace': + run('pnpm add ' + deps); + run('pnpm add -D ' + devDeps); + break; + } + + // init typescript project + fs.writeFileSync( + 'tsconfig.json', + JSON.stringify({ + compilerOptions: { + strict: true, + lib: ['esnext', 'dom'], + esModuleInterop: true, + }, + }) + ); + + return createProgram(); + } + + const plugins = [ + `plugin prisma { + provider = '@core/prisma' + output = 'prisma/my.prisma' + generateClient = true + }`, + `plugin meta { + provider = '@core/model-meta' + output = 'model-meta' + } + `, + `plugin policy { + provider = '@core/access-policy' + output = 'policy' + }`, + `plugin tanstack { + provider = '@zenstackhq/tanstack-query' + output = 'lib/tanstack-query' + target = 'react' + }`, + `plugin swr { + provider = '@zenstackhq/swr' + output = 'lib/swr' + }`, + `plugin trpc { + provider = '@zenstackhq/trpc' + output = 'lib/trpc' + }`, + `plugin openapi { + provider = '@zenstackhq/openapi' + output = 'myapi.yaml' + specVersion = '3.0.0' + title = 'My Awesome API' + version = '1.0.0' + description = 'awesome api' + prefix = '/myapi' + securitySchemes = { + myBasic: { type: 'http', scheme: 'basic' }, + myBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, + myApiKey: { type: 'apiKey', in: 'header', name: 'X-API-KEY' } + } + }`, + ]; + + const BASE_MODEL = ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + enum Role { + USER + ADMIN + } + + model User { + id String @id @default(cuid()) + email String @unique @email + role Role @default(USER) + posts Post[] + @@allow('create', true) + @@allow('all', auth() == this || role == ADMIN) + } + + model Post { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + published Boolean @default(false) + author User? @relation(fields: [authorId], references: [id]) + authorId String? + + @@allow('read', auth() != null && published) + @@allow('all', auth() == author) + } + `; + + it('all plugins standard prisma client output path', async () => { + for (const pm of PACKAGE_MANAGERS) { + console.log('[PACKAGE MANAGER]', pm); + await initProject(pm); + + let schemaContent = ` +generator client { + provider = "prisma-client-js" +} + +${BASE_MODEL} + `; + for (const plugin of plugins) { + schemaContent += `\n${plugin}`; + } + fs.writeFileSync('schema.zmodel', schemaContent); + + // generate + zenstackGenerate(pm); + + // compile + run('npx tsc'); + } + }); + + it('all plugins custom prisma client output path', async () => { + for (const pm of PACKAGE_MANAGERS) { + console.log('[PACKAGE MANAGER]', pm); + await initProject(pm); + + let schemaContent = ` +generator client { + provider = "prisma-client-js" + output = "foo/bar" +} + +${BASE_MODEL} +`; + for (const plugin of plugins) { + schemaContent += `\n${plugin}`; + } + fs.writeFileSync('schema.zmodel', schemaContent); + + // generate + zenstackGenerate(pm); + + // compile + run('npx tsc'); + } + }); + + it('all plugins absolute prisma client output path', async () => { + for (const pm of PACKAGE_MANAGERS) { + console.log('[PACKAGE MANAGER]', pm); + const { name: output } = tmp.dirSync({ unsafeCleanup: true }); + console.log('Output prisma client to:', output); + + await initProject(pm); + + let schemaContent = ` +generator client { + provider = "prisma-client-js" + output = "${output}" +} + +${BASE_MODEL} +`; + for (const plugin of plugins) { + schemaContent += `\n${plugin}`; + } + fs.writeFileSync('schema.zmodel', schemaContent); + + // generate + zenstackGenerate(pm); + + // compile + run('npx tsc'); + } + }); +}); diff --git a/packages/schema/tests/plugins/policy.test.ts b/tests/integration/tests/plugins/policy.test.ts similarity index 100% rename from packages/schema/tests/plugins/policy.test.ts rename to tests/integration/tests/plugins/policy.test.ts diff --git a/packages/schema/tests/plugins/prisma.test.ts b/tests/integration/tests/plugins/prisma.test.ts similarity index 100% rename from packages/schema/tests/plugins/prisma.test.ts rename to tests/integration/tests/plugins/prisma.test.ts diff --git a/packages/schema/tests/plugins/zod.test.ts b/tests/integration/tests/plugins/zod.test.ts similarity index 100% rename from packages/schema/tests/plugins/zod.test.ts rename to tests/integration/tests/plugins/zod.test.ts diff --git a/tests/integration/tsconfig.json b/tests/integration/tsconfig.json index 98869ecf1..babfdfdee 100644 --- a/tests/integration/tsconfig.json +++ b/tests/integration/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2016", + "target": "esnext", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true,