Skip to content

Commit

Permalink
Merge branch 'main' of github.com:redwoodjs/redwood into pp-add-api-u…
Browse files Browse the repository at this point in the history
…rl-config

* 'main' of github.com:redwoodjs/redwood:
  Add core-js to codemods dep for polyfills (#3448)
  add missing prepublish script (#3445)
  add missing deps (#3444)
  Remove codemods dependency on @redwoodjs/internal (#3443)
  Revert "add missing dep (#3441)" (#3442)
  add missing dep (#3441)
  Add @redwoodjs/codemods (#3411)
  Fix gitpod port forwarding (#3434)
  Re-vamp Redwood splash-page (#3183)
  Adds comments to directives and its templates (#3435)
  Readme updates to introduction technologies for GraphQL (#3433)
  • Loading branch information
dac09 committed Sep 29, 2021
2 parents b3e39d2 + 229af24 commit 316da11
Show file tree
Hide file tree
Showing 58 changed files with 2,561 additions and 140 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'fixtures',
'packages/structure/**',
'packages/core/**/__fixtures__/**/*',
'packages/codemods/**/__testfixtures__/**/*',
'packages/core/config/storybook/**/*',
'packages/create-redwood-app/template/web/src/Routes.tsx',
],
Expand Down
2 changes: 2 additions & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tasks:
openMode: split-left
init: |
eval $(gp env -e RWFW_PATH="/workspace/redwood")
eval $(gp env -e RWJS_DEV_API_URL="http://localhost")
mkdir /workspace/rw-test-app
command: |
cd /workspace/rw-test-app
Expand All @@ -27,6 +28,7 @@ tasks:
ports:
- port: 8911
onOpen: ignore
visibility: public
- port: 8910
onOpen: notify

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Here's a quick taste of the technologies a standard Redwood application will
use:

- [React](https://reactjs.org/)
- [GraphQL](https://graphql.org/) ([Apollo](https://github.com/apollographql))
- [GraphQL](https://graphql.org/) ([GraphQL Helix](https://github.com/contrawork/graphql-helix) + [Envelop](https://www.envelop.dev) + [Apollo Client](https://www.apollographql.com/docs/react))
- [Prisma](https://www.prisma.io/)
- [Jest](https://jestjs.io/)
- [Storybook](https://storybook.js.org/)
Expand All @@ -67,14 +67,16 @@ use:
- Forms with easy client- and/or server-side validation and error handling.
- [Hot module replacement](https://webpack.js.org/concepts/hot-module-replacement/) (HMR) for faster development.
- Database migrations (via Prisma 2).
- [Envelop Plugins](https://www.envelop.dev) that enhance the GraphQL lifecycle from context to execution
- Simple but powerful GraphQL Directives to validate access or transform resolved data
- Logging using [Pino](https://getpino.io) including to [transports](https://getpino.io/#/docs/transports)
- Signature verification and payload signing for handling incoming and outgoing Webhooks
- Page prerendering
- First class JAMstack-style deployment to [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), [Render](https://render.com/), and [Serverless](https://www.serverless.com/).

## Roadmap

We intend to ship v1 in the first quarter of 2021.
We intend to have a 1.0 release candidate before the end of 2021 and to release a truly production-ready 1.0 in early 2022.
To see all the features we plan on including in Redwood's first major release, you can check out our [Roadmap](https://redwoodjs.com/roadmap).

A framework like Redwood has a lot of moving parts; the Roadmap is a great way to get a high-level overview of where the framework is relative to where we want it to be. And since we link to all of our GitHub project boards, it's also a great way to get involved!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ exports[`creates a JavaScript validator directive: js directive 1`] = `
import { logger } from 'src/lib/logger'
export const schema = gql\`
\\"\\"\\"
Use @requireAdmin to validate access to a field, query or mutation.
\\"\\"\\"
directive @requireAdmin on FIELD_DEFINITION
\`
Expand Down Expand Up @@ -68,6 +71,9 @@ exports[`creates a TypeScript transformer directive: ts directive 1`] = `
import { logger } from 'src/lib/logger'
export const schema = gql\`
\\"\\"\\"
Use @bazingaFooBar to transform the resolved value to return a modified result.
\\"\\"\\"
directive @bazingaFooBar on FIELD_DEFINITION
\`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
import { logger } from 'src/lib/logger'

export const schema = gql`
"""
Use @${camelName} to transform the resolved value to return a modified result.
"""
directive @${camelName} on FIELD_DEFINITION
`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
import { logger } from 'src/lib/logger'

export const schema = gql`
"""
Use @${camelName} to validate access to a field, query or mutation.
"""
directive @${camelName} on FIELD_DEFINITION
`

Expand Down
1 change: 1 addition & 0 deletions packages/codemods/.babelrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { extends: '../../babel.config.js' }
139 changes: 139 additions & 0 deletions packages/codemods/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Codemods

- [Codemods](#codemods)
- [Purpose and Vision](#purpose-and-vision)
- [Package Leads](#package-leads)
- [Contributing](#contributing)

## Purpose and Vision

This package contains codemods that automate upgrading a Redwood project.

## Package Leads

- Daniel Choudhury (@dac09)
- Dominic Saadi (@jtoar)

## Usage

Applying a suite of codemods:

```
npx @redwood/codemods --from=v0.35.0 --to=v0.37.0
```

Applying a single one:

```
npx @redwood/codemods add-directives
```

## Contributing

You should be familiar with [jscodeshift](https://github.com/facebook/jscodeshift).
It's API isn't documented too well so we'll try to explain some of it here.

Like Babel and ESLint, jscodeshift is all about ASTs.
The difference is that it's overwriting files.
That means things that Babel doesn't care about, like spaces, styling (single quotes or double quotes, etc.), all of a sudden matter a lot.
The parser jscodeshift uses, [recast](https://github.com/benjamn/recast), knows how to preserve these details as much as possible.

### A Typical Transform

A typical transform looks something like this:

```typescript
// fooToBar.ts

import type { FileInfo, API } from 'jscodeshift'

module.exports = function (file: FileInfo, api: API) {
const j = api.jscodeshift

const root = j(file.source)

return root
.findVariableDeclarators('foo')
.renameTo('bar')
.toSource()
}
```

You can then run this transform on files via the CLI:

```
yarn run jscodeshift -t fooToBar.js foo.js
```

In this way, jscodeshift is similar to Jest in that it's a runner.

#### The API

In the example above, `file` is the file it's running the transformation on
and `jscodeshift` itself is actually a property of `api`.
Since it's used so much, you'll see this pattern a lot:

```javascript
const j = api.jscodeshift
```

`j` exposes the whole api, but it's also a function—it parses its argument into a `Collection`, jscodeshift's major type. It's similar to a javascript array and has many of the same methods (`forEach`, `map`, etc.).
The best way to familiarze yourself with its methods is to either 1) look at a bunch of examples or 2) [skim the source](https://github.com/facebook/jscodeshift/blob/main/src/Collection.js).

### Writing a transform

When beginning to write a transform, your best bet is to start by pasting the code you want to transform into [AST Explorer](https://astexplorer.net/). Use it to figure out what node you want, and then use one of `jscodeshift`'s `find` methods to find it:

```typescript
import type { FileInfo, API } from 'jscodeshift'

module.exports = function (file: FileInfo, api: API) {
const j = api.jscodeshift

const root = j(file.source)

/**
* This finds the line:
*
* ```
* import { ... } from '@redwoodjs/router'
* ```
*/
return root.find(j.ImportDeclaration, {
source: {
type: 'Literal',
value: '@redwoodjs/router',
},
})
}
```

Sometimes `jscodeshift` has a more-specific find method than `find`, like `findVariableDeclarators`. Use it when you can—it makes things a lot easier.
But note that these find methods aren't on `Collection`.
They're in the extensions:

- [Node](https://github.com/facebook/jscodeshift/blob/main/src/collections/Node.js)
- [JSXElement](https://github.com/facebook/jscodeshift/blob/main/src/collections/JSXElement.js)
- etc.

After you find what you're looking for, you usually want to replace it with something else.
Again, use AST Explorer to find out what the AST of that something else is.
Then, instead of using a type (like `j.ImportDeclaration`) to find it, use a builder (like `js.importDeclaration`—it's just the type lowercased) to make it.

Again, sometimes jscodeshift has a method that makes this trivial, especially for simple operations, like renaming or removing something (just use `renameTo` or `remove`).
But sometimes you'll just have to use one of the more generic methods: `replaceWith`, `inserterBefore`, `insertAfter`, etc.

## Testing

We're taking advantage of jscodeshift's integration with Jest to take most of the setup out of unit tests: https://github.com/facebook/jscodeshift#unit-testing.

### Testing TS fixtures...

To test TS files:
https://github.com/facebook/jscodeshift/blob/main/src/testUtils.js#L87

```javascript
defineTest(__dirname, 'addPrismaCreateToScenarios', null, 'realExample', {
parser: 'ts',
})
```
12 changes: 12 additions & 0 deletions packages/codemods/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/*.test.[jt]s?(x)'],
testPathIgnorePatterns: [
'__fixtures__',
'__tests__/utils/*',
'.d.ts',
'dist',
],
setupFilesAfterEnv: ['./jest.setup.js'],
testTimeout: 15000,
}
4 changes: 4 additions & 0 deletions packages/codemods/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
global.matchTransformSnapshot =
require('./testUtils/matchTransformSnapshot').matchTransformSnapshot
global.matchInlineTransformSnapshot =
require('./testUtils/matchTransformSnapshot').matchInlineTransformSnapshot
38 changes: 38 additions & 0 deletions packages/codemods/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@redwoodjs/codemods",
"version": "0.36.4",
"license": "MIT",
"bin": "./dist/codemods.js",
"files": [
"dist"
],
"dependencies": {
"deepmerge": "4.2.2",
"execa": "5.1.1",
"fast-glob": "3.2.7",
"findup-sync": "4.0.0",
"jscodeshift": "0.13.0",
"node-fetch": "2.6.1",
"require-directory": "2.1.1",
"tasuku": "1.0.2",
"toml": "3.0.0",
"vscode-ripgrep": "^1.12.0",
"yargs": "16.2.0",
"core-js": "3.17.3"
},
"scripts": {
"build": "yarn build:js",
"prepublishOnly": "yarn build",
"build:js": "babel src -d dist --extensions \".js,.ts\" --copy-files --no-copy-ignored --ignore \"src/**/__tests__/**\" --ignore \"src/**/__testfixtures__/**\"",
"build:watch": "nodemon --watch src --ignore dist,template --exec \"yarn build\"",
"test": "jest",
"test:watch": "yarn test --watch"
},
"devDependencies": {
"tempy": "1.0.1",
"@types/jest": "27.0.1",
"@types/jscodeshift": "^0.11.2",
"@types/require-directory": "^2.1.2",
"prettier": "2.4.1"
}
}
11 changes: 11 additions & 0 deletions packages/codemods/src/codemods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env node

import yargs from 'yargs'

// eslint-disable-next-line no-unused-expressions
yargs
.scriptName('codemods')
.example([['$0 add-directives', 'Run the add-directives codemod']])
.commandDir('./codemods', { recurse: true })
.demandCommand()
.strict().argv
21 changes: 21 additions & 0 deletions packages/codemods/src/codemods/v0.37.x/addDirectives/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add Directives

| | |
|:--------|:-----------------|
| version | `0.36` -> `0.37` |

Adds the `directives` directory to a Redwood project. Copies from the create-redwood-app template: https://github.com/redwoodjs/redwood/tree/main/packages/create-redwood-app/template/api/src/directives.

```
api
├── src
│ ├── directives
│ │ ├── requireAuth
│ │ ├── requireAuth.test.ts
│ │ │ └── requireAuth.ts
│ │ ├── skipAuth
│ │ │ ├── skipAuth.test.ts
│ │ │ └── skipAuth.ts
```

No jscodeshift is involved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import fs from 'fs'
import path from 'path'

import fetch from 'node-fetch'

import getRWPaths from '../../../lib/getRWPaths'

export const addDirectives = async () => {
const rwPaths = getRWPaths()

/**
* An object where the keys are resolved filenames and the values are (for the most part) URLs to fetch.
*
* @remarks
*
* Without the brackets areound requireAuthDir and skipAuthDir,
* the key would just be 'requireAuthDir' and 'skipAuthDir' instead of their values.
*/
const requireAuthDir = path.join(rwPaths.api.directives, 'requireAuth')
const skipAuthDir = path.join(rwPaths.api.directives, 'skipAuth')

const dirs = {
[requireAuthDir]: {
[path.join(requireAuthDir, 'requireAuth.ts')]:
'https://raw.githubusercontent.com/redwoodjs/redwood/main/packages/create-redwood-app/template/api/src/directives/requireAuth/requireAuth.ts',
[path.join(requireAuthDir, 'requireAuth.test.ts')]:
'https://raw.githubusercontent.com/redwoodjs/redwood/main/packages/create-redwood-app/template/api/src/directives/requireAuth/requireAuth.test.ts',
},
[skipAuthDir]: {
[path.join(skipAuthDir, 'skipAuth.ts')]:
'https://raw.githubusercontent.com/redwoodjs/redwood/main/packages/create-redwood-app/template/api/src/directives/skipAuth/skipAuth.test.ts',
[path.join(skipAuthDir, 'skipAuth.test.ts')]:
'https://raw.githubusercontent.com/redwoodjs/redwood/main/packages/create-redwood-app/template/api/src/directives/skipAuth/skipAuth.ts',
},
}

/**
* Now we just mkdirs and fetch files.
*/
fs.mkdirSync(rwPaths.api.directives)

for (const [dir, filenamesToUrls] of Object.entries(dirs)) {
fs.mkdirSync(dir)

for (const [filename, url] of Object.entries(filenamesToUrls)) {
const res = await fetch(url)
const text = await res.text()
fs.writeFileSync(filename, text)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import task from 'tasuku'

import { addDirectives } from './addDirectives'

export const command = 'add-directives'
export const description = 'Add directives'

export const handler = () => {
task('Add directives', async () => {
await addDirectives()
})
}
Loading

0 comments on commit 316da11

Please sign in to comment.