@zenstackhq/cli 3.0.0-alpha.0

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/src/index.ts ADDED
@@ -0,0 +1,138 @@
1
+ import { ZModelLanguageMetaData } from '@zenstackhq/language';
2
+ import colors from 'colors';
3
+ import { Command, Option } from 'commander';
4
+ import * as actions from './actions';
5
+ import { getVersion } from './utils/version-utils';
6
+
7
+ const generateAction = async (
8
+ options: Parameters<typeof actions.generate>[0]
9
+ ): Promise<void> => {
10
+ await actions.generate(options);
11
+ };
12
+
13
+ const migrateAction = async (command: string, options: any): Promise<void> => {
14
+ await actions.migrate(command, options);
15
+ };
16
+
17
+ const dbAction = async (command: string, options: any): Promise<void> => {
18
+ await actions.db(command, options);
19
+ };
20
+
21
+ const infoAction = async (projectPath: string): Promise<void> => {
22
+ await actions.info(projectPath);
23
+ };
24
+
25
+ const initAction = async (projectPath: string): Promise<void> => {
26
+ await actions.init(projectPath);
27
+ };
28
+
29
+ export function createProgram() {
30
+ const program = new Command('zenstack');
31
+
32
+ program.version(getVersion()!, '-v --version', 'display CLI version');
33
+
34
+ const schemaExtensions = ZModelLanguageMetaData.fileExtensions.join(', ');
35
+
36
+ program
37
+ .description(
38
+ `${colors.bold.blue(
39
+ 'ζ'
40
+ )} ZenStack is a Prisma power pack for building full-stack apps.\n\nDocumentation: https://zenstack.dev.`
41
+ )
42
+ .showHelpAfterError()
43
+ .showSuggestionAfterError();
44
+
45
+ const schemaOption = new Option(
46
+ '--schema <file>',
47
+ `schema file (with extension ${schemaExtensions}). Defaults to "schema.zmodel" unless specified in package.json.`
48
+ );
49
+
50
+ program
51
+ .command('generate')
52
+ .description('Run code generation.')
53
+ .addOption(schemaOption)
54
+ .addOption(
55
+ new Option(
56
+ '-o, --output <path>',
57
+ 'default output directory for core plugins'
58
+ )
59
+ )
60
+ .action(generateAction);
61
+
62
+ const migrateCommand = program
63
+ .command('migrate')
64
+ .description('Update the database schema with migrations.');
65
+
66
+ migrateCommand
67
+ .command('dev')
68
+ .addOption(schemaOption)
69
+ .addOption(new Option('-n, --name <name>', 'migration name'))
70
+ .addOption(
71
+ new Option('--create-only', 'only create migration, do not apply')
72
+ )
73
+ .description(
74
+ 'Create a migration from changes in schema and apply it to the database.'
75
+ )
76
+ .action((options) => migrateAction('dev', options));
77
+
78
+ migrateCommand
79
+ .command('reset')
80
+ .addOption(schemaOption)
81
+ .addOption(new Option('--force', 'skip the confirmation prompt'))
82
+ .description(
83
+ 'Reset your database and apply all migrations, all data will be lost.'
84
+ )
85
+ .action((options) => migrateAction('reset', options));
86
+
87
+ migrateCommand
88
+ .command('deploy')
89
+ .addOption(schemaOption)
90
+ .description(
91
+ 'Deploy your pending migrations to your production/staging database.'
92
+ )
93
+ .action((options) => migrateAction('deploy', options));
94
+
95
+ migrateCommand
96
+ .command('status')
97
+ .addOption(schemaOption)
98
+ .description('check the status of your database migrations.')
99
+ .action((options) => migrateAction('status', options));
100
+
101
+ const dbCommand = program
102
+ .command('db')
103
+ .description('Manage your database schema during development.');
104
+
105
+ dbCommand
106
+ .command('push')
107
+ .description('Push the state from your schema to your database')
108
+ .addOption(schemaOption)
109
+ .addOption(
110
+ new Option('--accept-data-loss', 'ignore data loss warnings')
111
+ )
112
+ .addOption(
113
+ new Option(
114
+ '--force-reset',
115
+ 'force a reset of the database before push'
116
+ )
117
+ )
118
+ .action((options) => dbAction('push', options));
119
+
120
+ program
121
+ .command('info')
122
+ .description(
123
+ 'Get information of installed ZenStack and related packages.'
124
+ )
125
+ .argument('[path]', 'project path', '.')
126
+ .action(infoAction);
127
+
128
+ program
129
+ .command('init')
130
+ .description('Initialize an existing project for ZenStack.')
131
+ .argument('[path]', 'project path', '.')
132
+ .action(initAction);
133
+
134
+ return program;
135
+ }
136
+
137
+ const program = createProgram();
138
+ program.parse(process.argv);
@@ -0,0 +1,29 @@
1
+ import { execSync as _exec, type ExecSyncOptions } from 'child_process';
2
+
3
+ /**
4
+ * Utility for executing command synchronously and prints outputs on current console
5
+ */
6
+ export function execSync(
7
+ cmd: string,
8
+ options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> }
9
+ ): void {
10
+ const { env, ...restOptions } = options ?? {};
11
+ const mergedEnv = env ? { ...process.env, ...env } : undefined;
12
+ _exec(cmd, {
13
+ encoding: 'utf-8',
14
+ stdio: options?.stdio ?? 'inherit',
15
+ env: mergedEnv,
16
+ ...restOptions,
17
+ });
18
+ }
19
+
20
+ /**
21
+ * Utility for running package commands through npx/bunx
22
+ */
23
+ export function execPackage(
24
+ cmd: string,
25
+ options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> }
26
+ ): void {
27
+ const packageManager = process?.versions?.['bun'] ? 'bunx' : 'npx';
28
+ execSync(`${packageManager} ${cmd}`, options);
29
+ }
@@ -0,0 +1,13 @@
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
+ export function getVersion(): string | undefined {
3
+ try {
4
+ return require('../package.json').version;
5
+ } catch {
6
+ try {
7
+ // dev environment
8
+ return require('../../package.json').version;
9
+ } catch {
10
+ return undefined;
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,185 @@
1
+ import { ExpressionUtils } from '@zenstackhq/runtime/schema';
2
+ import { generateTsSchema } from '@zenstackhq/testtools';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ describe('TypeScript schema generation tests', () => {
6
+ it('generates correct data models', async () => {
7
+ const { schema } = await generateTsSchema(`
8
+ model User {
9
+ id String @id @default(uuid())
10
+ name String
11
+ email String @unique
12
+ createdAt DateTime @default(now())
13
+ updatedAt DateTime @updatedAt
14
+ posts Post[]
15
+
16
+ @@map('users')
17
+ }
18
+
19
+ model Post {
20
+ id String @id @default(cuid())
21
+ title String
22
+ published Boolean @default(false)
23
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
24
+ authorId String
25
+ }
26
+ `);
27
+
28
+ expect(schema.provider).toMatchObject({
29
+ type: 'sqlite',
30
+ dialectConfigProvider: expect.any(Function),
31
+ });
32
+
33
+ expect(schema.models).toMatchObject({
34
+ User: {
35
+ fields: {
36
+ id: {
37
+ type: 'String',
38
+ id: true,
39
+ default: ExpressionUtils.call('uuid'),
40
+ attributes: [
41
+ { name: '@id' },
42
+ {
43
+ name: '@default',
44
+ args: [
45
+ {
46
+ value: {
47
+ kind: 'call',
48
+ function: 'uuid',
49
+ },
50
+ },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ name: { type: 'String' },
56
+ email: { type: 'String', unique: true },
57
+ createdAt: {
58
+ type: 'DateTime',
59
+ default: ExpressionUtils.call('now'),
60
+ attributes: [
61
+ {
62
+ name: '@default',
63
+ args: [
64
+ {
65
+ value: {
66
+ kind: 'call',
67
+ function: 'now',
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ },
74
+ updatedAt: {
75
+ type: 'DateTime',
76
+ attributes: [
77
+ {
78
+ name: '@updatedAt',
79
+ },
80
+ ],
81
+ updatedAt: true,
82
+ },
83
+ posts: {
84
+ type: 'Post',
85
+ array: true,
86
+ relation: {
87
+ opposite: 'author',
88
+ },
89
+ },
90
+ },
91
+ attributes: [
92
+ {
93
+ name: '@@map',
94
+ args: [{ name: 'name', value: { kind: 'literal' } }],
95
+ },
96
+ ],
97
+ idFields: ['id'],
98
+ uniqueFields: {
99
+ id: { type: 'String' },
100
+ email: { type: 'String' },
101
+ },
102
+ },
103
+ Post: {
104
+ fields: {
105
+ id: {
106
+ type: 'String',
107
+ id: true,
108
+ default: ExpressionUtils.call('cuid'),
109
+ attributes: [
110
+ { name: '@id' },
111
+ {
112
+ name: '@default',
113
+ args: [
114
+ {
115
+ value: {
116
+ kind: 'call',
117
+ function: 'cuid',
118
+ },
119
+ },
120
+ ],
121
+ },
122
+ ],
123
+ },
124
+ title: { type: 'String' },
125
+ published: {
126
+ type: 'Boolean',
127
+ default: false,
128
+ },
129
+ authorId: { type: 'String' },
130
+ author: {
131
+ type: 'User',
132
+ relation: {
133
+ fields: ['authorId'],
134
+ references: ['id'],
135
+ onDelete: 'Cascade',
136
+ opposite: 'posts',
137
+ },
138
+ attributes: [
139
+ {
140
+ name: '@relation',
141
+ args: [
142
+ {
143
+ name: 'fields',
144
+ value: {
145
+ kind: 'array',
146
+ items: [
147
+ {
148
+ kind: 'field',
149
+ field: 'authorId',
150
+ },
151
+ ],
152
+ },
153
+ },
154
+ {
155
+ name: 'references',
156
+ value: {
157
+ kind: 'array',
158
+ items: [
159
+ {
160
+ kind: 'field',
161
+ field: 'id',
162
+ },
163
+ ],
164
+ },
165
+ },
166
+ {
167
+ name: 'onDelete',
168
+ value: {
169
+ kind: 'literal',
170
+ value: 'Cascade',
171
+ },
172
+ },
173
+ ],
174
+ },
175
+ ],
176
+ },
177
+ },
178
+ idFields: ['id'],
179
+ uniqueFields: {
180
+ id: { type: 'String' },
181
+ },
182
+ },
183
+ });
184
+ });
185
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src/**/*.ts"]
7
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: 'src/index.ts',
6
+ },
7
+ outDir: 'dist',
8
+ splitting: false,
9
+ sourcemap: true,
10
+ clean: true,
11
+ dts: true,
12
+ format: ['esm', 'cjs'],
13
+ });
@@ -0,0 +1,4 @@
1
+ import { defineConfig, mergeConfig } from 'vitest/config';
2
+ import base from '../../vitest.base.config';
3
+
4
+ export default mergeConfig(base, defineConfig({}));