Skip to content

Commit 4ef3634

Browse files
authored
chore: misc fixes around Redwood (#917)
1 parent 0f73e56 commit 4ef3634

File tree

8 files changed

+164
-166
lines changed

8 files changed

+164
-166
lines changed

packages/misc/LICENSE

Lines changed: 0 additions & 21 deletions
This file was deleted.

packages/misc/redwood/README.md

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,50 @@ ZenStack is a full-stack toolkit built above Prisma ORM. It extends Prisma at th
99
- Multi-file schemas
1010
- Custom attributes and functions in schemas
1111

12-
Visit [homepage](https://zenstack.dev) for more details.
12+
You can find a more detailed integration guide [here](https://zenstack.dev/docs/guides/redwood).
1313

14-
## Setting up
14+
### Setting up
1515

16-
This package leverages RedwoodJS's experimental feature to register a custom CLI command to `yarn redwood`. To enable it, add the following to `redwood.toml`:
16+
Run the following package setup command:
1717

18-
```toml
19-
[experimental.cli]
20-
autoInstall = true
21-
[[experimental.cli.plugins]]
22-
package = "@zenstackhq/redwood"
18+
```bash
19+
yarn rw setup package @zenstackhq/redwood
2320
```
2421

25-
Then you can run `yarn rw @zenstackhq setup` to install ZenStack into your Redwood project. The setup command will:
22+
The setup command will:
2623

24+
1. Update "redwood.toml" to allow ZenStack CLI plugin.
2725
1. Install ZenStack dependencies.
28-
2. Copy your Prisma schema file "api/db/schema.prisma" to "api/db/schema.zmodel".
29-
3. Add a "zenstack" section into "api/package.json" to specify the location of both the "schema.prisma" and "schema.zmodel" files.
30-
4. Install a GraphQLYoga plugin in "api/src/functions/graphql.[ts|js]".
31-
5. Eject service templates and modify the templates to use `context.db` (ZenStack-enhanced `PrismaClient`) instead of `db` for data access.
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
3232

33-
## Modeling data and access policies
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.
3434

35-
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 regular Prisma schema file will be regenerated from the ZModel file when you run
35+
You should run the following command after updating "schema.zmodel":
3636

3737
```bash
3838
yarn rw @zenstackhq generate
3939
```
4040

41-
[The Complete Guide](https://zenstack.dev/docs/the-complete-guide/part1/) of ZenStack is the best way to learn how to author ZModel schemas. You can also use the
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
4248
4349
```bash
4450
yarn rw @zenstackhq sample
4551
```
4652
47-
command to browse a list of sample schemas and create from them.
53+
command to browse a list of sample schemas and create from them. -->
4854

49-
## Development workflow
55+
### Development workflow
5056

5157
The workflow of using ZenStack is very similar to using Prisma in RedwoodJS projects. The two main differences are:
5258

@@ -62,18 +68,30 @@ The workflow of using ZenStack is very similar to using Prisma in RedwoodJS proj
6268

6369
Other Prisma-related workflows like generation migration or pushing schema to the database stay unchanged.
6470

65-
## Deployment
71+
### Deployment
6672

6773
You should run the "generate" command in your deployment script before `yarn rw deploy`. For example, to deploy to Vercel, the command can be:
6874

6975
```bash
7076
yarn rw @zenstackhq generate && yarn rw deploy vercel
7177
```
7278

73-
## Sample application
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
7492

7593
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).
7694

77-
## Getting help
95+
### Getting help
7896

79-
The best way to getting help and updates about ZenStack is by joining our [Discord server](https://discord.gg/Ykhr738dUe).
97+
The best way to get help and updates about ZenStack is by joining our [Discord server](https://discord.gg/Ykhr738dUe).

packages/misc/redwood/bin/cli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env node
2+
3+
require('../dist/setup-package').default();

packages/misc/redwood/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
"default": "./package.json"
3030
}
3131
},
32+
"bin": "bin/cli",
33+
"engines": {
34+
"redwoodjs": ">=6.0.0"
35+
},
3236
"author": {
3337
"name": "ZenStack Team"
3438
},
@@ -37,17 +41,15 @@
3741
"dependencies": {
3842
"@zenstackhq/runtime": "workspace:*",
3943
"colors": "1.4.0",
40-
"ts-morph": "^16.0.0"
41-
},
42-
"peerDependencies": {
44+
"ts-morph": "^16.0.0",
45+
"@redwoodjs/cli-helpers": "^6.6.0",
4346
"execa": "^5.0.0",
4447
"listr2": "^6.0.0",
4548
"terminal-link": "^2.0.0",
4649
"yargs": "^17.7.2"
4750
},
4851
"devDependencies": {
4952
"@redwoodjs/graphql-server": "^6.6.0",
50-
"@redwoodjs/cli-helpers": "^6.6.0",
5153
"@types/yargs": "^17.0.32",
5254
"graphql-yoga": "^5.0.2"
5355
}

packages/misc/redwood/src/cli-passthrough.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ export function makePassthroughCommand(command: string): CommandModule<unknown>
4040
.strictOptions(false)
4141
.strictCommands(false)
4242
.strict(false)
43-
.parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false })
44-
.help(false)
45-
.version(false);
43+
.parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false });
4644
},
4745
handler: async ({ _, $0: _$0, ...options }) => {
4846
await runCommand(command, options);

packages/misc/redwood/src/commands/setup.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getPaths } from '@redwoodjs/cli-helpers';
1+
import { getPaths, updateTomlConfig } from '@redwoodjs/cli-helpers';
22
import colors from 'colors';
33
import execa from 'execa';
44
import fs from 'fs';
@@ -9,6 +9,15 @@ import { Project, SyntaxKind, type PropertyAssignment } from 'ts-morph';
99
import type { CommandModule } from 'yargs';
1010
import { addApiPackages } from '../utils';
1111

12+
function updateToml() {
13+
return {
14+
title: 'Updating redwood.toml...',
15+
task: () => {
16+
updateTomlConfig('@zenstackhq/redwood');
17+
},
18+
};
19+
}
20+
1221
function installDependencies() {
1322
return addApiPackages([
1423
{ pkg: 'zenstack', dev: true },
@@ -54,15 +63,15 @@ function installGraphQLPlugin() {
5463
title: 'Installing GraphQL plugin...',
5564
task: async () => {
5665
// locate "api/functions/graphql.[js|ts]"
57-
let sourcePath: string | undefined;
66+
let graphQlSourcePath: string | undefined;
5867
const functionsDir = getPaths().api.functions;
5968
if (fs.existsSync(path.join(functionsDir, 'graphql.ts'))) {
60-
sourcePath = path.join(functionsDir, 'graphql.ts');
69+
graphQlSourcePath = path.join(functionsDir, 'graphql.ts');
6170
} else if (fs.existsSync(path.join(functionsDir, 'graphql.js'))) {
62-
sourcePath = path.join(functionsDir, 'graphql.js');
71+
graphQlSourcePath = path.join(functionsDir, 'graphql.js');
6372
}
6473

65-
if (!sourcePath) {
74+
if (!graphQlSourcePath) {
6675
console.warn(
6776
colors.yellow(`Unable to find handler source file: ${path.join(functionsDir, 'graphql.(js|ts)')}`)
6877
);
@@ -71,21 +80,21 @@ function installGraphQLPlugin() {
7180

7281
// add import
7382
const project = new Project();
74-
const sf = project.addSourceFileAtPathIfExists(sourcePath)!;
75-
let changed = false;
83+
const graphQlSourceFile = project.addSourceFileAtPathIfExists(graphQlSourcePath)!;
84+
let graphQlSourceFileChanged = false;
7685
let identified = false;
7786

78-
const imports = sf.getImportDeclarations();
87+
const imports = graphQlSourceFile.getImportDeclarations();
7988
if (!imports.some((i) => i.getModuleSpecifierValue() === '@zenstackhq/redwood')) {
80-
sf.addImportDeclaration({
89+
graphQlSourceFile.addImportDeclaration({
8190
moduleSpecifier: '@zenstackhq/redwood',
8291
namedImports: ['useZenStack'],
8392
});
84-
changed = true;
93+
graphQlSourceFileChanged = true;
8594
}
8695

8796
// add "extraPlugins" option to `createGraphQLHandler` call
88-
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((expr) => {
97+
graphQlSourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((expr) => {
8998
if (identified) {
9099
return;
91100
}
@@ -104,15 +113,15 @@ function installGraphQLPlugin() {
104113
if (pluginArr) {
105114
if (!pluginArr.getElements().some((e) => e.getText().includes('useZenStack'))) {
106115
pluginArr.addElement('useZenStack(db)');
107-
changed = true;
116+
graphQlSourceFileChanged = true;
108117
}
109118
}
110119
} else {
111120
arg.addPropertyAssignment({
112121
name: 'extraPlugins',
113122
initializer: '[useZenStack(db)]',
114123
});
115-
changed = true;
124+
graphQlSourceFileChanged = true;
116125
}
117126
}
118127
}
@@ -126,8 +135,32 @@ function installGraphQLPlugin() {
126135
);
127136
}
128137

129-
if (changed) {
130-
sf.formatText();
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) {
131164
await project.save();
132165
}
133166
},
@@ -185,8 +218,8 @@ function whatsNext() {
185218
task: (_ctx, task) => {
186219
task.title =
187220
`What's next...\n\n` +
188-
` - Install ${terminalLink('ZenStack IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
189-
` - Use "${zmodel}" to model your database schema and access control.\n` +
221+
` - Install ${terminalLink('IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
222+
` - Use "${zmodel}" to model database schema and access control.\n` +
190223
` - Run \`yarn rw @zenstackhq generate\` to regenerate Prisma schema and client.\n` +
191224
` - Learn ${terminalLink(
192225
"how ZenStack extends Prisma's power",
@@ -205,8 +238,10 @@ function whatsNext() {
205238
const setupCommand: CommandModule<unknown> = {
206239
command: 'setup',
207240
describe: 'Set up ZenStack environment',
241+
builder: (yargs) => yargs,
208242
handler: async () => {
209243
const tasks = new Listr([
244+
updateToml(),
210245
installDependencies(),
211246
bootstrapSchema(),
212247
installGraphQLPlugin(),
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)