@zenstackhq/cli 3.0.0-alpha.2 → 3.0.0-alpha.21
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.
- package/.turbo/turbo-build.log +22 -23
- package/bin/cli +1 -1
- package/dist/index.cjs +244 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +245 -179
- package/dist/index.js.map +1 -1
- package/eslint.config.js +4 -0
- package/package.json +15 -16
- package/src/actions/action-utils.ts +81 -10
- package/src/actions/db.ts +30 -29
- package/src/actions/generate.ts +37 -22
- package/src/actions/index.ts +2 -1
- package/src/actions/info.ts +9 -18
- package/src/actions/init.ts +6 -27
- package/src/actions/migrate.ts +61 -63
- package/src/actions/templates.ts +7 -1
- package/src/actions/validate.ts +22 -0
- package/src/index.ts +45 -41
- package/src/utils/exec-utils.ts +2 -5
- package/src/utils/version-utils.ts +9 -9
- package/test/db.test.ts +18 -0
- package/test/generate.test.ts +59 -0
- package/test/init.test.ts +13 -0
- package/test/migrate.test.ts +41 -0
- package/test/ts-schema-gen.test.ts +180 -2
- package/test/utils.ts +23 -0
- package/test/validate.test.ts +101 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +1 -1
- package/.turbo/turbo-lint.log +0 -18
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ExpressionUtils } from '@zenstackhq/runtime/schema';
|
|
2
|
-
import { generateTsSchema } from '@zenstackhq/testtools';
|
|
2
|
+
import { createTestProject, generateTsSchema, generateTsSchemaInPlace } from '@zenstackhq/testtools';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
3
5
|
import { describe, expect, it } from 'vitest';
|
|
4
6
|
|
|
5
7
|
describe('TypeScript schema generation tests', () => {
|
|
@@ -27,7 +29,6 @@ model Post {
|
|
|
27
29
|
|
|
28
30
|
expect(schema.provider).toMatchObject({
|
|
29
31
|
type: 'sqlite',
|
|
30
|
-
dialectConfigProvider: expect.any(Function),
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
expect(schema.models).toMatchObject({
|
|
@@ -182,4 +183,181 @@ model Post {
|
|
|
182
183
|
},
|
|
183
184
|
});
|
|
184
185
|
});
|
|
186
|
+
|
|
187
|
+
it('merges fields and attributes from mixins', async () => {
|
|
188
|
+
const { schema } = await generateTsSchema(`
|
|
189
|
+
type Timestamped {
|
|
190
|
+
createdAt DateTime @default(now())
|
|
191
|
+
updatedAt DateTime @updatedAt
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
type Named {
|
|
195
|
+
name String
|
|
196
|
+
@@unique([name])
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
model User with Timestamped Named {
|
|
200
|
+
id String @id @default(uuid())
|
|
201
|
+
email String @unique
|
|
202
|
+
}
|
|
203
|
+
`);
|
|
204
|
+
expect(schema).toMatchObject({
|
|
205
|
+
models: {
|
|
206
|
+
User: {
|
|
207
|
+
fields: {
|
|
208
|
+
id: { type: 'String' },
|
|
209
|
+
email: { type: 'String' },
|
|
210
|
+
createdAt: {
|
|
211
|
+
type: 'DateTime',
|
|
212
|
+
default: expect.objectContaining({ function: 'now', kind: 'call' }),
|
|
213
|
+
},
|
|
214
|
+
updatedAt: { type: 'DateTime', updatedAt: true },
|
|
215
|
+
name: { type: 'String' },
|
|
216
|
+
},
|
|
217
|
+
uniqueFields: expect.objectContaining({
|
|
218
|
+
name: { type: 'String' },
|
|
219
|
+
}),
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('generates type definitions', async () => {
|
|
226
|
+
const { schema } = await generateTsSchema(`
|
|
227
|
+
type Base {
|
|
228
|
+
name String
|
|
229
|
+
@@meta('foo', 'bar')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
type Address with Base {
|
|
233
|
+
street String
|
|
234
|
+
city String
|
|
235
|
+
}
|
|
236
|
+
`);
|
|
237
|
+
expect(schema).toMatchObject({
|
|
238
|
+
typeDefs: {
|
|
239
|
+
Base: {
|
|
240
|
+
fields: {
|
|
241
|
+
name: { type: 'String' },
|
|
242
|
+
},
|
|
243
|
+
attributes: [
|
|
244
|
+
{
|
|
245
|
+
name: '@@meta',
|
|
246
|
+
args: [
|
|
247
|
+
{ name: 'name', value: { kind: 'literal', value: 'foo' } },
|
|
248
|
+
{ name: 'value', value: { kind: 'literal', value: 'bar' } },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
Address: {
|
|
254
|
+
fields: {
|
|
255
|
+
street: { type: 'String' },
|
|
256
|
+
city: { type: 'String' },
|
|
257
|
+
},
|
|
258
|
+
attributes: [
|
|
259
|
+
{
|
|
260
|
+
name: '@@meta',
|
|
261
|
+
args: [
|
|
262
|
+
{ name: 'name', value: { kind: 'literal', value: 'foo' } },
|
|
263
|
+
{ name: 'value', value: { kind: 'literal', value: 'bar' } },
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('merges fields and attributes from base models', async () => {
|
|
273
|
+
const { schema } = await generateTsSchema(`
|
|
274
|
+
model Base {
|
|
275
|
+
id String @id @default(uuid())
|
|
276
|
+
createdAt DateTime @default(now())
|
|
277
|
+
updatedAt DateTime @updatedAt
|
|
278
|
+
type String
|
|
279
|
+
@@delegate(type)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
model User extends Base {
|
|
283
|
+
email String @unique
|
|
284
|
+
}
|
|
285
|
+
`);
|
|
286
|
+
expect(schema).toMatchObject({
|
|
287
|
+
models: {
|
|
288
|
+
Base: {
|
|
289
|
+
fields: {
|
|
290
|
+
id: {
|
|
291
|
+
type: 'String',
|
|
292
|
+
id: true,
|
|
293
|
+
default: expect.objectContaining({ function: 'uuid', kind: 'call' }),
|
|
294
|
+
},
|
|
295
|
+
createdAt: {
|
|
296
|
+
type: 'DateTime',
|
|
297
|
+
default: expect.objectContaining({ function: 'now', kind: 'call' }),
|
|
298
|
+
},
|
|
299
|
+
updatedAt: { type: 'DateTime', updatedAt: true },
|
|
300
|
+
type: { type: 'String' },
|
|
301
|
+
},
|
|
302
|
+
attributes: [
|
|
303
|
+
{
|
|
304
|
+
name: '@@delegate',
|
|
305
|
+
args: [{ name: 'discriminator', value: { kind: 'field', field: 'type' } }],
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
isDelegate: true,
|
|
309
|
+
},
|
|
310
|
+
User: {
|
|
311
|
+
baseModel: 'Base',
|
|
312
|
+
fields: {
|
|
313
|
+
id: { type: 'String' },
|
|
314
|
+
createdAt: {
|
|
315
|
+
type: 'DateTime',
|
|
316
|
+
default: expect.objectContaining({ function: 'now', kind: 'call' }),
|
|
317
|
+
originModel: 'Base',
|
|
318
|
+
},
|
|
319
|
+
updatedAt: { type: 'DateTime', updatedAt: true, originModel: 'Base' },
|
|
320
|
+
type: { type: 'String', originModel: 'Base' },
|
|
321
|
+
email: { type: 'String' },
|
|
322
|
+
},
|
|
323
|
+
uniqueFields: expect.objectContaining({
|
|
324
|
+
email: { type: 'String' },
|
|
325
|
+
}),
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('merges all declarations from imported modules', async () => {
|
|
332
|
+
const workDir = createTestProject();
|
|
333
|
+
fs.writeFileSync(
|
|
334
|
+
path.join(workDir, 'a.zmodel'),
|
|
335
|
+
`
|
|
336
|
+
enum Role {
|
|
337
|
+
Admin
|
|
338
|
+
User
|
|
339
|
+
}
|
|
340
|
+
`,
|
|
341
|
+
);
|
|
342
|
+
fs.writeFileSync(
|
|
343
|
+
path.join(workDir, 'b.zmodel'),
|
|
344
|
+
`
|
|
345
|
+
import './a'
|
|
346
|
+
|
|
347
|
+
datasource db {
|
|
348
|
+
provider = 'sqlite'
|
|
349
|
+
url = 'file:./test.db'
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
model User {
|
|
353
|
+
id Int @id
|
|
354
|
+
role Role
|
|
355
|
+
}
|
|
356
|
+
`,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const { schema } = await generateTsSchemaInPlace(path.join(workDir, 'b.zmodel'));
|
|
360
|
+
expect(schema.enums).toMatchObject({ Role: expect.any(Object) });
|
|
361
|
+
expect(schema.models).toMatchObject({ User: expect.any(Object) });
|
|
362
|
+
});
|
|
185
363
|
});
|
package/test/utils.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createTestProject } from '@zenstackhq/testtools';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const ZMODEL_PRELUDE = `datasource db {
|
|
7
|
+
provider = "sqlite"
|
|
8
|
+
url = "file:./dev.db"
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export function createProject(zmodel: string, addPrelude = true) {
|
|
13
|
+
const workDir = createTestProject();
|
|
14
|
+
fs.mkdirSync(path.join(workDir, 'zenstack'), { recursive: true });
|
|
15
|
+
const schemaPath = path.join(workDir, 'zenstack/schema.zmodel');
|
|
16
|
+
fs.writeFileSync(schemaPath, addPrelude ? `${ZMODEL_PRELUDE}\n\n${zmodel}` : zmodel);
|
|
17
|
+
return workDir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function runCli(command: string, cwd: string) {
|
|
21
|
+
const cli = path.join(__dirname, '../dist/index.js');
|
|
22
|
+
execSync(`node ${cli} ${command}`, { cwd });
|
|
23
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { createProject, runCli } from './utils';
|
|
5
|
+
|
|
6
|
+
const validModel = `
|
|
7
|
+
model User {
|
|
8
|
+
id String @id @default(cuid())
|
|
9
|
+
email String @unique
|
|
10
|
+
name String?
|
|
11
|
+
posts Post[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
model Post {
|
|
15
|
+
id String @id @default(cuid())
|
|
16
|
+
title String
|
|
17
|
+
content String?
|
|
18
|
+
author User @relation(fields: [authorId], references: [id])
|
|
19
|
+
authorId String
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const invalidModel = `
|
|
24
|
+
model User {
|
|
25
|
+
id String @id @default(cuid())
|
|
26
|
+
email String @unique
|
|
27
|
+
posts Post[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
model Post {
|
|
31
|
+
id String @id @default(cuid())
|
|
32
|
+
title String
|
|
33
|
+
author User @relation(fields: [authorId], references: [id])
|
|
34
|
+
// Missing authorId field - should cause validation error
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
describe('CLI validate command test', () => {
|
|
39
|
+
it('should validate a valid schema successfully', () => {
|
|
40
|
+
const workDir = createProject(validModel);
|
|
41
|
+
|
|
42
|
+
// Should not throw an error
|
|
43
|
+
expect(() => runCli('validate', workDir)).not.toThrow();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should fail validation for invalid schema', () => {
|
|
47
|
+
const workDir = createProject(invalidModel);
|
|
48
|
+
|
|
49
|
+
// Should throw an error due to validation failure
|
|
50
|
+
expect(() => runCli('validate', workDir)).toThrow();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should respect custom schema location', () => {
|
|
54
|
+
const workDir = createProject(validModel);
|
|
55
|
+
fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'zenstack/custom.zmodel'));
|
|
56
|
+
|
|
57
|
+
// Should not throw an error when using custom schema path
|
|
58
|
+
expect(() => runCli('validate --schema ./zenstack/custom.zmodel', workDir)).not.toThrow();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should fail when schema file does not exist', () => {
|
|
62
|
+
const workDir = createProject(validModel);
|
|
63
|
+
|
|
64
|
+
// Should throw an error when schema file doesn't exist
|
|
65
|
+
expect(() => runCli('validate --schema ./nonexistent.zmodel', workDir)).toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should respect package.json config', () => {
|
|
69
|
+
const workDir = createProject(validModel);
|
|
70
|
+
fs.mkdirSync(path.join(workDir, 'foo'));
|
|
71
|
+
fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'foo/schema.zmodel'));
|
|
72
|
+
fs.rmdirSync(path.join(workDir, 'zenstack'));
|
|
73
|
+
|
|
74
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
|
|
75
|
+
pkgJson.zenstack = {
|
|
76
|
+
schema: './foo/schema.zmodel',
|
|
77
|
+
};
|
|
78
|
+
fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
|
79
|
+
|
|
80
|
+
// Should not throw an error when using package.json config
|
|
81
|
+
expect(() => runCli('validate', workDir)).not.toThrow();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should validate schema with syntax errors', () => {
|
|
85
|
+
const modelWithSyntaxError = `
|
|
86
|
+
datasource db {
|
|
87
|
+
provider = "sqlite"
|
|
88
|
+
url = "file:./dev.db"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
model User {
|
|
92
|
+
id String @id @default(cuid())
|
|
93
|
+
email String @unique
|
|
94
|
+
// Missing closing brace - syntax error
|
|
95
|
+
`;
|
|
96
|
+
const workDir = createProject(modelWithSyntaxError, false);
|
|
97
|
+
|
|
98
|
+
// Should throw an error due to syntax error
|
|
99
|
+
expect(() => runCli('validate', workDir)).toThrow();
|
|
100
|
+
});
|
|
101
|
+
});
|
package/tsconfig.json
CHANGED
package/vitest.config.ts
CHANGED
package/.turbo/turbo-lint.log
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> zenstack@3.0.0-alpha.1 lint /Users/yiming/git/zenstack/zenstack-v3/packages/cli
|
|
4
|
-
> eslint src --ext ts
|
|
5
|
-
|
|
6
|
-
=============
|
|
7
|
-
|
|
8
|
-
WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.
|
|
9
|
-
|
|
10
|
-
You may find that it works just fine, or you may not.
|
|
11
|
-
|
|
12
|
-
SUPPORTED TYPESCRIPT VERSIONS: >=4.7.4 <5.5.0
|
|
13
|
-
|
|
14
|
-
YOUR TYPESCRIPT VERSION: 5.7.3
|
|
15
|
-
|
|
16
|
-
Please only submit bug reports when using the officially supported version.
|
|
17
|
-
|
|
18
|
-
=============
|