# Upgrade Guide

# 1.0.0

# Latest CLI update adds seed command

Nodewood CLI v0.17.0 added the seed command, which can run seed files to pre-populate your database with known-good records. This is mostly useful for development, when you are standing up a new Postgres container or want to wipe everything and start fresh. New projects created from now on will have all the required files in place, but existing projects will require a few changes to enable this command.

First, add the following entry to the scripts object in your root package.json:

    "seed": "knex seed:run",

Next, add the following entry to your knexfile.js, in the development object:

module.exports = {
  development: {
    // ...
    seeds: {
      directory: './app/seeds/development',
    },
  },

  test: {
    // ...

Finally, add a seed file. By default, seeds for development are located in app/seeds/development, but you can change that when modifying your knexfile, as above. A sample seed file follows:

exports.seed = async (knex) => {
  await knex('users').del();
  await knex('users').insert([
    {
      id: 1,
      created_at: '2022-01-01 00:09:48.856518+00',
      updated_at: '2022-01-01 00:09:48.856518+00',
      name: 'User One',
      email: 'one@user.com',
      email_confirmed: true,
      password: '$2a$10$4A7ATOTcApZ3kCjfSJE.lOnWH..OMISwxx5lrsH3D.aGfmwI5YldG',
      account_type: 'admin',
      last_logged_in_at: '2022-01-01 00:09:48+00',
      flags: '{}',
      jwt_series: 1,
      secure_flags: '{}',
    },
    {
      id: 2,
      created_at: '2022-01-02 19:49:47.041227+00',
      updated_at: '2022-01-02 19:49:47.041227+00',
      name: 'User Two',
      email: 'two@user.com',
      email_confirmed: true,
      password: '$2a$10$.tVfraKR9tSZejKuIpEY5OBMRlWEzTNmIenFi.adgki8hcm/MJq0a',
      account_type: 'user',
      last_logged_in_at: '2022-01-02 20:23:42+00',
      flags: '{}',
      jwt_series: 1,
      secure_flags: '{}',
    },
  ]);
  await knex.schema.raw('ALTER SEQUENCE users_id_seq RESTART WITH 3');

  await knex('teams').del();
  await knex('teams').insert([
    {
      id: 1,
      created_at: '2022-01-01 00:09:48.856518+00',
      updated_at: '2022-01-01 00:09:48.856518+00',
      name: 'Team One',
      currency: 'usd',
      stripe_customer_id: null,
      flags: '{}',
      secure_flags: '{}',
    },
    {
      id: 2,
      created_at: '2022-01-02 19:49:47.041227+00',
      updated_at: '2022-01-02 19:49:47.041227+00',
      name: 'Team Two',
      currency: 'usd',
      stripe_customer_id: null,
      flags: '{}',
      secure_flags: '{}',
    },
  ]);
  await knex.schema.raw('ALTER SEQUENCE teams_id_seq RESTART WITH 3');

  await knex('users_teams').del();
  await knex('users_teams').insert([
    {
      user_id: 1,
      team_id: 1,
      created_at: '2022-01-01 00:09:48.856518+00',
      updated_at: '2022-01-01 00:09:48.856518+00',
      role: 'owner',
    },
    {
      user_id: 2,
      team_id: 2,
      created_at: '2022-01-02 19:49:47.041227+00',
      updated_at: '2022-01-02 19:49:47.041227+00',
      role: 'owner',
    },
  ]);
};

This file will create two users on their own separate team, with no subscriptions. Both users' passwords are simply "password". You can add, remove, and modify from this as you choose to create a reasonable starting template for development.

# Positioning styling of UserMenu moved.

The AdminTemplate/UserMenu and AppTemplate/UserMenu components have had their margin-based positioning styling removed, and instead added to the template they're included in. This is where said positioning should have been to start, honestly. This will only affect you if you've customized your AppTemplate or AdminTemplate, in which case you can re-add the missing mt-4 mr-6 classes to the component in those files.

# Services are now automatically injected into Controllers and other Services.

Services commonly use other services to accomplish things. (For example, the SubscriptionsService calls out to the UsersService to send mail.) This is a good way to keep related code grouped together and create "building blocks" that can be re-used later.

One problem you can run into though, is circular dependencies - if AbcService wants to call a function from XyzService, and XyzService also wants to make a call in AbcService, they both try to require() each other, and Node will error out.

To solve this, all Services are now loaded at application start and injected into every Controller and Service automatically. All of your calls to this.abcService.doThing() will continue to work, but you will want to go and ensure you remove every instance of:

this.abcService = new AbcService({ db })

Services created in this way will not have other services automatically injected into them, which can brings you back around again to circular dependency, if you're not careful.

You will also need to modify the constructor of your existing Controllers to look like this:

  /**
   * Constructor.
   *
   * @param {MassiveJS} db - The MassiveJS db connection.
   * @param {Mailer} mailer - The mailer.
   * @param {Array<Service>} services - The services to inject into this controller.
   */
  constructor({ db, mailer, services } = {}) {
    super({ db, mailer, services });

Note that services has been added as parameter for the constructor and passed along to the super() call. This will ensure all the services are injected into the Controller.

# keys parameter in Service's update() and insert() is now optional.

As a safety measure, service update() and insert() functions required you to pass a value object used to update or insert, as well as an array of keys that would be used to restrict the value object. This was great when using a request body, for example, because it allowed you to really simplify your code (pass the entire body) while keeping it secure (only accepting certain values).

But if you were writing trusted code (for example, updating one table based solely on values from another), this forced you to do something like:

this.abcService.insert(tx, newValues, Object.keys(newValues));

This is fine, but it's overly-verbose and unnecessary. Going forward, if the keys parameter is missing, all values from the values parameter are used without restriction.

This doesn't require any mandatory changes to your code, but if you do have any code similar to the above, you can safely remove the final parameter there and have slightly less-verbose code.

# Healthcheck testing

The docker-compose file now specifies healthchecks for its services, so that dependent services are always running before starting services with dependencies. This necessitated adding a healthcheck endpoint to the API service, at /api/public/healthcheck.

If nodewood dev seems to be taking a long time, it is likely because this healthcheck is not working correctly, which is likely because you have customized the features.wood config property in app/config/app.js. Make sure that the healthcheck feature is enabled here, and it should resolve the issue.

You can also use this healthcheck for your own purposes, such as site uptime measuring.

# Pulumi deploy system added

If you already have a deploy system set up and working for your app, this likely won't concern you. But if you have yet to deploy your app to production, this provides another option.

Pulumi (opens new window) is an infrastructure-as-code system designed to make it easy and reliable to stand up and modify cloud infrastructure for your projects. The Nodewood implementation of Pulumi will set up and deploy your application to AWS, using RDS Postgres databases and Fargate for application image serving.

For more information, check out the Pulumi section in Deploying to Production.

# 0.20.1

# Nodewood CLI 0.16.2 now required

The add command in the Nodewood CLI has been modified so that the feature parameter is never pluralized. This has been the source of a bunch of weird behaviours in the past, and it is simpler both in the code and conceptually to just accept the feature name as given, and only appropriately pluralize the generated files, not the feature folder they're added to.

This requires updating your Nodewood version to 0.20.1, which has updated template files that expect this behaviour.

Some examples of how this command will behave after the update:

> nodewood add:feature dog
Generated controller: /app/features/dog/api/controllers/DogsController.js
Generated model: /app/features/dog/lib/models/DogModel.js

> nodewood add:feature box --plural=boxen
Generated controller: /app/features/box/api/controllers/BoxenController.js
Generated model: /app/features/box/lib/models/BoxModel.js

> nodewood add:controller box label
Generated controller: /app/features/box/api/controllers/LabelsController.js

> nodewood add:model box label
Generated model: /app/features/box/lib/models/LabelModel.js

This behaviour is much more consistent and easier to predict.

To update your Nodewood CLI tool, run: yarn global add @nodewood/cli.

# 0.20.0

# Vue 2 compatibility mode disabled

The Vue 2 compatibility build has been disabled with this release, meaning Nodewood now runs on standard Vue 3 code. If you haven't completed your conversion to Vue 3 yet and are now getting errors you cannot trace down to fix, you can re-enable compatibility mode with the following steps:

  1. In app/package.json and wood/package.json, add "@vue/compat": "^3.2.0", to your dependencies.
  2. In wood/vue.config.js, add the following inside the module.exports object:
  chainWebpack: (config) => {
    config.resolve.alias.set('vue', '@vue/compat');

    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options) => ({
        ...options,
        compilerOptions: { compatConfig: { MODE: 2 } },
      }));
  },
  1. In wood/ui/main.js, change the first line to import { createApp, configureCompat } from 'vue';, and immediately after the imports section, add the following:
configureCompat({ INSTANCE_ATTRS_CLASS_STYLE: false });

# Script runner added

You can now write and run scripts from the command-line. However, you'll need to add a new file in your app folder for this to work correctly. Create an app/cli folder, and inside of it, create a script.js file containing the following:

const { runScript } = require('../../wood/cli/script');

runScript();

You'll also need to update your ESLint configuration, so it knows about the root #cli folder. Update settings.import/resolver.alias-array.map in .eslintrc.js:

        map: [
          ['#api', [
            resolve(__dirname, 'app/api'),
            resolve(__dirname, 'wood/api'),
          ]],
          ['#cli', [
            resolve(__dirname, 'app/cli'),
            resolve(__dirname, 'wood/cli'),
          ]],
          ['#config', [
            resolve(__dirname, 'app/config'),
            resolve(__dirname, 'wood/config'),
          ]],
          ['#features', [
            resolve(__dirname, 'app/features'),
            resolve(__dirname, 'wood/features'),
          ]],
          ['#lib', [
            resolve(__dirname, 'app/lib'),
            resolve(__dirname, 'wood/lib'),
          ]],
          ['#ui', [
            resolve(__dirname, 'app/ui'),
            resolve(__dirname, 'wood/ui'),
          ]],
          ['@app', resolve(__dirname, 'app')],
          ['@wood', resolve(__dirname, 'wood')],
        ],

# Admin Dashboard modified to use rollup table, DashboardRollupScript added

The Admin Dashboard used to use a temporary calculation to tell you how many users/teams and MRR you had, and compared it to the start of the month. As of 0.20.0, these calculations come instead from a rollup table (admin_dashboard_rollups).

You will need to make sure you run migrations (nodewood migrate), and then run the script, with either nodewood script features/admin/cli/scripts/DashboardRollupScript in development, or node app/cli/script.js features/admin/cli/scripts/DashboardRollupScript in production. Additionally, you likely want to run the DashboardRollupScript daily via cron in production, to ensure that the data is always up-to-date.

# Adds model name to FIELDS definition

It's very common to be working with multiple Models in a single file, and you may need to access multiple Model FIELDS as well. Since those are all currently exported with the same name, it falls to you to destructure them as a different name in such cases.

To fix that, we've renamed all instances of model FIELDS to NAME_OF_MODEL_FIELDS. If you import these model fields anywhere in your app, you'll need to rename that field as well.

# Adds validator name to FORM_FIELDS definition

Similarly, every Validator exported a FORM_FIELDS object, which could get tricky when you wanted to validate multiple things in a single file.

To fix that, we've renamed all instances of validator FORM_FIELDS to NAME_OF_VALIDATOR_FORM_FIELDS. If you import these validator fields anywhere in your app, you'll need to rename that field as well.

# Package upgrades

Many core packages were upgraded. Make sure to review the changes to your app/package.json and ensure your app compiles and runs correctly.

You will need to remove the following packages from your app/package.json:

  • @vue/compiler-sfc
  • @vue/eslint-config-airbnb

You will need to update the following packages in your app/package.json:

  • "@vue/cli-plugin-unit-jest": "^5.0.0-rc.1"

You will need to add the following line to the exports of your root jest.config.js:

  testEnvironment: 'node',

You will need to update any usage of loadable in Composition API components to also pass the current instance as a parameter:

import { ref, getCurrentInstance } from 'vue'; // Add `getCurrentInstance` to this import

// ...

const loadingFunction = loadable(
  (values) => store.dispatch('StoreName/loadingFunction', values),
  'loadingFunction',
  getCurrentInstance(),
);

You will need to add the following lines to the rules section of your root .eslintrc.js:

    'vuejs-accessibility/click-events-have-key-events': 'off', // Disabled until a11y review
    'vuejs-accessibility/mouse-events-have-key-events': 'off', // Disabled until a11y review
    'vuejs-accessibility/no-autofocus': 'off', // Disabled until a11y review

After completing these steps, run yarn install and restart your Docker containers. If you get any strange errors, try deleting your node_modules folder and re-running yarn install.

# Troubleshooting

  • Getting strange build errors from your UI container?
    Delete all your node_modules (make sure to get the ones in app and wood as well), and restart your containers.
  • Missing tokens from account creation and password reset emails?
    Make sure to run migrations, then restart your containers.

# 0.19.0

# Rename UI config values

The following options in the ui config file have been renamed:

  • appSidebar renamed to appMenuEntries
  • adminSidebar renamed to adminMenuEntries
  • dropdown renamed to userMenuEntries

If you have extended this file, make sure to rename these options in your file as well.

# Switch to DataTable component

A new DataTable component has been added that handles the vast majority of cases where you will want to display tabular data loaded from a controller. For examples of how to use it, review the documentation.

It is recommended you switch to using this DataTable component in most cases, since it vastly reduces the amount of code necessary to display tables, and it comes pre-styled to display correctly in both desktop and mobile mode.

# 0.18.0

# Vue 3 - General

This release upgrades Vue from 2.6 to 3.2. Vue 3 in Nodewood is configured in "compatibility mode", which gives you time to convert your UI components into a form that is supported by Vue 3 (and for the Vue modules that Nodewood relies on to be converted as well).

To enable your project to work in Vue 3's compatibility mode, you must do the following:

  • In your root package.json:
    • Update eslint to "eslint": "^7.19.0".
  • In app/package.json:
    • Add the following to your dependencies:
      • "@vue/compat": "^3.2.0"
      • "@vue/compiler-sfc": "^3.2.0"
    • Update the following in your dependencies:
      • "vue": "^3.2.0",
      • "vue-router": "^4.0.10",
      • "vuex": "^4.0.2"
    • Remove the following from your dependencies:
      • vue-template-compiler
  • In your root .eslintrc.js:
    • Add the following section (or add these entries to your existing globals section):
      globals: {
        // Vue 3 compiler macros
        defineProps: 'readonly',
        defineEmits: 'readonly',
        defineExpose: 'readonly',
        withDefaults: 'readonly',
      },
    
    • Add the following to the rules section:
        'vue/no-v-for-template-key': 'off', // Conflicts with vue3
        'import/no-extraneous-dependencies': 'off', // Conflicts with wood folder imports
    
    • Change 'plugin:vue/recommended' to 'plugin:vue/vue3-recommended'.
  • If you have added a chainWebpack entry in your app/vue.config.js, you must make sure it includes the following:
  chainWebpack: (config) => {
    config.resolve.alias.set('vue', '@vue/compat');

    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options) => ({
        ...options,
        compilerOptions: { compatConfig: { MODE: 2 } },
      }));
  },
  • Run yarn install.
  • Finally, update your app/ui/main.js to the following:
import app from '@wood/ui/main';

export default app;

If you have customized your app's main.js, you'll have to add those customizations back in, but this is much easier now due to new functions in #ui/configure that can be extended or overridden.

# Vue 3 - Stores

The application initialization has been changed to be a synchronous process, so store initialization must also be changed to be synchronous. You will need to change the initStores function in your ui/init.js files to look like the following:

/* eslint-disable global-require */
store.registerModule('Example', require('#features/examples/ui/stores/ExampleStore'));
// DO NOT REMOVE: Generated stores will be added above this line
/* eslint-enable */

# Vue 3 - Routes

Instead of adding routes to a router object, routes are collected into an array and passed into the router object at creation. This function is no longer asynchronous, either, as application initialization has changed to be synchronous instead. This means that the initRoutes function in your ui/init.js files will need to change to look like the following:

/**
 * Initialize Vue routes for this feature.
 *
 * @return {Array}
 */
initRoutes() {
  const routes = [];

  routes.push({
    path: '/example',
    name: 'example',
    component: () => import(/* webpackChunkName: "example" */ '#features/examples/ui/pages/ExamplePage'),
  });

  // DO NOT REMOVE: Generated routes will be added above this line

  return routes;
}

# Vue 3 - Charts

vue-chartjs is not Vue 3-compatible and likely will not be updated to be so, so we have switched to vue-chart-3. It can be used mostly the same way, but you may need to consult their documentation (opens new window).

Once your charts have been converted to vue-chart-3, make sure to remove vue-chartjs from your app/package.json.

# Vue 3 - Adding PrimeVue

This release adds the PrimeVue component library (opens new window), primarily to replace vue-js-modal (which looks likely that it will not be updated to work with Vue 3). In order to enable this, you will need to create a postcss.config.js file in your project root:

module.exports = {
  plugins: [
    require('autoprefixer'), // eslint-disable-line global-require, import/no-extraneous-dependencies
  ],
};

# Vue 3 - Dialog component changed to PrimeVue Dialog

The Dialog component Nodewood was using (vue-js-modal) is stuck on Vue 2, and is likely not going to be updated to be compatible with Vue 3. As a result, in this release, we have changed all the Nodewood dialogues to use the PrimeVue Dialog component, and you will need to do so as well.

First, you will need to add a visible entry in your data entry for your dialog component:

data: () => ({
  visible: false,
}),

Next, you'll need to replace your <modal> tag with a <Dialog> tag:

<Dialog
  :visible="visible"
  :closable="false"
  header="Edit User"
>

The header property replaces the contents of the h2.dialog-title, and by default, all PrimeVue Dialogs come with a close button. To retain the style Nodewood dialogs had before, set closable to false. The visible property controls whether the dialog is shown or hidden, so you'll need to change your openDialog() and closeDialog() functions:

openDialog() {
  this.visible = true;
},
closeDialog() {
  this.visible = false;
},

Styling dialogs is now much easier! You can set the :style property on your Dialog component and style it directly with CSS, and use :breakpoints to add responsive styles. For a dialog that starts at 50% width, but switches to 100% width when the viewport is less than 640px, you can do the following:

<Dialog
  :visible="visible"
  :style="{ width: '50vw' }"
  :breakpoints="{'640px': '100vw'}"
  :closable="false"
  header="Edit User"
>

Finally, make sure to remove vue-js-modal from your app/package.json file, to keep your bundle size small.

# Vue 3 - Toast component changed to PrimeVue Toast

vue-toasted (opens new window) has been removed, since it is only Vue 2-compatible and looks like it will not be updated for 3. Instead, it has been replaced with [PrimeVue Toasts](https://www.primefaces.org/primevue/showcase/#/toast, easily accessible through helper functions in #ui/lib/toast.js:

import errorToast from '#ui/lib/toast';

// ...

errorToast('Credit card invalid.');

The toast title and other options can be passed in as additional parameters to further customize the toast.

Once finished, make sure to remove vue-toasted from your app/package.json and run yarn install.

# Vue 3 - vue-loadable removed, replaced with vue-is-loading

vue-loadable (opens new window) has been removed, since it is only Vue 2-compatible and looks like it will not be updated for Vue 3. Instead, it has been replaced with vue-is-loading (opens new window), a Vue 3 javascript port. This is a drop-in port, and can be used as before.

If using Vue 3's Composition API (opens new window), you can use vue-is-loading's loadable function directly, like so:

<template>
  <div>
    <loading-spinner v-if="$isLoading('saveUser')" />

    <button @click="submit">
      Submit
    </button>
  </div>
</template>

<script setup>
import { ref, getCurrentInstance } from 'vue';
import { loadable } from 'vue-is-loading';
import LoadingSpinner from '#ui/components/LoadingSpinner';

const name = ref('');
const saveUser = loadable(
  (values) => store.dispatch('Users/saveUser', values),
  'saveUser',
  getCurrentInstance(),
);
const submit = async () => {
  await saveUser({ name });
};
</script>

Once finished, make sure to remove vue-loadable from your app/package.json and add "vue-is-loading": "1.0.0",, then run yarn install.

# Vue 3 - v-click-outside removed, replaced with click-outside-vue3

v-click-outside (opens new window) has been removed, since it is only Vue 2-compatible and looks like it will not be updated for Vue 3. Instead, it has been replaced with click-outside-vue3 (opens new window), a Vue 3 port. This is a drop-in port, and can be used as before.

You shouldn't need to make any changes to your code, as this directive is added in wood and shouldn't actually be referenced anywhere in app.

# Vue 3 - Final migration notes

From here, you will have to upgrade your own code from Vue 2 code to Vue 3 code (including any file generated by nodewood add). Lint messages and console warnings should indicate fairly clearly what changes you need to make (searching for the specific lint rule in question will give you examples for how to convert from "bad" code to "good" code), but the Vue docs have a more in-depth guide to migration (opens new window), if you have more advanced issues.

You may also need to update or switch libraries to Vue 3 compatible versions.

If you get stuck, feel free to email hello@nodewood.com with the details of the issue you're experiencing.

# Vue 3 - Troubleshooting

  • This dependency was not found: "vue"

Delete yarn.lock and run yarn install to get a fresh copy of the trouble libraries.

  • Syntax Error: Error: No PostCSS Config found in:

Make sure the add the postcss.config.js to your root folder as mentioned in the "Adding PrimeVue" section.

  • Miscellaneous linter errors

If you have new and persistent linter errors when working on your components or trying to build the UI, make sure to refer closely to this guide (especially old upgrade steps, if you are upgrading from anything but the most recent version) and ensure especially that all packages with eslint in them match the versions specified here.

# Styling

Some Tailwind styles have been simplified so that they behave more predictably across different browsers. A side effect of this is that your layout will completely blow up unless you modify your app/ui/public/index.html file and change the line <div id="app"></div> to <div id="app" class="w-full"></div>.

# 0.17.0

# CRITICAL: BACK UP YOUR DATABASE!

The migration in this release will create new teams and users_teams tables, and modifies the subscriptions table to be connected to the teams table instead of the users table. To do this, it creates teams and users_teams entries for every entry in users.

This process should be pretty safe and automatic! But if you've made any changes to your users table, there could be unexpected side-effects, so BACK UP YOUR DATABSE BEFORE APPLYING ANY MIGRATIONS FROM THIS RELEASE.

This goes double for any production databases you apply this to.

# New config files

Because of some upgrades made and how webpack works, you will need to create config files in app/config for all config files in wood/config or you will see "Cannot find module ./x" error messages in your browser console. These files can be effectively empty, just inheriting defaults from their "wood" counterparts, like so:

const woodConfig = require('@wood/config/teams');

/**
 * @type {Object} Application teams config values.
 */
module.exports = {
  /**
   * Start with default teams config.
   */
  ...woodConfig,

  // Overwrite default configs here
};

# Updating your email configuration

The email transport configuration has been moved to its own file, so that you can import email address configuration values safely from the UI without needing to add email-sending libraries to your UI package, needlessly increasing file size.

To move your configuration, run nodewood eject config-api/email, then move the transportConfig value from your existing config/email entry to config-api/email, ensuring you also move the necessary require statements with it.

# Updating tests

The COOKIE_JWT_USER_ID_1 constant has been renamed to COOKIE_JWT_OWNER_ID_1 to reflect the fact that that it now contains an Owner role. (An COOKIE_JWT_OWNER_ID_1 constant representing a user with a member role has also been added.) You will need to use find and replace to rename this constant through your tests.

# Updating .env file

Add an entry for GENERAL_HASHID_SALT=generalHashidSalt, where generalHashidSalt should be a random series of characters that will be used to salt general-purpose Hashids created in your project. This should be different from your JWT_HASHID_SALT entry so that users cannot examine their JWTs and compare Hashids generated there and elsewhere and potentially glean information about your user IDs or other sequential values.

# ActiveUser

The Users store was renamed to the ActiveUser store for clarity. Now, instead of referencing Users.current alongside Users.role, Users.subscription, and Users.team, you reference ActiveUser.user, ActiveUser.role, ActiveUser.subscription and ActiveUser.team.

You will need to update your Vue files, so that anywhere you were referencing the Users store, you are now referencing the ActiveUser store, and anywhere you were referencing the current property of that store, you are now referencing the user property.

# UserModel

A "teams" attribute was added to UserModel. If you have extended this file, make sure to copy across the additions to the constructor() and toJSON() functions, as well as require TeamModel and RoleModel in your UserModel, as in the copy in wood.

# Configuration

Configuration values are now loaded with the getConfig() function defined in #lib/Config. This allows for easy configuration value overrides when testing. For more information, check the Configuration section of the docs. Your old ways of loading configuration values will still work, but this style is officially deprecated.

For added security, you'll need to move your transportConfig configuration value from config/email.js to config-api/email.js. This is a new folder that has protections to keep it from being loaded in the UI front-end, making it safe to store API back-end configuration secrets. When loading values from files in this folder, you'll need to use the getConfigApi() function defined in #lib/Config. This function works much like getConfig(), except with protections to ensure it cannot be called from within webpack, protecting these files from being included in your UI front-end.

# 0.16.0

# Modifying app/ui/main.js to take into account the new samples feature

Instead of polluting the default main.js with routes for sample files, those files and their routes have been moved to the samples feature in the wood library, which can be cleanly disabled by removing the feature from your app/config/app.js file. Consequently, those routes can be removed from app/ui/main.js, which can be pared down to simply:

import main from '@wood/ui/main';

main();

You will also want to override the wood feature list in app/config/app.js, if you haven't already, and remove the samples feature, like so:

  features: {
    // (Keep your existing "app" features here!)

    /**
     * @type {Array<String>} List of enabled Nodewood features.
     */
    wood: [
      // 'samples',
      'users',
      'admin',
      'subscriptions',
    ],
  },

# Add eslint-plugin-promise to your eslintrc.js

The eslint-plugin-promise (opens new window) set of rules are designed to prevent you from making common mistakes with promises and async/await code. These mistakes can lead to especially hard-to-debug errors, so following these eslint rules will make a significant difference in your project.

To enable them, edit your .eslintrc.js file in your application root, and make sure the following two sections look like this:

  extends: [
    // Common
    'eslint:recommended',
    'plugin:import/errors',
    'airbnb-base/legacy',
    'plugin:promise/recommended',

    // Vue
    'plugin:vue/recommended',
    '@vue/airbnb',
  ],
  plugins: [
    'import',
    'promise',
  ],

# Updating your Webpack Dev Server, and enabling HMR

  1. Update the following line in the scripts section of your your root package.json:
    "dev-ui": "cd app && vue-cli-service serve",
  1. If you've created a custom docker-compose.yml file in app/docker, make sure to add the changes from the docker-compose.yml file in wood/docker, specifically the networks and ports section under ui, and the links section under nginx.

  2. If you've created a custom ngingx-default.conf file in app/docker, make sure to add the new location section from the docker-compose.yml file in wood/docker.

  3. If you use anything other than localhost for your local development environment (for example, if you've created a /etc/hosts entry for your Nodewood development), you'll need to put that URL in your devServer.public configuration in app/vue.config.js. For example, if you go to "https://develop.test/app" in your browser, you'll need to modify app/vue.config.js like so:

const config = require('../wood/vue.config.js');

module.exports = {
  ...config,

  devServer: {
    port: 9000,
    public: 'develop.test',
    stats: 'minimal',
    progress: false,
  },
};
  1. Finally, you'll need to destroy and rebuild your Nginx images:
docker-compose -p PROJECT_NAME -f wood/docker/docker-compose.yml rm -sv nginx
docker-compose -p PROJECT_NAME -f wood/docker/docker-compose.yml up --build --force-recreate --no-deps nginx

Where PROJECT_NAME is the basename of the directory your project lives in. For example, if your project is in ~/projects/myproject, your PROJECT_NAME is myproject. If you have a period in your directory (for example myproject.com), the period is omitted completely for your PROJECT_NAME (myprojectcom). If you use a custom docker-compose.yml, change wood to app in the lines above.

# 0.15.0

# Error message: Cannot find module '@tailwindcss/forms'

You will see this error message during upgrade, as we are upgrading to Tailwind 2, which replaces the @tailwindcss/custom-forms plugin with the @tailwindcss/forms plugin. Since part of the Nodewood upgrade process is trying to apply your chosen Tailwind prefix to all Tailwind classes, this step will break.

Thankfully, it's a pretty easy fix:

  • Complete the Other Packages step below.
  • Run yarn install.
  • If you have set a Tailwind prefix, run nodewood tailwind:prefix and apply your prefix again.

# app/ui/main.js

All the code for this file has been moved to wood/ui/main.js so that Nodewood library updates to this file are automatically applied instead of relying on you to manually update this file. In order to take advantage of this, however, you will need to replace the content of app/ui/main.js with the following:

import main from '@wood/ui/main';
import router from '#ui/router';

main();

router.addRoute({
  path: '/',
  name: 'home',
  component: () => import(/* webpackChunkName: "home" */ '#ui/pages/HomePage'),
});

router.addRoute({
  path: '/about',
  name: 'about',
  component: () => import(/* webpackChunkName: "about" */ '#ui/pages/AboutPage'),
  meta: {
    routeName: 'About',
  },
});

router.addRoute({
  path: '/components',
  name: 'components',
  component: () => import(/* webpackChunkName: "components" */ '#ui/pages/ComponentsPage'),
  meta: {
    routeName: 'Components',
  },
});

You can still import Vue at the beginning of this file and add custom Vue.use directives, etc, before calling main(), and if you have heavily customized this file already, you can leave your customizations in place. However, further updates to main.js may need to be manually applied to your customized file.

# Nginx

Tired of seeing all those OPTIONS logs? This release configures nginx to skip those lines when logging, resulting in cleaner log output. You'll need to rebuild your nginx Docker container to activate this new configuration:

docker-compose -p PROJECT_NAME -f wood/docker/docker-compose.yml build --no-cache nginx

Where PROJECT_NAME is the basename of the directory your project lives in. For example, if your project is in ~/projects/myproject, your PROJECT_NAME is myproject. If you have a period in your directory (for example myproject.com), the period is omitted completely for your PROJECT_NAME (myprojectcom).

# Tailwind 2

Tailwind 2 brings a lot of neat new capabilities, but it may require a little work to get working as expected. To start, you should read the official upgrade guide. (opens new window) Not all of it will be relevant, but it will give you an overview of what's changed.

Nodewood used the @tailwindcss/custom-forms plugin, which has been replaced with the @tailwindcss/forms plugin. This new plugin styles all forms cleanly by default, instead of just all the ones you added form-* classes to. You can safely go and remove all these classes, as they no longer do anything.

Of particular notice is how @apply statement order has changed (opens new window). This may mean that some classes now apply in a different order than you expect, and you'll have to make a small adjustment to that order in order to get your styles working again.

# Other Packages

Many other packages have had their versions updated as well, so you'll want to update the packages in the dependencies section of your app/package.json to the following versions:

    "@vue/cli-plugin-babel": "^4.5.11",
    "@vue/cli-plugin-eslint": "^4.5.11",
    "@vue/cli-plugin-unit-jest": "^4.5.11",
    "@vue/cli-service": "^4.5.11",
    "@vue/eslint-config-airbnb": "^5.3.0",
    "aws-sdk": "^2.839.0",
    "core-js": "^3.8.3",
    "debug": "^4.3.1",
    "enhanced-resolve": "^5.7.0",
    "eslint": "^7.19.0",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jest": "^24.1.3",
    "eslint-plugin-vue": "^7.5.0",
    "hashids": "^2.2.8",
    "massive": "^6.6.5",
    "moment": "^2.29.1",
    "nodemailer": "^6.4.17",
    "nodemailer-mock": "^1.5.3",
    "pino": "^6.11.1",
    "stripe": "^8.135.0",
    "superagent": "^6.1.0",
    "supertest": "^6.1.3",
    "vue": "2.6.12",
    "vue-js-modal": "^2.0.0-rc.6",
    "vue-router": "^3.5.1",
    "vue-template-compiler": "2.6.12",
    "vuex": "^3.6.2"

You'll also want to update the packages in the devDependencies section of your package.json (in the root of your project) to the following versions:

    "nodemon": "^2.0.7",
    "eslint": "^7.19.0",
    "husky": "^5.0.9",
    "lint-staged": "^10.5.4"

In addition the following packages have been moved to the wood library or removed, and can be removed from all sections of your app/package.json file:

    "jest",
    "knex",
    "knex-cleaner",
    "pino-pretty",
    "socket.io",
    "v-click-outside",
    "webpack",
    "webpack-bundle-analyzer",

This will update some of the eslint rules that we uses to keep code tidy. Specifically, anywhere that you use a v-slot directive in your Vue components, you'll need to replace it with the # shorthand. Specifically, v-slot:slotname will need to change to #slotname.

# 0.13.0

This release greatly simplifies the specific inclusion of files in the app and wood folders. Instead of prefixing your includes with @wood-config/file or @app-ui/file, you will instead prefix them with @wood/config/file or @app/ui/file. This may seem superficial, but it means that we only have special cases for the wood and app folders, instead of special cases for every folder under each of them. This greatly simplifies the require logic behind the scenes, and means that you can now specifically require any file in the wood and app folders, not just ones with specifically-defined aliases.

Upgrade steps:

  1. Update _moduleAliases in package.json:
  "_moduleAliases": {
    "@app": "./app",
    "@wood": "./wood"
  },
  1. Update moduleNameMapper in jest.config.js:
  moduleNameMapper: {
    '^@app(.*)$': '<rootDir>/app/$1',
    '^@wood(.*)$': '<rootDir>/wood/$1',
  },
  1. Update settings.import/resolver.alias-array.map in .eslintrc.js:
        map: [
          ['#api', [
            resolve(__dirname, 'app/api'),
            resolve(__dirname, 'wood/api'),
          ]],

          ['#config', [
            resolve(__dirname, 'app/config'),
            resolve(__dirname, 'wood/config'),
          ]],
          ['#features', [
            resolve(__dirname, 'app/features'),
            resolve(__dirname, 'wood/features'),
          ]],
          ['#lib', [
            resolve(__dirname, 'app/lib'),
            resolve(__dirname, 'wood/lib'),
          ]],
          ['#ui', [
            resolve(__dirname, 'app/ui'),
            resolve(__dirname, 'wood/ui'),
          ]],
          ['@app', resolve(__dirname, 'app')],
          ['@wood', resolve(__dirname, 'wood')],
        ],
  1. Update _moduleAliases in app/package.json:
  "_moduleAliases": {
    "@app": ".",
    "@wood": "../wood/"
  },
  1. Replace all instances of @wood-root with @wood.
  2. Replace all instances of @app-root with @app.
  3. Replace all instances of @wood- with @wood/.
  4. Replace all instances of @app- with @app/.

# 0.12.0

This release is a significant change in the file layout of Nodewood. If you are upgrading from < 0.12.0 and do not perform these changes, your app will not work.

This release moves all files in api/src to api, lib/src to lib, and ui/src to ui to better match how the rest of the framework is structured. These files were originally in separate root folders, but were all moved under the wood folder a while back and should properly have had their src folder removed at that time.

But as they say, the best time to plant a tree is twenty years ago, and the second-best time is now.

Upgrade steps:

  1. Move all code from your app/api/src folder to the app/api folder.
  2. Move all code from your app/lib/src folder to the app/lib folder.
  3. Move all code from your app/ui/src folder to the app/ui folder.
  4. Update .eslintrc.js to remove the /src suffix for all entries.
  5. Update jest.config.js to change all instances of src/$1 to $1.
  6. Update package.json to remove the src suffix for all entries in _moduleAliases.
  7. Update package.json to change main to "app/api/api.js".
  8. Update package.json to change scripts.dev-api-only to "cd app/api && NODE_ENV=development nodemon --watch . --watch ../config --watch ../features --watch ../../wood -L ./api.js | npx pino-pretty"
  9. Update app/package.json to remove the src suffix for all entries in _moduleAliases.
  10. Update app/api/api.js and change the startServer line to const { startServer } = require('../../wood/api/api.js');.
  11. Update `app/
  12. Update any other custom code you have added that refers to api/src, lib/src, or ui/src.
  13. Update any other custom code you have added to the api, lib, or ui folders that access files from other folders using relative paths (i.e. if you have a sequence of ../../../, etc).

If you experience into any issues, please contact support at hello@nodewood.com.