@zenstackhq/redwood 0.5.0 → 1.6.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/README.md CHANGED
@@ -1,5 +1,97 @@
1
1
  # ZenStack RedwoodJS Integration
2
2
 
3
- This package is for integrating ZenStack into [RedwoodJS](https://redwoodjs.com/) projects.
3
+ This package provides the CLI and runtime APIs for integrating [ZenStack](https://zenstack.dev) into a [RedwoodJS](https://redwoodjs.com/) project. You can use ZenStack as a drop-in replacement to Prisma and define flexible access control policies declaratively inside the database schema. It's especially useful for building multi-tenant applications which tend to have complex authorization requirements beyond RBAC.
4
4
 
5
- Visit [Homepage](https://zenstack.dev) for more details.
5
+ ZenStack is a full-stack toolkit built above Prisma ORM. It extends Prisma at the schema and the runtime level for adding the following capabilities:
6
+
7
+ - Flexible access control
8
+ - Data validation rules
9
+ - Multi-file schemas
10
+ - Custom attributes and functions in schemas
11
+
12
+ You can find a more detailed integration guide [here](https://zenstack.dev/docs/guides/redwood).
13
+
14
+ ### Setting up
15
+
16
+ Run the following package setup command:
17
+
18
+ ```bash
19
+ yarn rw setup package @zenstackhq/redwood
20
+ ```
21
+
22
+ The setup command will:
23
+
24
+ 1. Update "redwood.toml" to allow ZenStack CLI plugin.
25
+ 1. Install ZenStack dependencies.
26
+ 1. Copy your Prisma schema file "api/db/schema.prisma" to "api/db/schema.zmodel".
27
+ 1. Add a "zenstack" section into "api/package.json" to specify the location 1f both the "schema.prisma" and "schema.zmodel" files.
28
+ 1. Install a GraphQLYoga plugin in "api/src/functions/graphql.[ts|js]".
29
+ 1. Eject service templates and modify the templates to use `context.db` (ZenStack-enhanced `PrismaClient`) instead of `db` for data access.
30
+
31
+ ### Modeling data and access policies
32
+
33
+ ZenStack's ZModel language is a superset of Prisma schema language. You should use it to define both the data schema and access policies. [The Complete Guide](https://zenstack.dev/docs/the-complete-guide/part1/) of ZenStack is the best way to learn how to author ZModel schemas.
34
+
35
+ You should run the following command after updating "schema.zmodel":
36
+
37
+ ```bash
38
+ yarn rw @zenstackhq generate
39
+ ```
40
+
41
+ The command does the following things:
42
+
43
+ 1. Regenerate "schema.prisma"
44
+ 2. Run `prisma generate` to regenerate PrismaClient
45
+ 3. Generates supporting JS modules for enforcing access policies at runtime
46
+
47
+ <!-- You can also use the
48
+
49
+ ```bash
50
+ yarn rw @zenstackhq sample
51
+ ```
52
+
53
+ command to browse a list of sample schemas and create from them. -->
54
+
55
+ ### Development workflow
56
+
57
+ The workflow of using ZenStack is very similar to using Prisma in RedwoodJS projects. The two main differences are:
58
+
59
+ 1. Generation
60
+
61
+ You should run `yarn rw @zenstackhq generate` in place of `yarn rw prisma generate`. The ZenStack's generate command internally regenerates the Prisma schema from the ZModel schema, runs `prisma generate` automatically, and also generates other modules for supporting access policy enforcement at the runtime.
62
+
63
+ 2. Database access in services
64
+
65
+ In your service code, you should use `context.db` instead of `db` for accessing the database. The `context.db` is an enhanced Prisma client that enforces access policies.
66
+
67
+ The "setup" command prepared a customized service code template. When you run `yarn rw g service`, the generated code will already use `context.db`.
68
+
69
+ Other Prisma-related workflows like generation migration or pushing schema to the database stay unchanged.
70
+
71
+ ### Deployment
72
+
73
+ You should run the "generate" command in your deployment script before `yarn rw deploy`. For example, to deploy to Vercel, the command can be:
74
+
75
+ ```bash
76
+ yarn rw @zenstackhq generate && yarn rw deploy vercel
77
+ ```
78
+
79
+ ### Using the `@zenstackhq` CLI plugin
80
+
81
+ The `@zenstackhq/redwood` package registers a set of custom commands to the RedwoodJS CLI under the `@zenstackhq` namespace. You can run it with:
82
+
83
+ ```bash
84
+ yarn rw @zenstackhq <cmd> [options]
85
+ ```
86
+
87
+ The plugin is a simple wrapper of the standard `zenstack` CLI, similar to how RedwoodJS wraps the standard `prisma` CLI. It's equivalent to running `npx zenstack ...` inside the "api" directory.
88
+
89
+ See the [CLI references](https://zenstack.dev/docs/reference/cli) for the full list of commands.
90
+
91
+ ### Sample application
92
+
93
+ You can find a complete multi-tenant Todo application built with RedwoodJS and ZenStack at: [https://github.com/zenstackhq/sample-todo-redwood](https://github.com/zenstackhq/sample-todo-redwood).
94
+
95
+ ### Getting help
96
+
97
+ The best way to get help and updates about ZenStack is by joining our [Discord server](https://discord.gg/Ykhr738dUe).
package/bin/cli ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/setup-package').default();
@@ -0,0 +1,5 @@
1
+ import { CommandModule } from 'yargs';
2
+ /**
3
+ * Creates a yargs command that passes all options to the ZenStack CLI command.
4
+ */
5
+ export declare function makePassthroughCommand(command: string): CommandModule<unknown>;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.makePassthroughCommand = void 0;
27
+ const cli_helpers_1 = require("@redwoodjs/cli-helpers");
28
+ const colors_1 = __importDefault(require("colors"));
29
+ const execa_1 = __importDefault(require("execa"));
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ function runCommand(command, options) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ const args = ['zenstack', command];
34
+ for (const [name, value] of Object.entries(options)) {
35
+ args.push(name.length > 1 ? `--${name}` : `-${name}`);
36
+ if (typeof value === 'string') {
37
+ // Make sure options that take multiple quoted words
38
+ // are passed to zenstack with quotes.
39
+ value.split(' ').length > 1 ? args.push(`"${value}"`) : args.push(value);
40
+ }
41
+ }
42
+ console.log();
43
+ console.log(colors_1.default.green('Running ZenStack CLI...'));
44
+ console.log(colors_1.default.underline('$ npx ' + args.join(' ')));
45
+ console.log();
46
+ try {
47
+ yield (0, execa_1.default)('npx', args, { cwd: (0, cli_helpers_1.getPaths)().api.base, shell: true, stdio: 'inherit', cleanup: true });
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ }
50
+ catch (e) {
51
+ process.exit((e === null || e === void 0 ? void 0 : e.exitCode) || 1);
52
+ }
53
+ });
54
+ }
55
+ /**
56
+ * Creates a yargs command that passes all options to the ZenStack CLI command.
57
+ */
58
+ function makePassthroughCommand(command) {
59
+ return {
60
+ command,
61
+ describe: `Run \`zenstack ${command} ...\``,
62
+ builder: (yargs) => {
63
+ return yargs
64
+ .strictOptions(false)
65
+ .strictCommands(false)
66
+ .strict(false)
67
+ .parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false });
68
+ },
69
+ handler: (_a) => __awaiter(this, void 0, void 0, function* () {
70
+ var { _, $0: _$0 } = _a, options = __rest(_a, ["_", "$0"]);
71
+ yield runCommand(command, options);
72
+ }),
73
+ };
74
+ }
75
+ exports.makePassthroughCommand = makePassthroughCommand;
76
+ //# sourceMappingURL=cli-passthrough.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-passthrough.js","sourceRoot":"","sources":["../src/cli-passthrough.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wDAAkD;AAClD,oDAA4B;AAC5B,kDAA0B;AAG1B,8DAA8D;AAC9D,SAAe,UAAU,CAAC,OAAe,EAAE,OAAY;;QACnD,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACnC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC5B,oDAAoD;gBACpD,sCAAsC;gBACtC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7E,CAAC;QACL,CAAC;QAED,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,gBAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,gBAAM,CAAC,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,EAAE,CAAC;QAEd,IAAI,CAAC;YACD,MAAM,IAAA,eAAK,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACrG,8DAA8D;QAClE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,QAAQ,KAAI,CAAC,CAAC,CAAC;QACnC,CAAC;IACL,CAAC;CAAA;AAED;;GAEG;AACH,SAAgB,sBAAsB,CAAC,OAAe;IAClD,OAAO;QACH,OAAO;QACP,QAAQ,EAAE,kBAAkB,OAAO,QAAQ;QAC3C,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACf,OAAO,KAAK;iBACP,aAAa,CAAC,KAAK,CAAC;iBACpB,cAAc,CAAC,KAAK,CAAC;iBACrB,MAAM,CAAC,KAAK,CAAC;iBACb,mBAAmB,CAAC,EAAE,sBAAsB,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,EAAE,CAAO,EAA0B,EAAE,EAAE;gBAA9B,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,OAAc,EAAT,OAAO,cAAxB,WAA0B,CAAF;YACpC,MAAM,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACvC,CAAC,CAAA;KACJ,CAAC;AACN,CAAC;AAfD,wDAeC"}
@@ -0,0 +1,3 @@
1
+ import type { CommandModule } from 'yargs';
2
+ declare const setupCommand: CommandModule<unknown>;
3
+ export default setupCommand;
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const cli_helpers_1 = require("@redwoodjs/cli-helpers");
16
+ const colors_1 = __importDefault(require("colors"));
17
+ const execa_1 = __importDefault(require("execa"));
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const listr2_1 = require("listr2");
20
+ const path_1 = __importDefault(require("path"));
21
+ const terminal_link_1 = __importDefault(require("terminal-link"));
22
+ const ts_morph_1 = require("ts-morph");
23
+ const utils_1 = require("../utils");
24
+ function updateToml() {
25
+ return {
26
+ title: 'Updating redwood.toml...',
27
+ task: () => {
28
+ (0, cli_helpers_1.updateTomlConfig)('@zenstackhq/redwood');
29
+ },
30
+ };
31
+ }
32
+ function installDependencies() {
33
+ return (0, utils_1.addApiPackages)([
34
+ { pkg: 'zenstack', dev: true },
35
+ { pkg: '@zenstackhq/runtime' },
36
+ { pkg: '@zenstackhq/redwood' },
37
+ ]);
38
+ }
39
+ // copy schema.prisma to schema.zmodel, and update package.json
40
+ function bootstrapSchema() {
41
+ return {
42
+ title: 'Bootstrapping ZModel schema...',
43
+ task: () => {
44
+ const apiPaths = (0, cli_helpers_1.getPaths)().api;
45
+ const zmodel = path_1.default.join(path_1.default.dirname(apiPaths.dbSchema), 'schema.zmodel');
46
+ if (!fs_1.default.existsSync(zmodel)) {
47
+ fs_1.default.cpSync(apiPaths.dbSchema, zmodel);
48
+ }
49
+ else {
50
+ console.info(colors_1.default.blue(`Schema file "${path_1.default.relative((0, cli_helpers_1.getPaths)().base, zmodel)}" already exists. Skipping.`));
51
+ }
52
+ const pkgJson = path_1.default.join(apiPaths.base, 'package.json');
53
+ if (fs_1.default.existsSync(pkgJson)) {
54
+ const content = fs_1.default.readFileSync(pkgJson, 'utf-8');
55
+ const pkg = JSON.parse(content);
56
+ if (!pkg.zenstack) {
57
+ pkg.zenstack = {
58
+ schema: path_1.default.relative(apiPaths.base, zmodel),
59
+ prisma: path_1.default.relative(apiPaths.base, apiPaths.dbSchema),
60
+ };
61
+ fs_1.default.writeFileSync(pkgJson, JSON.stringify(pkg, null, 4));
62
+ }
63
+ }
64
+ },
65
+ };
66
+ }
67
+ // install ZenStack GraphQLYoga plugin
68
+ function installGraphQLPlugin() {
69
+ return {
70
+ title: 'Installing GraphQL plugin...',
71
+ task: () => __awaiter(this, void 0, void 0, function* () {
72
+ // locate "api/functions/graphql.[js|ts]"
73
+ let graphQlSourcePath;
74
+ const functionsDir = (0, cli_helpers_1.getPaths)().api.functions;
75
+ if (fs_1.default.existsSync(path_1.default.join(functionsDir, 'graphql.ts'))) {
76
+ graphQlSourcePath = path_1.default.join(functionsDir, 'graphql.ts');
77
+ }
78
+ else if (fs_1.default.existsSync(path_1.default.join(functionsDir, 'graphql.js'))) {
79
+ graphQlSourcePath = path_1.default.join(functionsDir, 'graphql.js');
80
+ }
81
+ if (!graphQlSourcePath) {
82
+ console.warn(colors_1.default.yellow(`Unable to find handler source file: ${path_1.default.join(functionsDir, 'graphql.(js|ts)')}`));
83
+ return;
84
+ }
85
+ // add import
86
+ const project = new ts_morph_1.Project();
87
+ const graphQlSourceFile = project.addSourceFileAtPathIfExists(graphQlSourcePath);
88
+ let graphQlSourceFileChanged = false;
89
+ let identified = false;
90
+ const imports = graphQlSourceFile.getImportDeclarations();
91
+ if (!imports.some((i) => i.getModuleSpecifierValue() === '@zenstackhq/redwood')) {
92
+ graphQlSourceFile.addImportDeclaration({
93
+ moduleSpecifier: '@zenstackhq/redwood',
94
+ namedImports: ['useZenStack'],
95
+ });
96
+ graphQlSourceFileChanged = true;
97
+ }
98
+ // add "extraPlugins" option to `createGraphQLHandler` call
99
+ graphQlSourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression).forEach((expr) => {
100
+ var _a, _b;
101
+ if (identified) {
102
+ return;
103
+ }
104
+ if (((_a = expr.getExpression().asKind(ts_morph_1.SyntaxKind.Identifier)) === null || _a === void 0 ? void 0 : _a.getText()) === 'createGraphQLHandler') {
105
+ const arg = (_b = expr.getArguments()[0]) === null || _b === void 0 ? void 0 : _b.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
106
+ if (arg) {
107
+ identified = true;
108
+ const props = arg.getProperties();
109
+ const pluginsProp = props.find((p) => { var _a; return ((_a = p.asKind(ts_morph_1.SyntaxKind.PropertyAssignment)) === null || _a === void 0 ? void 0 : _a.getName()) === 'extraPlugins'; });
110
+ if (pluginsProp) {
111
+ const pluginArr = pluginsProp.getInitializerIfKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
112
+ if (pluginArr) {
113
+ if (!pluginArr.getElements().some((e) => e.getText().includes('useZenStack'))) {
114
+ pluginArr.addElement('useZenStack(db)');
115
+ graphQlSourceFileChanged = true;
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ arg.addPropertyAssignment({
121
+ name: 'extraPlugins',
122
+ initializer: '[useZenStack(db)]',
123
+ });
124
+ graphQlSourceFileChanged = true;
125
+ }
126
+ }
127
+ }
128
+ });
129
+ if (!identified) {
130
+ console.warn(colors_1.default.yellow('Unable to determine how to install ZenStack GraphQL plugin. Please add it manually following https://zenstack.dev/docs/guides/redwood.'));
131
+ }
132
+ if (graphQlSourceFileChanged) {
133
+ graphQlSourceFile.formatText();
134
+ }
135
+ // create type-def file to add `db` into global context
136
+ let contextTypeDefCreated = false;
137
+ if (graphQlSourcePath.endsWith('.ts')) {
138
+ const typeDefPath = path_1.default.join((0, cli_helpers_1.getPaths)().api.src, 'zenstack.d.ts');
139
+ if (!fs_1.default.existsSync(typeDefPath)) {
140
+ const typeDefSourceFile = project.createSourceFile(typeDefPath, `import type { PrismaClient } from '@prisma/client'
141
+
142
+ declare module '@redwoodjs/graphql-server' {
143
+ interface GlobalContext {
144
+ db: PrismaClient
145
+ }
146
+ }
147
+ `);
148
+ typeDefSourceFile.formatText();
149
+ contextTypeDefCreated = true;
150
+ }
151
+ }
152
+ if (graphQlSourceFileChanged || contextTypeDefCreated) {
153
+ yield project.save();
154
+ }
155
+ }),
156
+ };
157
+ }
158
+ // eject templates used for `yarn rw generate service`
159
+ function ejectServiceTemplates() {
160
+ return {
161
+ title: 'Ejecting service templates...',
162
+ task: () => __awaiter(this, void 0, void 0, function* () {
163
+ if (fs_1.default.existsSync(path_1.default.join((0, cli_helpers_1.getPaths)().api.base, 'generators', 'service'))) {
164
+ console.info(colors_1.default.blue('Service templates already ejected. Skipping.'));
165
+ return;
166
+ }
167
+ yield (0, execa_1.default)('yarn', ['rw', 'setup', 'generator', 'service'], { cwd: (0, cli_helpers_1.getPaths)().api.base });
168
+ const serviceTemplateTsFile = path_1.default.join((0, cli_helpers_1.getPaths)().api.base, 'generators', 'service', 'service.ts.template');
169
+ const serviceTemplateJsFile = path_1.default.join((0, cli_helpers_1.getPaths)().api.base, 'generators', 'service', 'service.js.template');
170
+ const serviceTemplateFile = fs_1.default.existsSync(serviceTemplateTsFile)
171
+ ? serviceTemplateTsFile
172
+ : fs_1.default.existsSync(serviceTemplateJsFile)
173
+ ? serviceTemplateJsFile
174
+ : undefined;
175
+ if (!serviceTemplateFile) {
176
+ console.warn(colors_1.default.red('Unable to find the ejected service template file.'));
177
+ return;
178
+ }
179
+ // replace `db.` with `context.db.`
180
+ const templateContent = fs_1.default.readFileSync(serviceTemplateFile, 'utf-8');
181
+ const newTemplateContent = templateContent
182
+ .replace(/^import { db } from.*\n$/gm, '')
183
+ .replace(/return db\./g, 'return context.db.');
184
+ fs_1.default.writeFileSync(serviceTemplateFile, newTemplateContent);
185
+ }),
186
+ };
187
+ }
188
+ function whatsNext() {
189
+ const zmodel = path_1.default.relative((0, cli_helpers_1.getPaths)().base, path_1.default.join(path_1.default.dirname((0, cli_helpers_1.getPaths)().api.dbSchema), 'schema.zmodel'));
190
+ const task = {
191
+ title: `What's next...`,
192
+ task: (_ctx, task) => {
193
+ task.title =
194
+ `What's next...\n\n` +
195
+ ` - Install ${(0, terminal_link_1.default)('IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
196
+ ` - Use "${zmodel}" to model database schema and access control.\n` +
197
+ ` - Run \`yarn rw @zenstackhq generate\` to regenerate Prisma schema and client.\n` +
198
+ ` - Learn ${(0, terminal_link_1.default)("how ZenStack extends Prisma's power", 'https://zenstack.dev/docs/the-complete-guide/part1')}.\n` +
199
+ ` - Create a sample schema with \`yarn rw @zenstackhq sample\`.\n` +
200
+ ` - Join ${(0, terminal_link_1.default)('Discord community', 'https://discord.gg/Ykhr738dUe')} for questions and updates.\n`;
201
+ },
202
+ };
203
+ return task;
204
+ }
205
+ const setupCommand = {
206
+ command: 'setup',
207
+ describe: 'Set up ZenStack environment',
208
+ builder: (yargs) => yargs,
209
+ handler: () => __awaiter(void 0, void 0, void 0, function* () {
210
+ const tasks = new listr2_1.Listr([
211
+ updateToml(),
212
+ installDependencies(),
213
+ bootstrapSchema(),
214
+ installGraphQLPlugin(),
215
+ ejectServiceTemplates(),
216
+ whatsNext(),
217
+ ]);
218
+ try {
219
+ yield tasks.run();
220
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
221
+ }
222
+ catch (e) {
223
+ console.error(colors_1.default.red(e.message));
224
+ process.exit((e === null || e === void 0 ? void 0 : e.exitCode) || 1);
225
+ }
226
+ }),
227
+ };
228
+ exports.default = setupCommand;
229
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,wDAAoE;AACpE,oDAA4B;AAC5B,kDAA0B;AAC1B,4CAAoB;AACpB,mCAA0C;AAC1C,gDAAwB;AACxB,kEAAyC;AACzC,uCAAwE;AAExE,oCAA0C;AAE1C,SAAS,UAAU;IACf,OAAO;QACH,KAAK,EAAE,0BAA0B;QACjC,IAAI,EAAE,GAAG,EAAE;YACP,IAAA,8BAAgB,EAAC,qBAAqB,CAAC,CAAC;QAC5C,CAAC;KACJ,CAAC;AACN,CAAC;AAED,SAAS,mBAAmB;IACxB,OAAO,IAAA,sBAAc,EAAC;QAClB,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE;QAC9B,EAAE,GAAG,EAAE,qBAAqB,EAAE;QAC9B,EAAE,GAAG,EAAE,qBAAqB,EAAE;KACjC,CAAC,CAAC;AACP,CAAC;AAED,+DAA+D;AAC/D,SAAS,eAAe;IACpB,OAAO;QACH,KAAK,EAAE,gCAAgC;QACvC,IAAI,EAAE,GAAG,EAAE;YACP,MAAM,QAAQ,GAAG,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC;YAChC,MAAM,MAAM,GAAG,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC;YAC3E,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,YAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,IAAI,CACR,gBAAM,CAAC,IAAI,CAAC,gBAAgB,cAAI,CAAC,QAAQ,CAAC,IAAA,sBAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,CAAC,6BAA6B,CAAC,CACnG,CAAC;YACN,CAAC;YAED,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;YACzD,IAAI,YAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,MAAM,OAAO,GAAG,YAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAChC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;oBAChB,GAAG,CAAC,QAAQ,GAAG;wBACX,MAAM,EAAE,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;wBAC5C,MAAM,EAAE,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC;qBAC1D,CAAC;oBACF,YAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC5D,CAAC;YACL,CAAC;QACL,CAAC;KACJ,CAAC;AACN,CAAC;AAED,sCAAsC;AACtC,SAAS,oBAAoB;IACzB,OAAO;QACH,KAAK,EAAE,8BAA8B;QACrC,IAAI,EAAE,GAAS,EAAE;YACb,yCAAyC;YACzC,IAAI,iBAAqC,CAAC;YAC1C,MAAM,YAAY,GAAG,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,SAAS,CAAC;YAC9C,IAAI,YAAE,CAAC,UAAU,CAAC,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;gBACvD,iBAAiB,GAAG,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;YAC9D,CAAC;iBAAM,IAAI,YAAE,CAAC,UAAU,CAAC,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;gBAC9D,iBAAiB,GAAG,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;YAC9D,CAAC;YAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CACR,gBAAM,CAAC,MAAM,CAAC,uCAAuC,cAAI,CAAC,IAAI,CAAC,YAAY,EAAE,iBAAiB,CAAC,EAAE,CAAC,CACrG,CAAC;gBACF,OAAO;YACX,CAAC;YAED,aAAa;YACb,MAAM,OAAO,GAAG,IAAI,kBAAO,EAAE,CAAC;YAC9B,MAAM,iBAAiB,GAAG,OAAO,CAAC,2BAA2B,CAAC,iBAAiB,CAAE,CAAC;YAClF,IAAI,wBAAwB,GAAG,KAAK,CAAC;YACrC,IAAI,UAAU,GAAG,KAAK,CAAC;YAEvB,MAAM,OAAO,GAAG,iBAAiB,CAAC,qBAAqB,EAAE,CAAC;YAC1D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,uBAAuB,EAAE,KAAK,qBAAqB,CAAC,EAAE,CAAC;gBAC9E,iBAAiB,CAAC,oBAAoB,CAAC;oBACnC,eAAe,EAAE,qBAAqB;oBACtC,YAAY,EAAE,CAAC,aAAa,CAAC;iBAChC,CAAC,CAAC;gBACH,wBAAwB,GAAG,IAAI,CAAC;YACpC,CAAC;YAED,2DAA2D;YAC3D,iBAAiB,CAAC,oBAAoB,CAAC,qBAAU,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;;gBAC/E,IAAI,UAAU,EAAE,CAAC;oBACb,OAAO;gBACX,CAAC;gBAED,IAAI,CAAA,MAAA,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,qBAAU,CAAC,UAAU,CAAC,0CAAE,OAAO,EAAE,MAAK,sBAAsB,EAAE,CAAC;oBAC3F,MAAM,GAAG,GAAG,MAAA,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,0CAAE,MAAM,CAAC,qBAAU,CAAC,uBAAuB,CAAC,CAAC;oBAC/E,IAAI,GAAG,EAAE,CAAC;wBACN,UAAU,GAAG,IAAI,CAAC;wBAClB,MAAM,KAAK,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC;wBAClC,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAC1B,CAAC,CAAC,EAA2B,EAAE,WAC3B,OAAA,CAAA,MAAA,CAAC,CAAC,MAAM,CAAC,qBAAU,CAAC,kBAAkB,CAAC,0CAAE,OAAO,EAAE,MAAK,cAAc,CAAA,EAAA,CAC5E,CAAC;wBACF,IAAI,WAAW,EAAE,CAAC;4BACd,MAAM,SAAS,GAAG,WAAW,CAAC,oBAAoB,CAAC,qBAAU,CAAC,sBAAsB,CAAC,CAAC;4BACtF,IAAI,SAAS,EAAE,CAAC;gCACZ,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;oCAC5E,SAAS,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;oCACxC,wBAAwB,GAAG,IAAI,CAAC;gCACpC,CAAC;4BACL,CAAC;wBACL,CAAC;6BAAM,CAAC;4BACJ,GAAG,CAAC,qBAAqB,CAAC;gCACtB,IAAI,EAAE,cAAc;gCACpB,WAAW,EAAE,mBAAmB;6BACnC,CAAC,CAAC;4BACH,wBAAwB,GAAG,IAAI,CAAC;wBACpC,CAAC;oBACL,CAAC;gBACL,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,UAAU,EAAE,CAAC;gBACd,OAAO,CAAC,IAAI,CACR,gBAAM,CAAC,MAAM,CACT,wIAAwI,CAC3I,CACJ,CAAC;YACN,CAAC;YAED,IAAI,wBAAwB,EAAE,CAAC;gBAC3B,iBAAiB,CAAC,UAAU,EAAE,CAAC;YACnC,CAAC;YAED,uDAAuD;YACvD,IAAI,qBAAqB,GAAG,KAAK,CAAC;YAClC,IAAI,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpC,MAAM,WAAW,GAAG,cAAI,CAAC,IAAI,CAAC,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;gBACnE,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC9B,MAAM,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAC9C,WAAW,EACX;;;;;;;CAOvB,CACoB,CAAC;oBACF,iBAAiB,CAAC,UAAU,EAAE,CAAC;oBAC/B,qBAAqB,GAAG,IAAI,CAAC;gBACjC,CAAC;YACL,CAAC;YAED,IAAI,wBAAwB,IAAI,qBAAqB,EAAE,CAAC;gBACpD,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACzB,CAAC;QACL,CAAC,CAAA;KACJ,CAAC;AACN,CAAC;AAED,sDAAsD;AACtD,SAAS,qBAAqB;IAC1B,OAAO;QACH,KAAK,EAAE,+BAA+B;QACtC,IAAI,EAAE,GAAS,EAAE;YACb,IAAI,YAAE,CAAC,UAAU,CAAC,cAAI,CAAC,IAAI,CAAC,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;gBACzE,OAAO,CAAC,IAAI,CAAC,gBAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC,CAAC;gBAC1E,OAAO;YACX,CAAC;YAED,MAAM,IAAA,eAAK,EAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3F,MAAM,qBAAqB,GAAG,cAAI,CAAC,IAAI,CACnC,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EACnB,YAAY,EACZ,SAAS,EACT,qBAAqB,CACxB,CAAC;YACF,MAAM,qBAAqB,GAAG,cAAI,CAAC,IAAI,CACnC,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EACnB,YAAY,EACZ,SAAS,EACT,qBAAqB,CACxB,CAAC;YACF,MAAM,mBAAmB,GAAG,YAAE,CAAC,UAAU,CAAC,qBAAqB,CAAC;gBAC5D,CAAC,CAAC,qBAAqB;gBACvB,CAAC,CAAC,YAAE,CAAC,UAAU,CAAC,qBAAqB,CAAC;oBACtC,CAAC,CAAC,qBAAqB;oBACvB,CAAC,CAAC,SAAS,CAAC;YAEhB,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACvB,OAAO,CAAC,IAAI,CAAC,gBAAM,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC,CAAC;gBAC9E,OAAO;YACX,CAAC;YAED,mCAAmC;YACnC,MAAM,eAAe,GAAG,YAAE,CAAC,YAAY,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;YACtE,MAAM,kBAAkB,GAAG,eAAe;iBACrC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC;iBACzC,OAAO,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;YACnD,YAAE,CAAC,aAAa,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;QAC9D,CAAC,CAAA;KACJ,CAAC;AACN,CAAC;AAED,SAAS,SAAS;IACd,MAAM,MAAM,GAAG,cAAI,CAAC,QAAQ,CAAC,IAAA,sBAAQ,GAAE,CAAC,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC;IACjH,MAAM,IAAI,GAAc;QACpB,KAAK,EAAE,gBAAgB;QACvB,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;YACjB,IAAI,CAAC,KAAK;gBACN,oBAAoB;oBACpB,gBAAgB,IAAA,uBAAY,EAAC,gBAAgB,EAAE,sCAAsC,CAAC,KAAK;oBAC3F,aAAa,MAAM,kDAAkD;oBACrE,qFAAqF;oBACrF,cAAc,IAAA,uBAAY,EACtB,qCAAqC,EACrC,oDAAoD,CACvD,KAAK;oBACN,oEAAoE;oBACpE,aAAa,IAAA,uBAAY,EACrB,mBAAmB,EACnB,+BAA+B,CAClC,+BAA+B,CAAC;QACzC,CAAC;KACJ,CAAC;IACF,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAM,YAAY,GAA2B;IACzC,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,6BAA6B;IACvC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;IACzB,OAAO,EAAE,GAAS,EAAE;QAChB,MAAM,KAAK,GAAG,IAAI,cAAK,CAAC;YACpB,UAAU,EAAE;YACZ,mBAAmB,EAAE;YACrB,eAAe,EAAE;YACjB,oBAAoB,EAAE;YACtB,qBAAqB,EAAE;YACvB,SAAS,EAAE;SACd,CAAC,CAAC;QAEH,IAAI,CAAC;YACD,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;YAClB,8DAA8D;QAClE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,gBAAM,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,QAAQ,KAAI,CAAC,CAAC,CAAC;QACnC,CAAC;IACL,CAAC,CAAA;CACJ,CAAC;AAEF,kBAAe,YAAY,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { EnhancementOptions, type AuthUser } from '@zenstackhq/runtime';
2
+ import { type Plugin } from 'graphql-yoga';
3
+ /**
4
+ * Plugin options
5
+ */
6
+ export type ZenStackPluginOptions = EnhancementOptions;
7
+ /**
8
+ * A GraphQLYoga plugin that adds a ZenStack-enhanced PrismaClient into the context
9
+ * as `context.db`.
10
+ * @param db The original PrismaClient.
11
+ * @param getAuthUser A hook function for getting the current user. By default `context.currentUser` is used.
12
+ * @param options Options for creating the enhanced PrismaClient. See https://zenstack.dev/docs/reference/runtime-api#enhance for more details.
13
+ * @returns
14
+ */
15
+ export declare function useZenStack<PrismaClient extends object>(db: PrismaClient, getAuthUser?: (currentUser: unknown) => Promise<AuthUser>, options?: ZenStackPluginOptions): Plugin<{
16
+ currentUser: unknown;
17
+ db: PrismaClient;
18
+ }>;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.useZenStack = void 0;
13
+ const graphql_server_1 = require("@redwoodjs/graphql-server");
14
+ const runtime_1 = require("@zenstackhq/runtime");
15
+ /**
16
+ * A GraphQLYoga plugin that adds a ZenStack-enhanced PrismaClient into the context
17
+ * as `context.db`.
18
+ * @param db The original PrismaClient.
19
+ * @param getAuthUser A hook function for getting the current user. By default `context.currentUser` is used.
20
+ * @param options Options for creating the enhanced PrismaClient. See https://zenstack.dev/docs/reference/runtime-api#enhance for more details.
21
+ * @returns
22
+ */
23
+ function useZenStack(db, getAuthUser, options) {
24
+ return {
25
+ onContextBuilding: () => {
26
+ return ({ context }) => __awaiter(this, void 0, void 0, function* () {
27
+ const user = getAuthUser ? yield getAuthUser(context.currentUser) : context.currentUser;
28
+ context.db = (0, runtime_1.enhance)(db, { user }, Object.assign({ errorTransformer: transformError }, options));
29
+ });
30
+ },
31
+ };
32
+ }
33
+ exports.useZenStack = useZenStack;
34
+ // Transforms ZenStack errors into appropriate RedwoodJS errors
35
+ function transformError(error) {
36
+ var _a, _b, _c;
37
+ if ((0, runtime_1.isPrismaClientKnownRequestError)(error) && error.code === runtime_1.PrismaErrorCode.CONSTRAINED_FAILED) {
38
+ if (((_a = error.meta) === null || _a === void 0 ? void 0 : _a.reason) === runtime_1.CrudFailureReason.ACCESS_POLICY_VIOLATION ||
39
+ ((_b = error.meta) === null || _b === void 0 ? void 0 : _b.reason) === runtime_1.CrudFailureReason.RESULT_NOT_READABLE) {
40
+ return new graphql_server_1.ForbiddenError(error.message);
41
+ }
42
+ else if (((_c = error.meta) === null || _c === void 0 ? void 0 : _c.reason) === runtime_1.CrudFailureReason.DATA_VALIDATION_VIOLATION) {
43
+ return new runtime_1.ValidationError(error.message);
44
+ }
45
+ }
46
+ return error;
47
+ }
48
+ //# sourceMappingURL=graphql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql.js","sourceRoot":"","sources":["../src/graphql.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8DAA2D;AAC3D,iDAQ6B;AAQ7B;;;;;;;GAOG;AACH,SAAgB,WAAW,CACvB,EAAgB,EAChB,WAAyD,EACzD,OAA+B;IAE/B,OAAO;QACH,iBAAiB,EAAE,GAAG,EAAE;YACpB,OAAO,CAAO,EAAE,OAAO,EAAE,EAAE,EAAE;gBACzB,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAE,OAAO,CAAC,WAAwB,CAAC;gBACtG,OAAO,CAAC,EAAE,GAAG,IAAA,iBAAO,EAChB,EAAE,EACF,EAAE,IAAI,EAAE,kBAEJ,gBAAgB,EAAE,cAAc,IAC7B,OAAO,EAEjB,CAAC;YACN,CAAC,CAAA,CAAC;QACN,CAAC;KACJ,CAAC;AACN,CAAC;AApBD,kCAoBC;AAED,+DAA+D;AAC/D,SAAS,cAAc,CAAC,KAAc;;IAClC,IAAI,IAAA,yCAA+B,EAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,yBAAe,CAAC,kBAAkB,EAAE,CAAC;QAC9F,IACI,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,MAAK,2BAAiB,CAAC,uBAAuB;YAChE,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,MAAK,2BAAiB,CAAC,mBAAmB,EAC9D,CAAC;YACC,OAAO,IAAI,+BAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;aAAM,IAAI,CAAA,MAAA,KAAK,CAAC,IAAI,0CAAE,MAAM,MAAK,2BAAiB,CAAC,yBAAyB,EAAE,CAAC;YAC5E,OAAO,IAAI,yBAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ /// <reference types="yargs" />
2
+ export declare const commands: import("yargs").CommandModule<unknown, {}>[];
3
+ export * from './graphql';
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.commands = void 0;
21
+ const cli_passthrough_1 = require("./cli-passthrough");
22
+ const setup_1 = __importDefault(require("./commands/setup"));
23
+ exports.commands = [
24
+ setup_1.default,
25
+ (0, cli_passthrough_1.makePassthroughCommand)('generate'),
26
+ (0, cli_passthrough_1.makePassthroughCommand)('info'),
27
+ (0, cli_passthrough_1.makePassthroughCommand)('format'),
28
+ (0, cli_passthrough_1.makePassthroughCommand)('repl'),
29
+ ];
30
+ __exportStar(require("./graphql"), exports);
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,uDAA2D;AAC3D,6DAAqC;AAExB,QAAA,QAAQ,GAAG;IACpB,eAAK;IACL,IAAA,wCAAsB,EAAC,UAAU,CAAC;IAClC,IAAA,wCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,wCAAsB,EAAC,QAAQ,CAAC;IAChC,IAAA,wCAAsB,EAAC,MAAM,CAAC;CACjC,CAAC;AACF,4CAA0B"}
@@ -0,0 +1 @@
1
+ export default function setupPackage(): Promise<void>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const yargs_1 = __importDefault(require("yargs"));
16
+ const helpers_1 = require("yargs/helpers");
17
+ const setup_1 = __importDefault(require("./commands/setup"));
18
+ function setupPackage() {
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ yield (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
21
+ .scriptName('zenstack-setup')
22
+ // @ts-expect-error yargs types are wrong
23
+ .command('$0', 'set up ZenStack', setup_1.default.builder, setup_1.default.handler)
24
+ .parseAsync();
25
+ });
26
+ }
27
+ exports.default = setupPackage;
28
+ //# sourceMappingURL=setup-package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-package.js","sourceRoot":"","sources":["../src/setup-package.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,kDAA0B;AAC1B,2CAAwC;AACxC,6DAA4C;AAE5C,SAA8B,YAAY;;QACtC,MAAM,IAAA,eAAK,EAAC,IAAA,iBAAO,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aAC7B,UAAU,CAAC,gBAAgB,CAAC;YAC7B,yCAAyC;aACxC,OAAO,CAAC,IAAI,EAAE,iBAAiB,EAAE,eAAY,CAAC,OAAO,EAAE,eAAY,CAAC,OAAO,CAAC;aAC5E,UAAU,EAAE,CAAC;IACtB,CAAC;CAAA;AAND,+BAMC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Utility for adding npm dependencies to "api" package
3
+ */
4
+ export declare const addApiPackages: (apiPackages: {
5
+ pkg: string;
6
+ dev?: boolean;
7
+ }[]) => {
8
+ title: string;
9
+ task: () => Promise<void>;
10
+ };
package/dist/utils.js ADDED
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.addApiPackages = void 0;
16
+ const cli_helpers_1 = require("@redwoodjs/cli-helpers");
17
+ const execa_1 = __importDefault(require("execa"));
18
+ /**
19
+ * Utility for adding npm dependencies to "api" package
20
+ */
21
+ const addApiPackages = (apiPackages) => ({
22
+ title: 'Adding required api packages...',
23
+ task: () => __awaiter(void 0, void 0, void 0, function* () {
24
+ const devPkgs = apiPackages.filter((p) => p.dev).map((p) => p.pkg);
25
+ if (devPkgs.length > 0) {
26
+ yield (0, execa_1.default)('yarn', ['add', '-D', ...devPkgs], { cwd: (0, cli_helpers_1.getPaths)().api.base });
27
+ }
28
+ const runtimePkgs = apiPackages.filter((p) => !p.dev).map((p) => p.pkg);
29
+ if (runtimePkgs.length > 0) {
30
+ yield (0, execa_1.default)('yarn', ['add', ...runtimePkgs], { cwd: (0, cli_helpers_1.getPaths)().api.base });
31
+ }
32
+ }),
33
+ });
34
+ exports.addApiPackages = addApiPackages;
35
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,wDAAkD;AAClD,kDAA0B;AAE1B;;GAEG;AACI,MAAM,cAAc,GAAG,CAAC,WAA6C,EAAE,EAAE,CAAC,CAAC;IAC9E,KAAK,EAAE,iCAAiC;IACxC,IAAI,EAAE,GAAS,EAAE;QACb,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAA,eAAK,EAAC,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACxE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAA,eAAK,EAAC,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,IAAA,sBAAQ,GAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/E,CAAC;IACL,CAAC,CAAA;CACJ,CAAC,CAAC;AAbU,QAAA,cAAc,kBAaxB"}
package/package.json CHANGED
@@ -1,47 +1,55 @@
1
1
  {
2
2
  "name": "@zenstackhq/redwood",
3
- "displayName": "ZenStack RedwoodJS setup and runtime package",
4
- "version": "0.5.0",
5
- "description": "For using ZenStack with RedwoodJS projects.",
3
+ "displayName": "ZenStack RedwoodJS Integration",
4
+ "version": "1.6.0",
5
+ "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/zenstackhq/zenstack"
9
9
  },
10
- "main": "index.js",
11
- "types": "index.d.ts",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
12
  "exports": {
13
13
  ".": {
14
- "default": "./index.js",
15
- "types": "./index.d.ts"
14
+ "default": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
16
  },
17
- "./setup": {
18
- "default": "./setup.js"
17
+ "./graphql": {
18
+ "default": "./dist/graphql.js",
19
+ "types": "./dist/graphql.d.ts"
19
20
  },
20
21
  "./package.json": {
21
22
  "default": "./package.json"
22
23
  }
23
24
  },
24
- "publishConfig": {
25
- "directory": "dist",
26
- "linkDirectory": true
25
+ "bin": "bin/cli",
26
+ "engines": {
27
+ "redwoodjs": ">=6.0.0"
27
28
  },
28
29
  "author": {
29
30
  "name": "ZenStack Team"
30
31
  },
31
32
  "homepage": "https://zenstack.dev",
32
33
  "license": "MIT",
33
- "bin": "node setup.js",
34
34
  "dependencies": {
35
35
  "colors": "1.4.0",
36
- "@zenstackhq/runtime": "1.4.1"
36
+ "ts-morph": "^16.0.0",
37
+ "@redwoodjs/cli-helpers": "^6.6.0",
38
+ "execa": "^5.0.0",
39
+ "listr2": "^6.0.0",
40
+ "terminal-link": "^2.0.0",
41
+ "yargs": "^17.7.2",
42
+ "@zenstackhq/runtime": "1.6.0"
37
43
  },
38
44
  "devDependencies": {
45
+ "@redwoodjs/graphql-server": "^6.6.0",
46
+ "@types/yargs": "^17.0.32",
39
47
  "graphql-yoga": "^5.0.2"
40
48
  },
41
49
  "scripts": {
42
50
  "clean": "rimraf dist",
43
- "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && copyfiles ./package.json ./README.md ../../LICENSE dist",
44
- "watch": "concurrently \"tsc --watch\" \"tsup-node --config ./tsup-browser.config.ts --watch\" \"tsup-node --config ./tsup-cross.config.ts --watch\"",
51
+ "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && pnpm pack dist --pack-destination '../../../.build'",
52
+ "watch": "tsc --watch",
45
53
  "lint": "eslint src --ext ts"
46
54
  }
47
55
  }
@@ -0,0 +1,49 @@
1
+ import { getPaths } from '@redwoodjs/cli-helpers';
2
+ import colors from 'colors';
3
+ import execa from 'execa';
4
+ import { CommandModule } from 'yargs';
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ async function runCommand(command: string, options: any) {
8
+ const args = ['zenstack', command];
9
+ for (const [name, value] of Object.entries(options)) {
10
+ args.push(name.length > 1 ? `--${name}` : `-${name}`);
11
+ if (typeof value === 'string') {
12
+ // Make sure options that take multiple quoted words
13
+ // are passed to zenstack with quotes.
14
+ value.split(' ').length > 1 ? args.push(`"${value}"`) : args.push(value);
15
+ }
16
+ }
17
+
18
+ console.log();
19
+ console.log(colors.green('Running ZenStack CLI...'));
20
+ console.log(colors.underline('$ npx ' + args.join(' ')));
21
+ console.log();
22
+
23
+ try {
24
+ await execa('npx', args, { cwd: getPaths().api.base, shell: true, stdio: 'inherit', cleanup: true });
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ } catch (e: any) {
27
+ process.exit(e?.exitCode || 1);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Creates a yargs command that passes all options to the ZenStack CLI command.
33
+ */
34
+ export function makePassthroughCommand(command: string): CommandModule<unknown> {
35
+ return {
36
+ command,
37
+ describe: `Run \`zenstack ${command} ...\``,
38
+ builder: (yargs) => {
39
+ return yargs
40
+ .strictOptions(false)
41
+ .strictCommands(false)
42
+ .strict(false)
43
+ .parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false });
44
+ },
45
+ handler: async ({ _, $0: _$0, ...options }) => {
46
+ await runCommand(command, options);
47
+ },
48
+ };
49
+ }
@@ -0,0 +1,262 @@
1
+ import { getPaths, updateTomlConfig } from '@redwoodjs/cli-helpers';
2
+ import colors from 'colors';
3
+ import execa from 'execa';
4
+ import fs from 'fs';
5
+ import { Listr, ListrTask } from 'listr2';
6
+ import path from 'path';
7
+ import terminalLink from 'terminal-link';
8
+ import { Project, SyntaxKind, type PropertyAssignment } from 'ts-morph';
9
+ import type { CommandModule } from 'yargs';
10
+ import { addApiPackages } from '../utils';
11
+
12
+ function updateToml() {
13
+ return {
14
+ title: 'Updating redwood.toml...',
15
+ task: () => {
16
+ updateTomlConfig('@zenstackhq/redwood');
17
+ },
18
+ };
19
+ }
20
+
21
+ function installDependencies() {
22
+ return addApiPackages([
23
+ { pkg: 'zenstack', dev: true },
24
+ { pkg: '@zenstackhq/runtime' },
25
+ { pkg: '@zenstackhq/redwood' },
26
+ ]);
27
+ }
28
+
29
+ // copy schema.prisma to schema.zmodel, and update package.json
30
+ function bootstrapSchema() {
31
+ return {
32
+ title: 'Bootstrapping ZModel schema...',
33
+ task: () => {
34
+ const apiPaths = getPaths().api;
35
+ const zmodel = path.join(path.dirname(apiPaths.dbSchema), 'schema.zmodel');
36
+ if (!fs.existsSync(zmodel)) {
37
+ fs.cpSync(apiPaths.dbSchema, zmodel);
38
+ } else {
39
+ console.info(
40
+ colors.blue(`Schema file "${path.relative(getPaths().base, zmodel)}" already exists. Skipping.`)
41
+ );
42
+ }
43
+
44
+ const pkgJson = path.join(apiPaths.base, 'package.json');
45
+ if (fs.existsSync(pkgJson)) {
46
+ const content = fs.readFileSync(pkgJson, 'utf-8');
47
+ const pkg = JSON.parse(content);
48
+ if (!pkg.zenstack) {
49
+ pkg.zenstack = {
50
+ schema: path.relative(apiPaths.base, zmodel),
51
+ prisma: path.relative(apiPaths.base, apiPaths.dbSchema),
52
+ };
53
+ fs.writeFileSync(pkgJson, JSON.stringify(pkg, null, 4));
54
+ }
55
+ }
56
+ },
57
+ };
58
+ }
59
+
60
+ // install ZenStack GraphQLYoga plugin
61
+ function installGraphQLPlugin() {
62
+ return {
63
+ title: 'Installing GraphQL plugin...',
64
+ task: async () => {
65
+ // locate "api/functions/graphql.[js|ts]"
66
+ let graphQlSourcePath: string | undefined;
67
+ const functionsDir = getPaths().api.functions;
68
+ if (fs.existsSync(path.join(functionsDir, 'graphql.ts'))) {
69
+ graphQlSourcePath = path.join(functionsDir, 'graphql.ts');
70
+ } else if (fs.existsSync(path.join(functionsDir, 'graphql.js'))) {
71
+ graphQlSourcePath = path.join(functionsDir, 'graphql.js');
72
+ }
73
+
74
+ if (!graphQlSourcePath) {
75
+ console.warn(
76
+ colors.yellow(`Unable to find handler source file: ${path.join(functionsDir, 'graphql.(js|ts)')}`)
77
+ );
78
+ return;
79
+ }
80
+
81
+ // add import
82
+ const project = new Project();
83
+ const graphQlSourceFile = project.addSourceFileAtPathIfExists(graphQlSourcePath)!;
84
+ let graphQlSourceFileChanged = false;
85
+ let identified = false;
86
+
87
+ const imports = graphQlSourceFile.getImportDeclarations();
88
+ if (!imports.some((i) => i.getModuleSpecifierValue() === '@zenstackhq/redwood')) {
89
+ graphQlSourceFile.addImportDeclaration({
90
+ moduleSpecifier: '@zenstackhq/redwood',
91
+ namedImports: ['useZenStack'],
92
+ });
93
+ graphQlSourceFileChanged = true;
94
+ }
95
+
96
+ // add "extraPlugins" option to `createGraphQLHandler` call
97
+ graphQlSourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((expr) => {
98
+ if (identified) {
99
+ return;
100
+ }
101
+
102
+ if (expr.getExpression().asKind(SyntaxKind.Identifier)?.getText() === 'createGraphQLHandler') {
103
+ const arg = expr.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
104
+ if (arg) {
105
+ identified = true;
106
+ const props = arg.getProperties();
107
+ const pluginsProp = props.find(
108
+ (p): p is PropertyAssignment =>
109
+ p.asKind(SyntaxKind.PropertyAssignment)?.getName() === 'extraPlugins'
110
+ );
111
+ if (pluginsProp) {
112
+ const pluginArr = pluginsProp.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
113
+ if (pluginArr) {
114
+ if (!pluginArr.getElements().some((e) => e.getText().includes('useZenStack'))) {
115
+ pluginArr.addElement('useZenStack(db)');
116
+ graphQlSourceFileChanged = true;
117
+ }
118
+ }
119
+ } else {
120
+ arg.addPropertyAssignment({
121
+ name: 'extraPlugins',
122
+ initializer: '[useZenStack(db)]',
123
+ });
124
+ graphQlSourceFileChanged = true;
125
+ }
126
+ }
127
+ }
128
+ });
129
+
130
+ if (!identified) {
131
+ console.warn(
132
+ colors.yellow(
133
+ 'Unable to determine how to install ZenStack GraphQL plugin. Please add it manually following https://zenstack.dev/docs/guides/redwood.'
134
+ )
135
+ );
136
+ }
137
+
138
+ if (graphQlSourceFileChanged) {
139
+ graphQlSourceFile.formatText();
140
+ }
141
+
142
+ // create type-def file to add `db` into global context
143
+ let contextTypeDefCreated = false;
144
+ if (graphQlSourcePath.endsWith('.ts')) {
145
+ const typeDefPath = path.join(getPaths().api.src, 'zenstack.d.ts');
146
+ if (!fs.existsSync(typeDefPath)) {
147
+ const typeDefSourceFile = project.createSourceFile(
148
+ typeDefPath,
149
+ `import type { PrismaClient } from '@prisma/client'
150
+
151
+ declare module '@redwoodjs/graphql-server' {
152
+ interface GlobalContext {
153
+ db: PrismaClient
154
+ }
155
+ }
156
+ `
157
+ );
158
+ typeDefSourceFile.formatText();
159
+ contextTypeDefCreated = true;
160
+ }
161
+ }
162
+
163
+ if (graphQlSourceFileChanged || contextTypeDefCreated) {
164
+ await project.save();
165
+ }
166
+ },
167
+ };
168
+ }
169
+
170
+ // eject templates used for `yarn rw generate service`
171
+ function ejectServiceTemplates() {
172
+ return {
173
+ title: 'Ejecting service templates...',
174
+ task: async () => {
175
+ if (fs.existsSync(path.join(getPaths().api.base, 'generators', 'service'))) {
176
+ console.info(colors.blue('Service templates already ejected. Skipping.'));
177
+ return;
178
+ }
179
+
180
+ await execa('yarn', ['rw', 'setup', 'generator', 'service'], { cwd: getPaths().api.base });
181
+ const serviceTemplateTsFile = path.join(
182
+ getPaths().api.base,
183
+ 'generators',
184
+ 'service',
185
+ 'service.ts.template'
186
+ );
187
+ const serviceTemplateJsFile = path.join(
188
+ getPaths().api.base,
189
+ 'generators',
190
+ 'service',
191
+ 'service.js.template'
192
+ );
193
+ const serviceTemplateFile = fs.existsSync(serviceTemplateTsFile)
194
+ ? serviceTemplateTsFile
195
+ : fs.existsSync(serviceTemplateJsFile)
196
+ ? serviceTemplateJsFile
197
+ : undefined;
198
+
199
+ if (!serviceTemplateFile) {
200
+ console.warn(colors.red('Unable to find the ejected service template file.'));
201
+ return;
202
+ }
203
+
204
+ // replace `db.` with `context.db.`
205
+ const templateContent = fs.readFileSync(serviceTemplateFile, 'utf-8');
206
+ const newTemplateContent = templateContent
207
+ .replace(/^import { db } from.*\n$/gm, '')
208
+ .replace(/return db\./g, 'return context.db.');
209
+ fs.writeFileSync(serviceTemplateFile, newTemplateContent);
210
+ },
211
+ };
212
+ }
213
+
214
+ function whatsNext() {
215
+ const zmodel = path.relative(getPaths().base, path.join(path.dirname(getPaths().api.dbSchema), 'schema.zmodel'));
216
+ const task: ListrTask = {
217
+ title: `What's next...`,
218
+ task: (_ctx, task) => {
219
+ task.title =
220
+ `What's next...\n\n` +
221
+ ` - Install ${terminalLink('IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
222
+ ` - Use "${zmodel}" to model database schema and access control.\n` +
223
+ ` - Run \`yarn rw @zenstackhq generate\` to regenerate Prisma schema and client.\n` +
224
+ ` - Learn ${terminalLink(
225
+ "how ZenStack extends Prisma's power",
226
+ 'https://zenstack.dev/docs/the-complete-guide/part1'
227
+ )}.\n` +
228
+ ` - Create a sample schema with \`yarn rw @zenstackhq sample\`.\n` +
229
+ ` - Join ${terminalLink(
230
+ 'Discord community',
231
+ 'https://discord.gg/Ykhr738dUe'
232
+ )} for questions and updates.\n`;
233
+ },
234
+ };
235
+ return task;
236
+ }
237
+
238
+ const setupCommand: CommandModule<unknown> = {
239
+ command: 'setup',
240
+ describe: 'Set up ZenStack environment',
241
+ builder: (yargs) => yargs,
242
+ handler: async () => {
243
+ const tasks = new Listr([
244
+ updateToml(),
245
+ installDependencies(),
246
+ bootstrapSchema(),
247
+ installGraphQLPlugin(),
248
+ ejectServiceTemplates(),
249
+ whatsNext(),
250
+ ]);
251
+
252
+ try {
253
+ await tasks.run();
254
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
+ } catch (e: any) {
256
+ console.error(colors.red(e.message));
257
+ process.exit(e?.exitCode || 1);
258
+ }
259
+ },
260
+ };
261
+
262
+ export default setupCommand;
package/src/graphql.ts ADDED
@@ -0,0 +1,61 @@
1
+ import { ForbiddenError } from '@redwoodjs/graphql-server';
2
+ import {
3
+ CrudFailureReason,
4
+ EnhancementOptions,
5
+ PrismaErrorCode,
6
+ ValidationError,
7
+ enhance,
8
+ isPrismaClientKnownRequestError,
9
+ type AuthUser,
10
+ } from '@zenstackhq/runtime';
11
+ import { type Plugin } from 'graphql-yoga';
12
+
13
+ /**
14
+ * Plugin options
15
+ */
16
+ export type ZenStackPluginOptions = EnhancementOptions;
17
+
18
+ /**
19
+ * A GraphQLYoga plugin that adds a ZenStack-enhanced PrismaClient into the context
20
+ * as `context.db`.
21
+ * @param db The original PrismaClient.
22
+ * @param getAuthUser A hook function for getting the current user. By default `context.currentUser` is used.
23
+ * @param options Options for creating the enhanced PrismaClient. See https://zenstack.dev/docs/reference/runtime-api#enhance for more details.
24
+ * @returns
25
+ */
26
+ export function useZenStack<PrismaClient extends object>(
27
+ db: PrismaClient,
28
+ getAuthUser?: (currentUser: unknown) => Promise<AuthUser>,
29
+ options?: ZenStackPluginOptions
30
+ ): Plugin<{ currentUser: unknown; db: PrismaClient }> {
31
+ return {
32
+ onContextBuilding: () => {
33
+ return async ({ context }) => {
34
+ const user = getAuthUser ? await getAuthUser(context.currentUser) : (context.currentUser as AuthUser);
35
+ context.db = enhance(
36
+ db,
37
+ { user },
38
+ {
39
+ errorTransformer: transformError,
40
+ ...options,
41
+ }
42
+ );
43
+ };
44
+ },
45
+ };
46
+ }
47
+
48
+ // Transforms ZenStack errors into appropriate RedwoodJS errors
49
+ function transformError(error: unknown) {
50
+ if (isPrismaClientKnownRequestError(error) && error.code === PrismaErrorCode.CONSTRAINED_FAILED) {
51
+ if (
52
+ error.meta?.reason === CrudFailureReason.ACCESS_POLICY_VIOLATION ||
53
+ error.meta?.reason === CrudFailureReason.RESULT_NOT_READABLE
54
+ ) {
55
+ return new ForbiddenError(error.message);
56
+ } else if (error.meta?.reason === CrudFailureReason.DATA_VALIDATION_VIOLATION) {
57
+ return new ValidationError(error.message);
58
+ }
59
+ }
60
+ return error;
61
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { makePassthroughCommand } from './cli-passthrough';
2
+ import setup from './commands/setup';
3
+
4
+ export const commands = [
5
+ setup,
6
+ makePassthroughCommand('generate'),
7
+ makePassthroughCommand('info'),
8
+ makePassthroughCommand('format'),
9
+ makePassthroughCommand('repl'),
10
+ ];
11
+ export * from './graphql';
@@ -0,0 +1,11 @@
1
+ import yargs from 'yargs';
2
+ import { hideBin } from 'yargs/helpers';
3
+ import setupCommand from './commands/setup';
4
+
5
+ export default async function setupPackage() {
6
+ await yargs(hideBin(process.argv))
7
+ .scriptName('zenstack-setup')
8
+ // @ts-expect-error yargs types are wrong
9
+ .command('$0', 'set up ZenStack', setupCommand.builder, setupCommand.handler)
10
+ .parseAsync();
11
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { getPaths } from '@redwoodjs/cli-helpers';
2
+ import execa from 'execa';
3
+
4
+ /**
5
+ * Utility for adding npm dependencies to "api" package
6
+ */
7
+ export const addApiPackages = (apiPackages: { pkg: string; dev?: boolean }[]) => ({
8
+ title: 'Adding required api packages...',
9
+ task: async () => {
10
+ const devPkgs = apiPackages.filter((p) => p.dev).map((p) => p.pkg);
11
+ if (devPkgs.length > 0) {
12
+ await execa('yarn', ['add', '-D', ...devPkgs], { cwd: getPaths().api.base });
13
+ }
14
+
15
+ const runtimePkgs = apiPackages.filter((p) => !p.dev).map((p) => p.pkg);
16
+ if (runtimePkgs.length > 0) {
17
+ await execa('yarn', ['add', ...runtimePkgs], { cwd: getPaths().api.base });
18
+ }
19
+ },
20
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext"],
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src/**/*.ts"]
8
+ }
package/index.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import { type AuthUser } from '@zenstackhq/runtime';
2
- import { type Plugin } from 'graphql-yoga';
3
- export declare function useZenStack<PrismaClient extends object>(db: PrismaClient): Plugin<{
4
- currentUser: AuthUser;
5
- db: PrismaClient;
6
- }>;
package/index.js DELETED
@@ -1,15 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useZenStack = void 0;
4
- const runtime_1 = require("@zenstackhq/runtime");
5
- function useZenStack(db) {
6
- return {
7
- onContextBuilding() {
8
- return ({ context }) => {
9
- context.db = (0, runtime_1.enhance)(db, { user: context === null || context === void 0 ? void 0 : context.currentUser });
10
- };
11
- },
12
- };
13
- }
14
- exports.useZenStack = useZenStack;
15
- //# sourceMappingURL=index.js.map
package/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAA6D;AAG7D,SAAgB,WAAW,CACvB,EAAgB;IAEhB,OAAO;QACH,iBAAiB;YACb,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;gBACnB,OAAO,CAAC,EAAE,GAAG,IAAA,iBAAO,EAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,WAAW,EAAE,CAAC,CAAC;YAC7D,CAAC,CAAC;QACN,CAAC;KACJ,CAAC;AACN,CAAC;AAVD,kCAUC"}
package/setup.d.ts DELETED
File without changes
package/setup.js DELETED
@@ -1,3 +0,0 @@
1
- "use strict";
2
- console.log('Setup redwood');
3
- //# sourceMappingURL=setup.js.map
package/setup.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":";AAAA,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC"}