diff --git a/.eslintrc.js b/.eslintrc.js index 999abeabb67e..0e7cb92fa6e4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -127,7 +127,7 @@ module.exports = { 'packages/api-server/src/**', 'packages/cli/src/**', 'packages/core/config/**', - 'packages/create-redwood-app/src/create-redwood-app.js', + 'packages/create-redwood-app/src/*.js', 'packages/internal/src/**', 'packages/prerender/src/**', 'packages/structure/src/**', @@ -135,6 +135,7 @@ module.exports = { 'packages/testing/config/**', 'packages/eslint-config/*.js', 'packages/record/src/**', + 'packages/telemetry/src/**', ], env: { es6: true, diff --git a/.github/renovate.json b/.github/renovate.json index f53c81778395..4947789da737 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,6 +1,16 @@ { - "extends": [ - "config:base" + "extends": ["config:base"], + "postUpdateOptions": ["yarnDedupeHighest"], + "assignees": ["@jtoar"], + "labels": ["release:chore"], + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch" + ], + "automerge": true + } ], "ignoreDeps": [ "boxen", @@ -15,20 +25,24 @@ "@types/node-fetch", "chalk", "pascalcase", - "node-fetch" - ], - "packageRules": [ - { - "matchUpdateTypes": [ - "minor", - "patch" - ], - "automerge": true - } - ], - "postUpdateOptions": ["yarnDedupeHighest"], - "assignees": [ - "@jtoar" - ], - "labels": ["release:chore"] + "node-fetch", + "@redwoodjs/api", + "@redwoodjs/api-server", + "@redwoodjs/auth", + "@redwoodjs/cli", + "@redwoodjs/codemods", + "@redwoodjs/core", + "@redwoodjs/create-redwood-app", + "@redwoodjs/eslint-config", + "@redwoodjs/forms", + "@redwoodjs/graphql-server", + "@redwoodjs/internal", + "@redwoodjs/prerender", + "@redwoodjs/record", + "@redwoodjs/router", + "@redwoodjs/structure", + "@redwoodjs/telemetry", + "@redwoodjs/testing", + "@redwoodjs/web" + ] } diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index e3cfc196a002..e7f1fe06666a 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -82,7 +82,6 @@ jobs: working-directory: ./tasks/e2e spec: | cypress/integration/01-tutorial/*.spec.js - cypress/integration/03-storybook/*.spec.js cypress/integration/04-logger/*.spec.js - name: Prepare for CLI checks by restoring 01-tutorial end state @@ -90,6 +89,10 @@ jobs: git restore . && git clean -df working-directory: ${{ steps.createpath.outputs.project_path }} + - name: Run `rw storybook` + run: yarn rw storybook --smoke-test + working-directory: ${{ steps.createpath.outputs.project_path }} + - name: Run `rw test api` run: yarn rw test api --no-watch working-directory: ${{ steps.createpath.outputs.project_path }} diff --git a/.github/workflows/projects_beta_automation.yml b/.github/workflows/projects_beta_automation.yml deleted file mode 100644 index 58652adedb3c..000000000000 --- a/.github/workflows/projects_beta_automation.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Projects Beta Automation - -on: - issues: - types: - - opened - pull_request_target: - types: - - opened - -jobs: - add_incoming_to_projects_beta: - runs-on: ubuntu-latest - - steps: - - name: Get project and status field IDs - env: - GITHUB_TOKEN: ${{ secrets.PROJECTS_BETA_AUTOMATION }} - run: | - gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='{ - organization(login: "redwoodjs") { - projectNext(number: 4) { - id - fields(first:20) { - nodes { - id - name - settings - } - } - } - } - }' > project_data.json - - echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV - echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name=="Status") | .id' project_data.json) >> $GITHUB_ENV - echo 'NEW_ISSUES_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name=="Status") | .settings | fromjson.options[] | select(.name=="New issues") | .id' project_data.json) >> $GITHUB_ENV - - - name: Set env var for issue - if: github.event_name == 'issues' - run: echo 'CONTENT_ID="${{ github.event.issue.node_id }}"' >> $GITHUB_ENV - - - name: Set env var for pull request - if: github.event_name == 'pull_request_target' - run: echo 'CONTENT_ID="${{ github.event.pull_request.node_id }}"' >> $GITHUB_ENV - - - name: Add to project - env: - GITHUB_TOKEN: ${{ secrets.PROJECTS_BETA_AUTOMATION }} - run: | - item_id="$(gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query=' - mutation($projectId: ID!, $contentId: ID!) { - addProjectNextItem(input: { projectId: $projectId, contentId: $contentId }) { - projectNextItem { - id - } - } - }' -f projectId=$PROJECT_ID -f contentId=$CONTENT_ID --jq '.data.addProjectNextItem.projectNextItem.id')" - - echo 'ITEM_ID='$item_id >> $GITHUB_ENV - - - name: Set status to "New issues" - env: - GITHUB_TOKEN: ${{ secrets.PROJECTS_BETA_AUTOMATION }} - run: | - gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query=' - mutation ($projectId: ID!, $itemId: ID!, $statusFieldId: ID!, $newIssuesOptionId: String!) { - updateProjectNextItemField( - input: { - projectId: $projectId - itemId: $itemId - fieldId: $statusFieldId - value: $newIssuesOptionId - } - ) { - projectNextItem { - id - } - } - }' -f projectId=$PROJECT_ID -f itemId=$ITEM_ID -f statusFieldId=$STATUS_FIELD_ID -f newIssuesOptionId=${{ env.NEW_ISSUES_OPTION_ID }} diff --git a/.github/workflows/telemetry-benchmarks.yaml b/.github/workflows/telemetry-benchmarks.yaml new file mode 100644 index 000000000000..973ba9e96778 --- /dev/null +++ b/.github/workflows/telemetry-benchmarks.yaml @@ -0,0 +1,96 @@ +name: Telemetry Checks and Benchmarks + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + cypress-run: + if: github.repository == 'redwoodjs/redwood' + strategy: + matrix: + os: ['ubuntu-latest'] + node-version: ['14', '16'] + fail-fast: true + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} | Node ${{ matrix.node-version }} latest + env: + REDWOOD_CI: 1 + REDWOOD_VERBOSE_TELEMETRY: 1 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + + - name: Cache yarn + uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + yarn- + + - name: Install dependencies and Build Framework + run: | + yarn install --immutable + yarn build:clean && yarn build:js + + - name: Create a temporary directory + id: createpath + run: | + project_path=$(mktemp -d -t redwood.XXXXXX) + echo "::set-output name=project_path::$project_path" + framework_path=$(pwd) + echo "::set-output name=framework_path::$framework_path" + + - name: Create Redwood Project + run: | + yarn babel-node packages/create-redwood-app/src/create-redwood-app.js ${{ steps.createpath.outputs.project_path }} --no-yarn-install + + - name: Add Framework Dependencies to Project + run: | + yarn project:deps ${{ steps.createpath.outputs.project_path }} + + - name: Run Project Yarn Install + run: | + yarn install + working-directory: ${{ steps.createpath.outputs.project_path }} + + - name: Copy Framework Packages to Project + run: | + yarn project:copy ${{ steps.createpath.outputs.project_path }} + + - name: Run `rw info` + run: | + yarn rw info + working-directory: ${{ steps.createpath.outputs.project_path }} + + - name: Run `rw build` + run: | + yarn rw build + working-directory: ${{ steps.createpath.outputs.project_path }} + + - name: Run "prisma migrate dev" + run: | + yarn rw prisma migrate dev --name ci-test + working-directory: ${{ steps.createpath.outputs.project_path }} + + - name: Run "g page" + run: | + yarn rw g page home / + working-directory: ${{ steps.createpath.outputs.project_path }} + + - name: Throw Error | Run `rw g sdl ` + run: | + yarn rw g sdl DoesNotExist + working-directory: ${{ steps.createpath.outputs.project_path }} + continue-on-error: true diff --git a/.gitpod.yml b/.gitpod.yml index cb7d60ce5775..cfcdd4963c0c 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -20,6 +20,7 @@ tasks: before: | export RWFW_PATH="/workspace/redwood" export RWJS_DEV_API_URL="http://localhost" + export REDWOOD_DISABLE_TELEMETRY=1 init: | cd /workspace/redwood yarn install diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 856b41fb4e76..c0b5ea2f7087 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,8 @@ Before interacting with the Redwood community, please read and understand our [C - [Code Organization](#code-organization) - [Local Setup](#local-setup) - [Redwood Framework](#redwood-framework) - - [Redwood Project: Create a Functional Test Project](#redwood-project-create-a-functional-test-project) + - [Redwood Project: Options](#redwood-project-options) + - [Redwood Functional Test Project](#redwood-functional-test-project) - [Testing the Framework in Your Project](#testing-the-framework-in-your-project) - [Testing the CLI in Your Project](#testing-the-cli-in-your-project) - [Browser-based Setup](#browser-based-setup) @@ -49,16 +50,20 @@ cd redwood yarn install ``` -### Redwood Project: Create a Functional Test Project +### Redwood Project: Options You'll almost always want to test the functionality of your changes to the Redwood Framework in a Redwood Project. When it comes to getting a Redwood Project to test your changes out in, you have several options: - run `yarn create redwood-app ./redwood-project` - `git clone` the [RedwoodJS Tutorial Blog](https://github.com/redwoodjs/redwood-tutorial) - use a project you've already created -- run `yarn run build:test-project ` from the root of your local copy of the Redwood Framework to create a functional test project 👀 +- create a functional test project using `yarn run build:test-project ` 👀 -**Using the functional test project might be the fastest and easiest way to test your changes.** You can create a Redwood Project that contains a lot of functionality in just a few minutes. For example, here's a brief overview of all the things `yarn run build:test-project ` does. It... +**Using the functional test project might be the fastest and easiest way to test your changes.** + +#### Redwood Functional Test Project + +You can create a Redwood Project that contains a lot of functionality in just a few minutes. For example, here's a brief overview of all the things `yarn run build:test-project ` does. It... 1. installs using the `create-redwood-app` template in the current branch of your Redwood Framework 2. with the current `canary` version of Redwood Packages (with the option to use the `latest` stable version) @@ -66,9 +71,7 @@ You'll almost always want to test the functionality of your changes to the Redwo 4. then applies code mods from the [Redwood tutorial](https://learn.redwoodjs.com/docs/tutorial/welcome-to-redwood/) to add functionality and styling 5. and initializes a Prisma DB migration for SQLite -Unless you've already got a project with a lot of functionality, it'd take quite some to add all of this yourself. Moreover, testing your changes in a project that has a lot of functionality will increase your confidence in the changes you're making. - -But how do you actually test your changes in the Redwood Framework in your Redwood Project? With another command, this time in the root of your Redwood Project: `yarn rwfw`. +Run `yarn run build:test-project ` from the root of your local copy of the Redwood Framework to create a functional test project. > Besides ``, `build:test-project` takes a few other options as well: > @@ -88,6 +91,10 @@ But how do you actually test your changes in the Redwood Framework in your Redwo > yarn run build:test-project ~/my-repos/redwood-project --typescript --link > ``` +Unless you've already got a project with a lot of functionality, it'd take quite some to add all of this yourself. Moreover, testing your changes in a project that has a lot of functionality will increase your confidence in the changes you're making. + +But how do you actually test your changes in the Redwood Framework in your Redwood Project? With another command, this time in the root of your Redwood Project: `yarn rwfw`. + ### Testing the Framework in Your Project As you make changes to the Redwood Framework, you'll want to see your changes reflected "live" in a Redwood Project. Since we're always looking for ways to make contributing to Redwood easier, there are a few workflows we've come up with. The one you'll want to use is `yarn rwfw`. @@ -145,7 +152,7 @@ To do that, use the `--cwd` option to set the current working directory to your cd redwood yarn build cd packages/cli -yarn dev --cwd +yarn dev --cwd ``` `yarn dev` runs the CLI and `--cwd` makes the command run in your Redwood Project. If you make a change to the code, remember to rebuild the packages! diff --git a/lerna.json b/lerna.json index 78e97f0dcf25..f33f9cdaf735 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.41.0", + "version": "0.42.1", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/package.json b/package.json index d5e11e20053c..321cae72b50a 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,10 @@ "babel-jest": "27.4.6", "babel-plugin-auto-import": "1.1.0", "babel-plugin-remove-code": "0.0.6", - "core-js": "3.20.2", - "cypress": "9.2.1", + "core-js": "3.20.3", + "cypress": "9.3.1", "cypress-wait-until": "1.7.2", - "eslint": "8.6.0", + "eslint": "8.7.0", "fast-glob": "3.2.11", "jest": "27.4.7", "jscodeshift": "0.13.0", @@ -42,7 +42,7 @@ "ora": "5.4.1", "rimraf": "3.0.2", "terminal-link": "2.1.1", - "typescript": "4.5.4", + "typescript": "4.5.5", "typescript-transform-paths": "3.3.1" }, "resolutions": { @@ -50,7 +50,7 @@ "prop-types": "15.8.1", "react-dom": "17.0.2", "react": "17.0.2", - "typescript": "4.5.4", + "typescript": "4.5.5", "vscode-languageserver-protocol": "3.15.3", "vscode-languageserver-types": "3.16.0", "vscode-languageserver": "6.1.1", diff --git a/packages/api-server/package.json b/packages/api-server/package.json index e25159f93f74..05faed326832 100644 --- a/packages/api-server/package.json +++ b/packages/api-server/package.json @@ -1,7 +1,7 @@ { "name": "@redwoodjs/api-server", "description": "Redwood's HTTP server for Serverless Functions", - "version": "0.41.0", + "version": "0.42.1", "bin": { "rw-api-server": "./dist/index.js", "rw-api-server-watch": "./dist/watch.js", @@ -16,9 +16,9 @@ "@babel/plugin-transform-runtime": "7.16.7", "ansi-colors": "4.1.1", "chalk": "4.1.2", - "chokidar": "3.5.2", + "chokidar": "3.5.3", "fast-json-parse": "1.0.3", - "fastify": "3.25.3", + "fastify": "3.27.0", "fastify-http-proxy": "6.2.1", "fastify-raw-body": "3.2.0", "fastify-static": "4.5.0", @@ -37,13 +37,14 @@ }, "devDependencies": { "@babel/cli": "7.16.7", - "@types/aws-lambda": "8.10.89", + "@babel/core": "7.16.7", + "@types/aws-lambda": "8.10.92", "@types/lodash.escape": "4.0.6", "@types/qs": "6.9.7", "@types/split2": "3.2.1", "aws-lambda": "1.0.7", "jest": "27.4.7", - "typescript": "4.5.4" + "typescript": "4.5.5" }, "scripts": { "build": "yarn build:js", diff --git a/packages/api/package.json b/packages/api/package.json index 47f167b84d42..f10f3128d773 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/api", - "version": "0.41.0", + "version": "0.42.1", "files": [ "dist", "logger", @@ -17,7 +17,7 @@ "jwks-rsa": "2.0.5", "md5": "2.3.0", "pascalcase": "1.0.0", - "pino": "7.6.3", + "pino": "7.6.4", "uuid": "8.3.2" }, "repository": { @@ -27,14 +27,15 @@ }, "devDependencies": { "@babel/cli": "7.16.7", - "@redwoodjs/auth": "0.41.0", + "@babel/core": "7.16.7", + "@redwoodjs/auth": "0.42.1", "@types/crypto-js": "4.1.0", - "@types/jsonwebtoken": "8.5.7", + "@types/jsonwebtoken": "8.5.8", "@types/md5": "2.3.1", "aws-lambda": "1.0.7", "jest": "27.4.7", - "supertokens-node": "8.4.0", - "typescript": "4.5.4" + "supertokens-node": "8.5.0", + "typescript": "4.5.5" }, "scripts": { "build": "yarn build:js && yarn build:types", diff --git a/packages/auth/package.json b/packages/auth/package.json index 3d164ccadf8b..b0be2bb31a24 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth", - "version": "0.41.0", + "version": "0.42.1", "files": [ "dist" ], @@ -11,22 +11,23 @@ "@auth0/auth0-spa-js": "1.19.4", "@azure/msal-browser": "2.21.0", "@babel/cli": "7.16.7", - "@clerk/clerk-js": "2.6.1", - "@clerk/clerk-sdk-node": "2.6.1", - "@clerk/types": "1.21.0", - "@supabase/supabase-js": "1.29.1", + "@babel/core": "7.16.7", + "@clerk/clerk-js": "2.10.0", + "@clerk/clerk-sdk-node": "2.7.2", + "@clerk/types": "1.23.0", + "@supabase/supabase-js": "1.29.4", "@types/netlify-identity-widget": "1.9.2", "@types/react": "17.0.38", - "firebase": "9.6.3", - "firebase-admin": "10.0.1", + "firebase": "9.6.4", + "firebase-admin": "10.0.2", "gotrue-js": "0.9.29", "jest": "27.4.7", "magic-sdk": "7.0.0", "netlify-identity-widget": "1.9.2", "nhost-js-sdk": "3.1.0", "react": "17.0.2", - "supertokens-auth-react": "0.17.9", - "typescript": "4.5.4" + "supertokens-auth-react": "0.18.4", + "typescript": "4.5.5" }, "repository": { "type": "git", diff --git a/packages/auth/src/authClients/clerk.ts b/packages/auth/src/authClients/clerk.ts index 354216fdecd4..a1762d2d02f6 100644 --- a/packages/auth/src/authClients/clerk.ts +++ b/packages/auth/src/authClients/clerk.ts @@ -1,8 +1,9 @@ -import { Clerk } from '@clerk/clerk-js' +import Clerk from '@clerk/clerk-js' import { UserResource as ClerkUserResource, SignInProps, SignUpProps, + SignOutCallback, } from '@clerk/types' import type { AuthClient } from '.' @@ -14,10 +15,10 @@ export type { Clerk } export type ClerkUser = ClerkUserResource & { roles: string[] | null } // In production, there is an issue where the AuthProvider sometimes captures -// Clerk as null (and then sends it over as () => null). This intercepts that +// Clerk as null. This intercepts that // issue and falls back to `window.Clerk` to access the client. -function clerkClient(propsClient: Clerk | (() => null)): Clerk | null { - if (!propsClient || (typeof propsClient === 'function' && !propsClient())) { +function clerkClient(propsClient: Clerk | null) { + if (!propsClient) { return window.Clerk ?? null } else { return propsClient @@ -30,16 +31,17 @@ export const clerk = (client: Clerk): AuthClientClerk => { client, login: async (options?: SignInProps) => clerkClient(client)?.openSignIn(options || {}), - logout: async () => new Promise((res) => clerkClient(client)?.signOut(res)), + logout: async (options?: SignOutCallback) => + clerkClient(client)?.signOut(options), signup: async (options?: SignUpProps) => clerkClient(client)?.openSignUp(options || {}), // Clerk uses the session ID PLUS the __session cookie. - getToken: async () => clerkClient(client)?.session.id, + getToken: async () => clerkClient(client)?.session?.id || null, getUserMetadata: async () => { return clerkClient(client)?.user ? { ...clerkClient(client)?.user, - roles: clerkClient(client)?.user.publicMetadata?.['roles'] ?? [], + roles: clerkClient(client)?.user?.publicMetadata?.['roles'] ?? [], } : null }, diff --git a/packages/cli/package.json b/packages/cli/package.json index b208d3ec5555..5c3677b7c36c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@redwoodjs/cli", "description": "The Redwood Command Line", - "version": "0.41.0", + "version": "0.42.1", "license": "MIT", "bin": { "redwood": "./dist/index.js", @@ -18,19 +18,20 @@ ], "dependencies": { "@prisma/sdk": "3.8.1", - "@redwoodjs/api-server": "0.41.0", - "@redwoodjs/internal": "0.41.0", - "@redwoodjs/prerender": "0.41.0", - "@redwoodjs/structure": "0.41.0", + "@redwoodjs/api-server": "0.42.1", + "@redwoodjs/internal": "0.42.1", + "@redwoodjs/prerender": "0.42.1", + "@redwoodjs/structure": "0.42.1", + "@redwoodjs/telemetry": "0.42.1", "boxen": "5.1.2", "camelcase": "6.3.0", "chalk": "4.1.2", - "concurrently": "6.5.1", + "concurrently": "7.0.0", "configstore": "3.1.5", - "core-js": "3.20.2", + "core-js": "3.20.3", "cross-env": "7.0.3", "decamelize": "5.0.0", - "dotenv-defaults": "3.0.0", + "dotenv-defaults": "5.0.0", "envinfo": "7.8.1", "execa": "5.1.1", "fast-glob": "3.2.11", @@ -53,10 +54,11 @@ }, "devDependencies": { "@babel/cli": "7.16.7", + "@babel/core": "7.16.7", "@types/listr": "0.14.4", "@types/node-fetch": "2.5.12", "jest": "27.4.7", - "typescript": "4.5.4" + "typescript": "4.5.5" }, "scripts": { "dev": "RWJS_CWD=../../__fixtures__/example-todo-main node dist/index.js", diff --git a/packages/cli/src/commands/__tests__/test.test.js b/packages/cli/src/commands/__tests__/test.test.js index 6e9ba3e6c2ac..75f28f1fbbe0 100644 --- a/packages/cli/src/commands/__tests__/test.test.js +++ b/packages/cli/src/commands/__tests__/test.test.js @@ -34,9 +34,6 @@ test('Creates/resets a test db when side has api, before calling jest', async () }) expect(execa.mock.results[1].value.cmd).toBe('yarn jest') - - // Api tests need to run sequentially for scenarios - expect(execa.mock.results[1].value.params).toContain('--runInBand') }) test('Runs tests for all available sides if no filter passed', async () => { @@ -65,9 +62,6 @@ test('Syncs or creates test database when the flag --db-push is set to true', as }) expect(execa.mock.results[1].value.cmd).toBe('yarn jest') - - // Api tests need to run sequentially for scenarios - expect(execa.mock.results[1].value.params).toContain('--runInBand') }) test('Skips test database sync/creation when the flag --db-push is set to false', async () => { diff --git a/packages/cli/src/commands/__tests__/type-check.test.js b/packages/cli/src/commands/__tests__/type-check.test.js index bf7b2b33ade0..9b9cfc3aee70 100644 --- a/packages/cli/src/commands/__tests__/type-check.test.js +++ b/packages/cli/src/commands/__tests__/type-check.test.js @@ -8,6 +8,17 @@ jest.mock('execa', () => }) ) +jest.mock('concurrently', () => + jest.fn((commands, options) => { + return { + commands, + options, + } + }) +) + +import '../../lib/mockTelemetry' + let mockedRedwoodConfig = { api: {}, web: {}, @@ -35,6 +46,7 @@ jest.mock('../../lib', () => { import path from 'path' +import concurrently from 'concurrently' import execa from 'execa' import { runCommandTask } from '../../lib' @@ -51,23 +63,22 @@ test('Should run tsc commands correctly, in order', async () => { generate: true, }) - expect(execa.mock.results[0].value.cmd).toEqual('yarn rw g types') + const concurrentlyArgs = concurrently.mock.results[0].value - // Ensure tsc command run correctly for web side - expect(execa.mock.results[1].value.cmd).toEqual('yarn tsc') - expect(execa.mock.results[1].value.params).toContain('--noEmit') - expect(execa.mock.results[1].value.params).toContain('--skipLibCheck') - expect(execa.mock.results[1].value.options.cwd).toBe( - path.normalize('myBasePath/web') - ) + expect(execa.mock.results[0].value.cmd).toEqual('yarn rw-gen') // Ensure tsc command run correctly for web side - expect(execa.mock.results[2].value.cmd).toEqual('yarn tsc') - expect(execa.mock.results[2].value.params).toContain('--noEmit') - expect(execa.mock.results[2].value.params).toContain('--skipLibCheck') - expect(execa.mock.results[2].value.options.cwd).toBe( - path.normalize('myBasePath/api') - ) + expect(concurrentlyArgs.commands).toContainEqual({ + cwd: path.join('myBasePath', 'web'), + command: 'yarn -s tsc --noEmit --skipLibCheck', + }) + // Ensure tsc command run correctly for web side + expect(concurrentlyArgs.commands).toContainEqual({ + cwd: path.join('myBasePath', 'api'), + command: 'yarn -s tsc --noEmit --skipLibCheck', + }) + // Ensure we have raw sequential output from tsc + expect(concurrentlyArgs.options).toEqual({ group: true, raw: true }) }) test('Should generate prisma client', async () => { @@ -77,14 +88,16 @@ test('Should generate prisma client', async () => { generate: true, }) - expect(execa.mock.results[0].value.cmd).toEqual('yarn rw g types') + const concurrentlyArgs = concurrently.mock.results[0].value - // Ensure tsc command run correctly for api side - expect(execa.mock.results[1].value.cmd).toEqual('yarn tsc') + expect(execa.mock.results[0].value.cmd).toEqual('yarn rw-gen') + + // Ensure tsc command run correctly for web side + expect(concurrentlyArgs.commands).toContainEqual({ + cwd: path.join('myBasePath', 'api'), + command: 'yarn -s tsc --noEmit --skipLibCheck', + }) expect(runCommandTask.mock.results[0].value[0]).toEqual( 'yarn prisma generate --schema="../../__fixtures__/example-todo-main/api/prisma"' ) - expect(execa.mock.results[1].value.options.cwd).toBe( - path.normalize('myBasePath/api') - ) }) diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 3a9916d9cd26..162357881481 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -8,6 +8,7 @@ import terminalLink from 'terminal-link' import { buildApi, loadAndValidateSdls } from '@redwoodjs/internal' import { detectPrerenderRoutes } from '@redwoodjs/prerender/detection' +import { timedTelemetry, errorTelemetry } from '@redwoodjs/telemetry' import { getPaths } from '../lib' import c from '../lib/colors' @@ -191,9 +192,12 @@ export const handler = async ({ }) try { - await jobs.run() + await timedTelemetry(process.argv, { type: 'build' }, async () => { + await jobs.run() + }) } catch (e) { console.log(c.error(e.message)) + errorTelemetry(process.argv, e.message) process.exit(1) } } diff --git a/packages/cli/src/commands/dataMigrate/install.js b/packages/cli/src/commands/dataMigrate/install.js index d1dbc6288e18..a19b3accf358 100644 --- a/packages/cli/src/commands/dataMigrate/install.js +++ b/packages/cli/src/commands/dataMigrate/install.js @@ -5,6 +5,8 @@ import fs from 'fs-extra' import Listr from 'listr' import terminalLink from 'terminal-link' +import { errorTelemetry } from '@redwoodjs/telemetry' + import { getPaths } from '../../lib' import c from '../../lib/colors' @@ -90,6 +92,7 @@ export const handler = async () => { try { await tasks.run() } catch (e) { + errorTelemetry(process.argv, e.message) console.error(c.error(e.message)) process.exit(e?.exitCode || 1) } diff --git a/packages/cli/src/commands/dataMigrate/up.js b/packages/cli/src/commands/dataMigrate/up.js index 2be574ac2d65..cefeaeb2347b 100644 --- a/packages/cli/src/commands/dataMigrate/up.js +++ b/packages/cli/src/commands/dataMigrate/up.js @@ -6,6 +6,7 @@ import VerboseRenderer from 'listr-verbose-renderer' import terminalLink from 'terminal-link' import { registerApiSideBabelHook } from '@redwoodjs/internal' +import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths } from '../../lib' import c from '../../lib/colors' @@ -26,6 +27,8 @@ const sortMigrations = (migrations) => { }) } +const SUPPORTED_EXTENSIONS = ['.js', '.ts'] + // Return the list of migrations that haven't run against the database yet const getMigrations = async (db) => { const basePath = path.join(getPaths().api.dataMigrations) @@ -37,7 +40,7 @@ const getMigrations = async (db) => { // gets all migrations present in the app const files = fs .readdirSync(basePath) - .filter((m) => path.extname(m) === '.js') + .filter((m) => SUPPORTED_EXTENSIONS.includes(path.extname(m))) .map((m) => { return { [m.split('-')[0]]: path.join(basePath, m), @@ -171,6 +174,7 @@ export const handler = async () => { } catch (e) { await db.$disconnect() report(counters) + errorTelemetry(process.argv, e.message) process.exit(e?.exitCode || 1) } } diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js index bc04cc43cff3..cd71e03fd20f 100644 --- a/packages/cli/src/commands/dev.js +++ b/packages/cli/src/commands/dev.js @@ -4,6 +4,7 @@ import concurrently from 'concurrently' import terminalLink from 'terminal-link' import { getConfig, getConfigPath, shutdownPort } from '@redwoodjs/internal' +import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths } from '../lib' import c from '../lib/colors' @@ -60,12 +61,17 @@ export const handler = async ({ schema: rwjsPaths.api.dbSchema, }) } catch (e) { + errorTelemetry( + process.argv, + `Error generating prisma client: ${e.message}` + ) console.error(c.error(e.message)) } try { await shutdownPort(getConfig().api.port) } catch (e) { + errorTelemetry(process.argv, `Error shutting down "api": ${e.message}`) console.error( `Error whilst shutting down "api" port: ${c.error(e.message)}` ) @@ -76,6 +82,7 @@ export const handler = async ({ try { await shutdownPort(getConfig().web.port) } catch (e) { + errorTelemetry(process.argv, `Error shutting down "web": ${e.message}`) console.error( `Error whilst shutting down "web" port: ${c.error(e.message)}` ) @@ -115,7 +122,7 @@ export const handler = async ({ } // TODO: Convert jobs to an array and supply cwd command. - concurrently( + const { result } = concurrently( Object.keys(jobs) .map((job) => { if (side.includes(job) || job === 'gen') { @@ -127,8 +134,13 @@ export const handler = async ({ prefix: '{name} |', timestampFormat: 'HH:mm:ss', } - ).catch((e) => { + ) + result.catch((e) => { if (typeof e?.message !== 'undefined') { + errorTelemetry( + process.argv, + `Error concurrently starting sides: ${e.message}` + ) console.error(c.error(e.message)) process.exit(1) } diff --git a/packages/cli/src/commands/generate/dataMigration/__tests__/__snapshots__/dataMigration.test.js.snap b/packages/cli/src/commands/generate/dataMigration/__tests__/__snapshots__/dataMigration.test.js.snap new file mode 100644 index 000000000000..c28192718940 --- /dev/null +++ b/packages/cli/src/commands/generate/dataMigration/__tests__/__snapshots__/dataMigration.test.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can generate a TS file with expected contents 1`] = ` +"import type { PrismaClient } from '@prisma/client' + +export default async ({ db }: { db: PrismaClient }) => { + // Migration here... +} +" +`; + +exports[`creates a JS file with expected contents 1`] = ` +"/** + * @typedef { import(\\"@prisma/client\\").PrismaClient } PrismaClient + * @param {{db: PrismaClient}} db + */ +export default async ({ db }) => { + // Migration here... +} +" +`; diff --git a/packages/cli/src/commands/generate/dataMigration/__tests__/dataMigration.test.js b/packages/cli/src/commands/generate/dataMigration/__tests__/dataMigration.test.js index 8468d2a656e8..3d32f32a0ab7 100644 --- a/packages/cli/src/commands/generate/dataMigration/__tests__/dataMigration.test.js +++ b/packages/cli/src/commands/generate/dataMigration/__tests__/dataMigration.test.js @@ -1,7 +1,7 @@ global.__dirname = __dirname import path from 'path' +import '../../../../lib/test' -import { loadGeneratorFixture } from '../../../../lib/test' import * as generator from '../dataMigration' const asyncForEach = async (array, callback) => { @@ -53,10 +53,14 @@ test('generates a file with a paramcase version of the passed name', async () => ) }) -test('creates a file with expected contents', async () => { +test('creates a JS file with expected contents', async () => { const files = await generator.files({ name: 'MoveUser' }) const filename = Object.keys(files)[0] - expect(files[filename]).toEqual( - loadGeneratorFixture('dataMigration', 'dataMigration.js') - ) + expect(files[filename]).toMatchSnapshot() +}) + +test('can generate a TS file with expected contents', async () => { + const files = await generator.files({ name: 'MoveUser', typescript: true }) + const filename = Object.keys(files)[0] + expect(files[filename]).toMatchSnapshot() }) diff --git a/packages/cli/src/commands/generate/dataMigration/__tests__/fixtures/dataMigration.js b/packages/cli/src/commands/generate/dataMigration/__tests__/fixtures/dataMigration.js deleted file mode 100644 index eae7a969899f..000000000000 --- a/packages/cli/src/commands/generate/dataMigration/__tests__/fixtures/dataMigration.js +++ /dev/null @@ -1,3 +0,0 @@ -export default async ({ db }) => { - // Migration here... -} diff --git a/packages/cli/src/commands/generate/dataMigration/dataMigration.js b/packages/cli/src/commands/generate/dataMigration/dataMigration.js index ec8c4447c200..8b7658deeeb6 100644 --- a/packages/cli/src/commands/generate/dataMigration/dataMigration.js +++ b/packages/cli/src/commands/generate/dataMigration/dataMigration.js @@ -7,6 +7,7 @@ import terminalLink from 'terminal-link' import { getPaths, writeFilesTask } from '../../../lib' import c from '../../../lib/colors' +import { yargsDefaults } from '../../generate' const POST_RUN_INSTRUCTIONS = `Next steps...\n\n ${c.warning( 'After writing your migration, you can run it with:' @@ -15,20 +16,21 @@ const POST_RUN_INSTRUCTIONS = `Next steps...\n\n ${c.warning( yarn rw dataMigrate up ` -const TEMPLATE_PATH = path.resolve( - __dirname, - 'templates', - 'dataMigration.js.template' -) +const TEMPLATE_PATHS = { + js: path.resolve(__dirname, 'templates', 'dataMigration.js.template'), + ts: path.resolve(__dirname, 'templates', 'dataMigration.ts.template'), +} -export const files = ({ name }) => { +export const files = ({ name, typescript }) => { const now = new Date().toISOString() const timestamp = now.split('.')[0].replace(/\D/g, '') - const outputFilename = `${timestamp}-${paramCase(name)}.js` + const basename = `${timestamp}-${paramCase(name)}` + const extension = typescript ? 'ts' : 'js' + const outputFilename = basename + '.' + extension const outputPath = path.join(getPaths().api.dataMigrations, outputFilename) return { - [outputPath]: fs.readFileSync(TEMPLATE_PATH).toString(), + [outputPath]: fs.readFileSync(TEMPLATE_PATHS[extension]).toString(), } } @@ -46,6 +48,11 @@ export const builder = (yargs) => { 'https://redwoodjs.com/reference/command-line-interface#generate-auth' )}` ) + + // Merge generator defaults in + Object.entries(yargsDefaults).forEach(([option, config]) => { + yargs.option(option, config) + }) } export const handler = async (args) => { diff --git a/packages/cli/src/commands/generate/dataMigration/templates/dataMigration.js.template b/packages/cli/src/commands/generate/dataMigration/templates/dataMigration.js.template index eae7a969899f..97c911ed07a4 100644 --- a/packages/cli/src/commands/generate/dataMigration/templates/dataMigration.js.template +++ b/packages/cli/src/commands/generate/dataMigration/templates/dataMigration.js.template @@ -1,3 +1,7 @@ +/** + * @typedef { import("@prisma/client").PrismaClient } PrismaClient + * @param {{db: PrismaClient}} db + */ export default async ({ db }) => { // Migration here... } diff --git a/packages/cli/src/commands/generate/dataMigration/templates/dataMigration.ts.template b/packages/cli/src/commands/generate/dataMigration/templates/dataMigration.ts.template new file mode 100644 index 000000000000..ee17f451c9f6 --- /dev/null +++ b/packages/cli/src/commands/generate/dataMigration/templates/dataMigration.ts.template @@ -0,0 +1,5 @@ +import type { PrismaClient } from '@prisma/client' + +export default async ({ db }: { db: PrismaClient }) => { + // Migration here... +} diff --git a/packages/cli/src/commands/generate/function/__tests__/function.test.ts b/packages/cli/src/commands/generate/function/__tests__/function.test.ts index 440b7dfac19b..189cf362dbbd 100644 --- a/packages/cli/src/commands/generate/function/__tests__/function.test.ts +++ b/packages/cli/src/commands/generate/function/__tests__/function.test.ts @@ -1,9 +1,9 @@ global.__dirname = __dirname -import path from 'path' - // Load shared mocks import '../../../../lib/test' +import path from 'path' + import * as functionGenerator from '../function' // Should be refactored as it's repeated diff --git a/packages/cli/src/commands/generate/function/function.js b/packages/cli/src/commands/generate/function/function.js index 2cb53ce61605..11ecd907c672 100644 --- a/packages/cli/src/commands/generate/function/function.js +++ b/packages/cli/src/commands/generate/function/function.js @@ -4,6 +4,8 @@ import camelcase from 'camelcase' import Listr from 'listr' import terminalLink from 'terminal-link' +import { errorTelemetry } from '@redwoodjs/telemetry' + import { getPaths, transformTSToJS, writeFilesTask } from '../../../lib' import c from '../../../lib/colors' import { yargsDefaults } from '../../generate' @@ -151,6 +153,7 @@ export const handler = async ({ name, force, ...rest }) => { ) console.info('') } catch (e) { + errorTelemetry(process.argv, e.message) console.error(c.error(e.message)) process.exit(e?.exitCode || 1) } diff --git a/packages/cli/src/commands/generate/helpers.js b/packages/cli/src/commands/generate/helpers.js index 13cf9c1739d8..55c658de89d3 100644 --- a/packages/cli/src/commands/generate/helpers.js +++ b/packages/cli/src/commands/generate/helpers.js @@ -7,6 +7,7 @@ import pascalcase from 'pascalcase' import terminalLink from 'terminal-link' import { ensurePosixPath, getConfig } from '@redwoodjs/internal' +import { errorTelemetry } from '@redwoodjs/telemetry' import { generateTemplate, getPaths, writeFilesTask } from '../../lib' import c from '../../lib/colors' @@ -183,6 +184,7 @@ export const createYargsForComponentGeneration = ({ await tasks.run() } catch (e) { + errorTelemetry(process.argv, e.message) console.error(c.error(e.message)) process.exit(e?.exitCode || 1) } diff --git a/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap b/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap index 42d6df74fba9..be8dd5e991fc 100644 --- a/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap +++ b/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap @@ -329,10 +329,88 @@ export default Routes", exports[`generates typescript pages 1`] = `undefined`; -exports[`generates typescript pages 2`] = `undefined`; +exports[`generates typescript pages 2`] = ` +"import TsFilesPage from './TsFilesPage' -exports[`generates typescript pages 3`] = `undefined`; +export const generated = () => { + return +} + +export default { title: 'Pages/TsFilesPage' } +" +`; + +exports[`generates typescript pages 3`] = ` +"import { render } from '@redwoodjs/testing/web' + +import TsFilesPage from './TsFilesPage' + +describe('TsFilesPage', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) +" +`; + +exports[`generates typescript pages 4`] = ` +"import { Link, routes } from '@redwoodjs/router' +import { MetaTags } from '@redwoodjs/web' + +type TsParamFilesPageProps = { + id: string +} + +const TsParamFilesPage = ({ id }: TsParamFilesPageProps) => { + return ( + <> + + +

TsParamFilesPage

+

+ Find me in ./web/src/pages/TSParamFilesPage/TSParamFilesPage.tsx +

+

+ My default route is named tsParamFiles, link to me with \` + TsParamFiles 42\` +

+

The parameter passed to me is {id}

+ + ) +} + +export default TsParamFilesPage +" +`; -exports[`generates typescript pages 4`] = `undefined`; +exports[`generates typescript pages 5`] = ` +"import { Link, routes } from '@redwoodjs/router' +import { MetaTags } from '@redwoodjs/web' -exports[`generates typescript pages 5`] = `undefined`; +type TsParamTypeFilesPageProps = { + id: number +} + +const TsParamTypeFilesPage = ({ id }: TsParamTypeFilesPageProps) => { + return ( + <> + + +

TsParamTypeFilesPage

+

+ Find me in ./web/src/pages/TSParamTypeFilesPage/TSParamTypeFilesPage.tsx +

+

+ My default route is named tsParamTypeFiles, link to me with \` + TsParamTypeFiles 42\` +

+

The parameter passed to me is {id}

+ + ) +} + +export default TsParamTypeFilesPage +" +`; diff --git a/packages/cli/src/commands/generate/page/__tests__/page.test.js b/packages/cli/src/commands/generate/page/__tests__/page.test.js index 1305bff38493..1d9f95cfa1cb 100644 --- a/packages/cli/src/commands/generate/page/__tests__/page.test.js +++ b/packages/cli/src/commands/generate/page/__tests__/page.test.js @@ -112,9 +112,11 @@ beforeAll(() => { typescriptParamTypeFiles = page.files({ name: 'TSParamTypeFiles', typescript: true, - tests: true, - stories: true, - ...page.paramVariants(pathName('{id:Int}', 'typescript-param-with-type')), + tests: false, + stories: false, + ...page.paramVariants( + pathName('/bazinga-ts/{id:Int}', 'typescript-param-with-type') + ), }) }) @@ -446,7 +448,7 @@ test('generates typescript pages', () => { expect( typescriptFiles[ path.normalize( - '/path/to/project/web/src/pages/TsFilesPage/TsFilesPage.stories.tsx' + '/path/to/project/web/src/pages/TSFilesPage/TSFilesPage.stories.tsx' ) ] ).toMatchSnapshot() @@ -454,7 +456,7 @@ test('generates typescript pages', () => { expect( typescriptFiles[ path.normalize( - '/path/to/project/web/src/pages/TsFilesPage/TsFilesPage.test.tsx' + '/path/to/project/web/src/pages/TSFilesPage/TSFilesPage.test.tsx' ) ] ).toMatchSnapshot() @@ -462,7 +464,7 @@ test('generates typescript pages', () => { expect( typescriptParamFiles[ path.normalize( - '/path/to/project/web/src/pages/TsParamFilesPage/TsParamFilesPage.tsx' + '/path/to/project/web/src/pages/TSParamFilesPage/TSParamFilesPage.tsx' ) ] ).toMatchSnapshot() @@ -470,7 +472,7 @@ test('generates typescript pages', () => { expect( typescriptParamTypeFiles[ path.normalize( - '/path/to/project/web/src/pages/TsParamTypeFilesPage/TsParamTypeFilesPage.tsx' + '/path/to/project/web/src/pages/TSParamTypeFilesPage/TSParamTypeFilesPage.tsx' ) ] ).toMatchSnapshot() diff --git a/packages/cli/src/commands/generate/page/page.js b/packages/cli/src/commands/generate/page/page.js index 6513bd044052..8cf017bb1527 100644 --- a/packages/cli/src/commands/generate/page/page.js +++ b/packages/cli/src/commands/generate/page/page.js @@ -5,6 +5,7 @@ import Listr from 'listr' import pascalcase from 'pascalcase' import { getConfig, generate as generateTypes } from '@redwoodjs/internal' +import { errorTelemetry } from '@redwoodjs/telemetry' import { addRoutesToRouterTask, @@ -243,6 +244,7 @@ export const handler = async ({ try { await tasks.run() } catch (e) { + errorTelemetry(process.argv, e.message) console.error(c.error(e.message)) process.exit(e?.exitCode || 1) } diff --git a/packages/cli/src/commands/generate/scaffold/__tests__/__snapshots__/scaffoldNoNest.test.js.snap b/packages/cli/src/commands/generate/scaffold/__tests__/__snapshots__/scaffoldNoNest.test.js.snap index 7e8555244de5..2f358b72353a 100644 --- a/packages/cli/src/commands/generate/scaffold/__tests__/__snapshots__/scaffoldNoNest.test.js.snap +++ b/packages/cli/src/commands/generate/scaffold/__tests__/__snapshots__/scaffoldNoNest.test.js.snap @@ -18,9 +18,9 @@ exports[`in javascript (default) mode creates a form component 1`] = ` FieldError, Label, TextField, + DatetimeLocalField, CheckboxField, NumberField, - DatetimeLocalField, TextAreaField, Submit, } from '@redwoodjs/forms' @@ -130,6 +130,22 @@ const PostForm = (props) => { + + + + + - @@ -181,20 +197,21 @@ const PostForm = (props) => { - - +