V0.38 Upgrade Guide 🛠

This is the upgrade guide for the v0.38 Release Notes . Read that first (if you haven’t already).

:sparkles: New Automated Codemods

Many upgrade steps can be completed using the @redwoodjs/codemods package (preview feature). Amazing! Codemods are applied using the npm subcommand npx, which runs a package without needing to install either locally or globally. Here’s an example codemod you’ll run from your project root directory:

:rocket: Codemod Available

npx @redwoodjs/codemods update-scaffold-styles

There are a few other things you should know in order to get the most out of this preview feature:

  1. Start by saving the current state of your project with git — there should be no uncommitted changes. This way, if you ever need to re-try or rollback a codemod step, you can easily revert using git.
  2. Always review the file changes after you run a codemod. They’re going to be accurate 90% of the time. The remaining 10% is on you.
  3. Heads up :construction_worker_man: Although we’ve verified these Codemods with npm v7, previous Codemods only worked with npm v6, which is included with Node.js v14. (npx is a subcommand of the npm command.) If you encounter errors, they are likely because you are using npm v7 (Node.js v16). Confirm either way by running npm --version. (Need to switch versions? Try nvm for Mac or Windows.)

1. Improved Scaffold Styles

Scaffold inputs now have a soft red glow when focused with errors. (See PR#3591)

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods update-scaffold-styles
Click for manual steps

Just add the following to your web/src/scaffold.css:

.rw-input-error:focus {
   outline: none;
   border-color: #c53030;
   box-shadow: 0 0 5px #c53030;
 }

2. Node.js v16

Redwood now supports Node.js v16 ‘Gallium’, the new LTS version.

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods update-node-engine
Click for manual steps

Make the following change to the “engines” property in your root ./package.json:

  "engines": {
-    "node": ">=14.x",
+    "node": ">=14.x <=16.x",
    "yarn": ">=1.15"
  },

3. redwood.toml: remove apiProxyPath and replace it with apiUrl

We’ve greatly improved Redwood’s api-path configuration in this release. While the update you need to make now is simple, you should also take a look at the other new options and configuration examples here in the Docs.

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods rename-api-proxy-path

:bulb: Using apiProxyPath elsewhere?

This codemod only renames apiProxyPath in redwood.toml.

Click for manual steps

Make the following change in your redwood.toml:

[web]
-  apiProxyPath = "/.redwood/functions" # value may be different
+  apiUrl = "/.redwood/functions" # value may be different

Other Changes

If you use apiProxyPath or __REDWOOD__API_PROXY_PATH anywhere else in your project, you’ll need to update it there as well.

Just “find” → “replace”:

  1. apiProxyPathapiUrl
  2. __REDWOOD__API_PROXY_PATHRWJS_API_GRAPHQL_URL

4. Migrate to the new Prisma db seed

With Prisma v3, we made some changes to the way you seed your database. Upgrading your Redwood project is a two-step process.

Step 1. Add the “prisma” property to package.json and create scripts/seed.js

:rocket: Codemod Available

To implement this step via automated codemod, run:

npx @redwoodjs/codemods update-seed-script

:bulb: Have an existing seed file?

Note that this codemod doesn’t migrate your existing seed file over—it just adds the template to the scripts directory.

Once you’ve copied over the logic, you can delete the seed file in api/db.

Click for manual steps You'll have to add the `"prisma"` property to your project's root `package.json`:
+ "prisma": {
+   "seed": "yarn rw exec seed"
+ }

This tells Prisma how to seed your database, using the seed file in the scripts directory.

If you don’t have a scripts directory at the root of your project (alongside api and web), make one now and a file named seed with the appropriate .js or .ts extension:

$ mkdir scripts
$ touch scripts/seed.js

Then copy the template for the scripts/seed.{js,ts} file from here and paste its contents into the one you just made. If your project is JS instead of TS, you’ll have to remove the types (just make the red squiggles go away).

Step 2. Move over logic from api/db/seed.{js,ts} to scripts/seed.{js,ts}

If you use the existing seed file in api/db, you’ll have to move the logic over to the file named seed.{js,ts} in the scripts directory. This should be relatively straightforward — not much has changed between the files aside from the way the db is imported and instantiated. Simply follow the new template in scripts/seed.{js,tx}, replacing the data and models with your existing Seed file information.

Once you’re done, or if you are not using the Seed file, you can delete the old seed file in api/db/seed.{js,ts}.

Be sure to read the updated docs.

:warning: Prisma v3 Upgrade Guide
For the most part, Redwood v0.38 internally handles the Prisma v3 upgrade. However, mileage will vary based on your project’s specific implementation. You should refer to Prisma’s v3 upgrade guide:


5. dbAuth: add Forgot/Reset Password

This is a required upgrade for projects using dbAuth.

If you have been using dbAuth since pre-0.37.0 there are a couple of steps to get Forgot/Reset Password added to your app.

For a full overview, see the introduction to PR#3429

Click here for code modification instructions

If you don’t mind overriding your existing Login and Signup pages then running the generator again will be the quickest way to get up to speed:

yarn rw g dbAuth --force

If you’ve customize those pages, or moved them, then you’ll want to follow the instructions in the Generate Pages section below.

In either case, you’ll still need to make the schema changes and auth.js function changes, however.

Schema Updates

Add two new columns to your User schema (whatever you named it):

model User {
  id                  Int     @id @default(autoincrement())
  email               String  @unique
  name                String?
  hashedPassword      String
  salt                String
  resetToken          String?   // <--- new
  resetTokenExpiresAt DateTime? // <--- new
}

In api/src/functions/auth.js you’ll need to let Redwood know what you named these columns:

authFields: {
  id: 'id',
  username: 'email',
  hashedPassword: 'hashedPassword',
  salt: 'salt',
  resetToken: 'resetToken',                   // <--- new
  resetTokenExpiresAt: 'resetTokenExpiresAt', // <--- new
},

functions/auth.js Updates

In addition to the update to the authFields object above, you’ll need a new key for forgotPassword and another for resetPassword options:

forgotPassword: {
  // Invoked after verifying that a user was found with the given username.
  // This is where you can send the user an email with a link to reset their
  // password. With the default dbAuth routes and field names, the URL
  // to reset the password will be:
  //
  // https://example.com/reset-password?resetToken=${user.resetToken}
  //
  // Whatever is returned from this function will be returned from
  // the `forgotPassword()` function that is destructured from `useAuth()`
  // You could use this return value to, for example, show the email
  // address in a toast message so the user will know it worked and where
  // to look for the email.
  handler: (user) => {
    return user
  },

  // How long the resetToken is valid for, in seconds (default is 24 hours)
  expires: 60 * 60 * 24,

  errors: {
    // for security reasons you may want to be vague here rather than expose
    // the fact that the email address wasn't found (prevents fishing for
    // valid email addresses)
    usernameNotFound:
      'If a user with that username exists you will receive an email with a link to reset your password',
    // if the user somehow gets around client validation
    usernameRequired: 'Gotta have a username yo',
  },
},

resetPassword: {
  // Invoked after the password has been successfully updated in the database.
  //
  // Return the user to automatically log them in
  // Return `false` and the user will NOT be logged in automatically. You
  // can then, for example, redirect them to the Login page
  handler: (user) => {
    return user
  },

  // If `false` then the new password MUST be different than the current one
  allowReusedPassword: false,

  errors: {
    // the resetToken is valid, but expired
    resetTokenExpired: 'This reset token is old, try again',
    // no user was found with the given resetToken
    resetTokenInvalid: 'This reset token was not found',
    // the resetToken was not present in the URL
    resetTokenRequired: 'You have to include a reset token',
    // new password is the same as the old password (apparently they did not forget it)
    reusedPassword: 'You must choose a new password',
  },
},

With comments removed, the whole file now looks something like:

import { db } from 'src/lib/db'
import { DbAuthHandler } from '@redwoodjs/api'

export const handler = async (event, context) => {

  const forgotPasswordOptions = {
    handler: (user) => {
      return user
    },
    expires: 60 * 60 * 24,
    errors: {
      usernameNotFound: 'Username not found',
      usernameRequired: 'Username is required',
    },
  }

  const loginOptions = {
    handler: (user) => {
      return user
    },
    errors: {
      usernameOrPasswordMissing: 'Both username and password are required',
      usernameNotFound: 'Username ${username} not found',
      incorrectPassword: 'Incorrect password for ${username}',
    },
    expires: 60 * 60 * 24 * 365 * 10,
  }

  const resetPasswordOptions = {
    handler: (user) => {
      return true
    },
    allowReusedPassword: true,
    errors: {
      resetTokenExpired: 'resetToken is expired',
      resetTokenInvalid: 'resetToken is invalid',
      resetTokenRequired: 'resetToken is required',
      reusedPassword: 'Must choose a new password',
    },
  }

  const signupOptions = {
    handler: ({ username, hashedPassword, salt, userAttributes }) => {
      return db.user.create({
        data: {
          email: username,
          hashedPassword: hashedPassword,
          salt: salt,
          // name: userAttributes.name
        },
      })
    },
    errors: {
      fieldMissing: '${field} is required',
      usernameTaken: 'Username `${username}` already in use',
    },
  }

  const authHandler = new DbAuthHandler(event, context, {
    db: db,
    authModelAccessor: 'user',
    authFields: {
      id: 'id',
      username: 'email',
      hashedPassword: 'hashedPassword',
      salt: 'salt',
      resetToken: 'resetToken',
      resetTokenExpiresAt: 'resetTokenExpiresAt',
    },
    forgotPassword: forgotPasswordOptions,
    login: loginOptions,
    resetPassword: resetPasswordOptions,
    signup: signupOptions,
  })

  return await authHandler.invoke()
}

Generate Pages

If you do not want to overwrite your existing Login and Signup pages, you can generate just the Forgot Password and Reset Password pages by adding a couple of flags to the generate command:

yarn rw generate dbAuth --skip-login --skip-signup

You’ll want to a link to the Forgot Password page in your Login page:

<Link to={routes.forgotPassword()}>Forgot Password?</Link>

Add Styles

You’ll need to add a couple of new styles to your web/src/scaffold.css file (assuming you are still using it for styling, if not then nevermind!):

.rw-toast {
  background-color: #fff;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
    'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
.rw-forgot-link {
  font-size: 0.75rem;
  color: #a0aec0;
  text-align: right;
  margin-top: 0.1rem;
}
.rw-forgot-link:hover {
  font-size: 0.75rem;
  color: #4299e1;
}

6. Magic.link: API token validation and decoding

Applies to projects using Magic.link Auth.

Other than adding a new API-side dependency, this is otherwise backwards compatible for existing projects that use magic link for authentication.

For a full overview, see the introduction to PR#3432

Click here for details and upgrade instructions

This change populates API-side requests context with the decoded magic link token. Only cryptographically validated and non-expired tokens are successfully decoded, otherwise an error is thrown and logged. See documentation describing the token format here: Getting Started with Decentralized ID (DID) Tokens

To update your project:

  1. Add the dependency yarn workspace api add @magic-sdk/admin
  2. (Optionally) Update your api-side handlers or functions to use the decoded token. For example you might update your getCurrentUser() in api/src/lib/auth.[js,ts] file to make use of the first argument containing the decoded token, to see an example, please refer to the template that will populate api/src/lib/auth.[js,ts] for new projects: redwood/magicLink.auth.js.template at main · redwoodjs/redwood · GitHub

7. Remove @webpack-cli/serve resolution in ./package.json

Projects using Redwood v0.37.3 (or v0.37.4) included a workaround in ./package.json that resolved an upstream bug from the webpack-cli dependency. As of v0.37.5, Projects no longer needed to “pin” the dependency @webpack-cli/serve to 1.5.2.

Click here for details and upgrade instructions

If your project has the following resolution, the following change must be made in ./package.json:

// ./package.json

{
  "private": true,
  "workspaces": {
    "packages": [
      "api",
      "web",
      "packages/*"
    ]
  },
  "devDependencies": {
    "@redwoodjs/core": "0.37.3"
  },
  "eslintConfig": {
    "extends": "@redwoodjs/eslint-config",
    "root": true
  },
  "engines": {
    "node": "14.x",
    "yarn": "1.x"
+  }
-  },
-  "resolutions": {
-    "@webpack-cli/serve": "1.5.2"
-  }
}

:checkered_flag: You’re almost done

Don’t forget to upgrade your packages with yarn rw upgrade!

Head back over to the Release Notes if you need specific instructions:

5 Likes

Thanks for another exciting release!

I have an app I recently started with v0.37 and I just tried the upgrade to v0.38. After following the upgrade instructions, I found that I was getting the following error upon running yarn rw dev:

web | [webpack-cli] You need to install 'webpack-dev-server' for running 'webpack serve'.

After some exploration I found that I had to remove the following block from my top-level package.json and then run yarn install again to clear the error:

-  "resolutions": {
-    "@webpack-cli/serve": "1.5.2"
-  },

I hadn’t added this manually to my recollection, so I assume it was formerly part of the package.json scaffolding and that this issue will affect more users. Probably worth adding to the upgrade guide!

5 Likes

Thanks @corbt! That change got lost in the v0.37.5 patch Release Notes. I’ve now added the instructions as Step 7 in the guide above. Good catch :rocket:

2 Likes

Thanks for an excellent upgrade! To get the reset password functionality to work, I also had to add resetToken fields to the User model in schema.prisma:

model User {

  • resetToken String?
  • resetTokenExpiresAt DateTime?
    }
1 Like

Adding those fields was mentioned in the detailed release notes, check out the “Schema Updates” section: Adds Forgot/Reset Password functionality to dbAuth by cannikin · Pull Request #3429 · redwoodjs/redwood · GitHub

Oh, alright. I though that the above guide was exhaustive since this was the only thing missing for me :slight_smile:

There’s a Schema Update section in the guide above too, once you expand the flippy triangle! :slight_smile:

Yikes :sweat_smile: Sorry!

I’m having trouble with this migration, one of my main queries throws after the upgrade and the error message is criptic. more details here but in essence the error is

Failed to validate the query: `Field does not exist on enclosing type.

Because there’s a prisma update I tried running yarn rw prisma migrate dev and it did create a new migration even though there were no changes to my schema, but that did not effect the error.

Has anyone seen this error?

(I also tried nuking my node_modules and reinstalling to be safe.)

You could try nuking your migrations completely and starting over, creating just 1 for the current state of your schema. I’ve never seen that message before, and it definitely seems to be coming from Prisma.

What’s import_db? I see you have import_db.db, whereas we’d normally start from db since it’s an instance of the Prisma Client.

So much great stuff !! Coming right when I need it !!

Coming from .35 I started a new project and copied all my work over. Everything’s working as expected, kudos !!

Note:

I get 1 warning when I: yarn rw dev

api | Building...  > ../.redwood/prebuild/api/src/services/events/events.js:805:4: warning: This case clause will never be evaluated because it duplicates an earlier case clause
api |     805 │     case 'reschedule':
1 Like

import_db.db is the built js. It’s just db. in my source projects.ts

I tried nuking the migrations like you said @rob and no change. I think the lead is later in the error 'Field does not exist on enclosing type.' at 'Query.findManyProject.Project.forkedFromId', emphasis on forkedFromId is this is a one-to-many self-relation, so might it be that prisma v3 has a bug with self-relations? should I raise this with them?

for context this is the project schema:

model Project {
  id          String   @id @default(uuid())
  title       String   @db.VarChar(25)
  description String? // markdown string
  code        String?
  mainImage   String? // link to cloudinary
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  user        User     @relation(fields: [userId], references: [id])
  userId      String
  deleted     Boolean  @default(false)
  cadPackage  CadPackage @default(openscad)
  socialCard  SocialCard?
  forkedFromId String?
  forkedFrom   Project? @relation("Fork", fields: [forkedFromId], references: [id])

  childForks   Project[] @relation("Fork")
  Comment      Comment[]
  Reaction     ProjectReaction[]
  @@unique([title, userId])
}

emphasis on:

  id          String   @id @default(uuid())
  forkedFromId String?
  forkedFrom   Project? @relation("Fork", fields: [forkedFromId], references: [id])
  childForks   Project[] @relation("Fork")

I don’t see anything obvious at first glance, but my best guess is that you’re getting bit by one of these breaking changes to Prisma:

I know @Danny encountered the “Renamed Aggregate Fields” specifically.

Thanks for the advice.

I finally got there and it was just I hadn’t nuked enough things, deleting the node_modules inside of api and also the dist directory fixed it.

I went as far as implementing a minunimum prisma project to replicate the issue to raise with prisma and when I couldn’t replicate, I went back to deleting things and then it worked. Annoying but glad it’s resolved.

side note, I think that step "4. Migrate to the new Prisma db seed" should have a third step of running either yarn rw prisma migrate dev or yarn rw prisma pull for upgrading to v3 like in the docs

running 0.38.1 in prod :slight_smile:

1 Like

Great idea! I’m adding that upgrade guide as a link to Step 4. Thanks :rocket:

1 Like

Just updated and also had to run the following

I am, however, still getting this error even after removing prettyPrint from logger.js

api | (node:55042) [PINODEP008] PinoWarning: prettyPrint is deprecated, use the pino-pretty transport instead

I’ve seen the comments here but not sure what the steps are to remove it.

Thanks for posting. Glad I’m not the only one encountering this! Nuking node_modules and dist did the trick to fix seeding. I did not have to wipe migrations.

1 Like