Skip to content

Commit

Permalink
chore: misc fixes around Redwood (#917)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Jan 1, 2024
1 parent 0f73e56 commit 4ef3634
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 166 deletions.
21 changes: 0 additions & 21 deletions packages/misc/LICENSE

This file was deleted.

62 changes: 40 additions & 22 deletions packages/misc/redwood/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,50 @@ ZenStack is a full-stack toolkit built above Prisma ORM. It extends Prisma at th
- Multi-file schemas
- Custom attributes and functions in schemas

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

## Setting up
### Setting up

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`:
Run the following package setup command:

```toml
[experimental.cli]
autoInstall = true
[[experimental.cli.plugins]]
package = "@zenstackhq/redwood"
```bash
yarn rw setup package @zenstackhq/redwood
```

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

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

### Modeling data and access policies

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

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
You should run the following command after updating "schema.zmodel":

```bash
yarn rw @zenstackhq generate
```

[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
The command does the following things:

1. Regenerate "schema.prisma"
2. Run `prisma generate` to regenerate PrismaClient
3. Generates supporting JS modules for enforcing access policies at runtime

<!-- You can also use the
```bash
yarn rw @zenstackhq sample
```
command to browse a list of sample schemas and create from them.
command to browse a list of sample schemas and create from them. -->

## Development workflow
### Development workflow

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

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

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

## Deployment
### Deployment

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

```bash
yarn rw @zenstackhq generate && yarn rw deploy vercel
```

## Sample application
### Using the `@zenstackhq` CLI plugin

The `@zenstackhq/redwood` package registers a set of custom commands to the RedwoodJS CLI under the `@zenstackhq` namespace. You can run it with:

```bash
yarn rw @zenstackhq <cmd> [options]
```

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.

See the [CLI references](https://zenstack.dev/docs/reference/cli) for the full list of commands.

### Sample application

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

## Getting help
### Getting help

The best way to getting help and updates about ZenStack is by joining our [Discord server](https://discord.gg/Ykhr738dUe).
The best way to get help and updates about ZenStack is by joining our [Discord server](https://discord.gg/Ykhr738dUe).
3 changes: 3 additions & 0 deletions packages/misc/redwood/bin/cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('../dist/setup-package').default();
10 changes: 6 additions & 4 deletions packages/misc/redwood/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
"default": "./package.json"
}
},
"bin": "bin/cli",
"engines": {
"redwoodjs": ">=6.0.0"
},
"author": {
"name": "ZenStack Team"
},
Expand All @@ -37,17 +41,15 @@
"dependencies": {
"@zenstackhq/runtime": "workspace:*",
"colors": "1.4.0",
"ts-morph": "^16.0.0"
},
"peerDependencies": {
"ts-morph": "^16.0.0",
"@redwoodjs/cli-helpers": "^6.6.0",
"execa": "^5.0.0",
"listr2": "^6.0.0",
"terminal-link": "^2.0.0",
"yargs": "^17.7.2"
},
"devDependencies": {
"@redwoodjs/graphql-server": "^6.6.0",
"@redwoodjs/cli-helpers": "^6.6.0",
"@types/yargs": "^17.0.32",
"graphql-yoga": "^5.0.2"
}
Expand Down
4 changes: 1 addition & 3 deletions packages/misc/redwood/src/cli-passthrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ export function makePassthroughCommand(command: string): CommandModule<unknown>
.strictOptions(false)
.strictCommands(false)
.strict(false)
.parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false })
.help(false)
.version(false);
.parserConfiguration({ 'camel-case-expansion': false, 'boolean-negation': false });
},
handler: async ({ _, $0: _$0, ...options }) => {
await runCommand(command, options);
Expand Down
69 changes: 52 additions & 17 deletions packages/misc/redwood/src/commands/setup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getPaths } from '@redwoodjs/cli-helpers';
import { getPaths, updateTomlConfig } from '@redwoodjs/cli-helpers';
import colors from 'colors';
import execa from 'execa';
import fs from 'fs';
Expand All @@ -9,6 +9,15 @@ import { Project, SyntaxKind, type PropertyAssignment } from 'ts-morph';
import type { CommandModule } from 'yargs';
import { addApiPackages } from '../utils';

function updateToml() {
return {
title: 'Updating redwood.toml...',
task: () => {
updateTomlConfig('@zenstackhq/redwood');
},
};
}

function installDependencies() {
return addApiPackages([
{ pkg: 'zenstack', dev: true },
Expand Down Expand Up @@ -54,15 +63,15 @@ function installGraphQLPlugin() {
title: 'Installing GraphQL plugin...',
task: async () => {
// locate "api/functions/graphql.[js|ts]"
let sourcePath: string | undefined;
let graphQlSourcePath: string | undefined;
const functionsDir = getPaths().api.functions;
if (fs.existsSync(path.join(functionsDir, 'graphql.ts'))) {
sourcePath = path.join(functionsDir, 'graphql.ts');
graphQlSourcePath = path.join(functionsDir, 'graphql.ts');
} else if (fs.existsSync(path.join(functionsDir, 'graphql.js'))) {
sourcePath = path.join(functionsDir, 'graphql.js');
graphQlSourcePath = path.join(functionsDir, 'graphql.js');
}

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

// add import
const project = new Project();
const sf = project.addSourceFileAtPathIfExists(sourcePath)!;
let changed = false;
const graphQlSourceFile = project.addSourceFileAtPathIfExists(graphQlSourcePath)!;
let graphQlSourceFileChanged = false;
let identified = false;

const imports = sf.getImportDeclarations();
const imports = graphQlSourceFile.getImportDeclarations();
if (!imports.some((i) => i.getModuleSpecifierValue() === '@zenstackhq/redwood')) {
sf.addImportDeclaration({
graphQlSourceFile.addImportDeclaration({
moduleSpecifier: '@zenstackhq/redwood',
namedImports: ['useZenStack'],
});
changed = true;
graphQlSourceFileChanged = true;
}

// add "extraPlugins" option to `createGraphQLHandler` call
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((expr) => {
graphQlSourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((expr) => {
if (identified) {
return;
}
Expand All @@ -104,15 +113,15 @@ function installGraphQLPlugin() {
if (pluginArr) {
if (!pluginArr.getElements().some((e) => e.getText().includes('useZenStack'))) {
pluginArr.addElement('useZenStack(db)');
changed = true;
graphQlSourceFileChanged = true;
}
}
} else {
arg.addPropertyAssignment({
name: 'extraPlugins',
initializer: '[useZenStack(db)]',
});
changed = true;
graphQlSourceFileChanged = true;
}
}
}
Expand All @@ -126,8 +135,32 @@ function installGraphQLPlugin() {
);
}

if (changed) {
sf.formatText();
if (graphQlSourceFileChanged) {
graphQlSourceFile.formatText();
}

// create type-def file to add `db` into global context
let contextTypeDefCreated = false;
if (graphQlSourcePath.endsWith('.ts')) {
const typeDefPath = path.join(getPaths().api.src, 'zenstack.d.ts');
if (!fs.existsSync(typeDefPath)) {
const typeDefSourceFile = project.createSourceFile(
typeDefPath,
`import type { PrismaClient } from '@prisma/client'
declare module '@redwoodjs/graphql-server' {
interface GlobalContext {
db: PrismaClient
}
}
`
);
typeDefSourceFile.formatText();
contextTypeDefCreated = true;
}
}

if (graphQlSourceFileChanged || contextTypeDefCreated) {
await project.save();
}
},
Expand Down Expand Up @@ -185,8 +218,8 @@ function whatsNext() {
task: (_ctx, task) => {
task.title =
`What's next...\n\n` +
` - Install ${terminalLink('ZenStack IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
` - Use "${zmodel}" to model your database schema and access control.\n` +
` - Install ${terminalLink('IDE extensions', 'https://zenstack.dev/docs/guides/ide')}.\n` +
` - Use "${zmodel}" to model database schema and access control.\n` +
` - Run \`yarn rw @zenstackhq generate\` to regenerate Prisma schema and client.\n` +
` - Learn ${terminalLink(
"how ZenStack extends Prisma's power",
Expand All @@ -205,8 +238,10 @@ function whatsNext() {
const setupCommand: CommandModule<unknown> = {
command: 'setup',
describe: 'Set up ZenStack environment',
builder: (yargs) => yargs,
handler: async () => {
const tasks = new Listr([
updateToml(),
installDependencies(),
bootstrapSchema(),
installGraphQLPlugin(),
Expand Down
11 changes: 11 additions & 0 deletions packages/misc/redwood/src/setup-package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import setupCommand from './commands/setup';

export default async function setupPackage() {
await yargs(hideBin(process.argv))
.scriptName('zenstack-setup')
// @ts-expect-error yargs types are wrong
.command('$0', 'set up ZenStack', setupCommand.builder, setupCommand.handler)
.parseAsync();
}
Loading

0 comments on commit 4ef3634

Please sign in to comment.