Upgrade-path


Overview


Upgrading big Actionhero projects to a new major might require some effort. Every Actionhero version has it's own specific project files which you generate using Actionhero generate command.

One of the ways to upgrade your project is to generate a new project using the latest Actionhero framework (npx Actionhero generate). Using that as your starting point you can then carefully copy all your configs, initializers, servers, tasks, actions, and other custom code from your old project, making sure that you are at the same working state as before. It's a good practice to make tests for your actions (or any other component) before you plan to upgrade your Actionhero project.

Actionhero follows semantic versioning. This means that a minor change is a right-most number. A new feature added is the middle number, and a breaking change is the left number. You should expect something in your application to need to be changed if you upgrade a major version.


Upgrading from v28 to v29


For most users, this breaking change will not be noticeable. In https://github.com/actionhero/actionhero/pull/2541, Actionhero changed the internal representation fo cache objects to remove readAt and expireTimestamp. We now rely on redis itself for object expiration. Error responses when objects have expired will be changed as well.


Upgrading from v27 to v28


Strongly Typed Config

The biggest change in Actionhero v28 is that Actionhero's codebase now has no implicit any Typescript types. This means that all of Actionhero is strongly Typed! The largest change was how we now type ./config files so that they can extend the type of the global config object using modules.

To upgrade from v27 to v28 the fastest way forward may be to delete your ./src/config directory and re-initialize your actionhero project with npx actionhero generate ., and then re-apply your changes. Otherwise, you should copy and paste the config files from Actionhero's source keeping the following in mind:

  • replace any .. with "actionhero"
  • the previous files ./src/config/servers/web.ts and ./src/config/servers/websocket.ts have been moved to ./src/config/web.ts and ./src/config/websocket.ts respectively to match their new namespace config locations. There is no more config.servers.web - it's config.web now.

An example of the required changes can be seen here.


Upgrading from v26 to v27


Spec Helper Type changes

It's now much easier to get the types of your response from specHelper.runAction<Action>() and specHelper.runTask<Task>()!

Just provide your Action or Task Class!

// In `__tests__/actions/randomNumber.ts`
import { Process, specHelper } from "actionhero";
import { RandomNumber } from "../../src/actions/randomNumber";

describe("Action: randomNumber", () => {
  const actionhero = new Process();
  beforeAll(async () => await actionhero.start());
  afterAll(async () => await actionhero.stop());

  test("generates random numbers", async () => {
    // now "randomNumber" is typed properly as a number
    const { randomNumber } = await specHelper.runAction<RandomNumber>(
      "randomNumber"
    );
    expect(randomNumber).toBeGreaterThan(0);
    expect(randomNumber).toBeLessThan(1);
  });
});

Version 27 also removed i18n and uglify from Actionhero

Localization Removal

  1. Remove any /locales/* files you have, and move that text content into your Actions and Tasks
  2. Remove any instances if connection.localize() in your code - this method is removed

Configuration

  1. In src/config/api.ts:
    • Add config.general.welcomeMessage = 'Welcome to the Actionhero API!' or similar message
    • Remove config.general.paths.locale
  2. In src/config/errors.ts:
    • Remove all instances of data.connection.localize and use regular JS strings

Minified Websocket Client Library Removed

ActionheroWebsocketClient.min.js will no longer be generated in your Actionhero projects. Most users include /public/javascript/ActionheroWebsocketClient.js in their build and it is compiled into their react or angular project... or cached and minified by their CDN. Minifiying this client-side javascript is now outside of the scope of Actionhero.


Upgrading from v25 to v26


There are no major changes in this version's code, but as of v26, Actionhero requires Node.js v12+ Support for Node.js v10 has been dropped.


Upgrading from v24 to v25


Configuration

  • Redis can no longer be disabled, but you can opt to use ioredis-mock instead. See what a configuration from using this redis mock looks like on the main branch. See PR #1653 for more information.

Logging and HTTP Server

  • The default HTTP response code if an Action throws is now 500, configured by a new config setting, config.servers.web.defaultErrorStatusCode (default 500). This option is only effective if the status code has not been set by the action. See PR #1661 for more information.
    • Error log message format has changed as well.

CLI Commands

  • Commands now have optional initialize and start options, so you can opt-into initializing or starting your server as needed for your CLI command. The default is to initialize (initialize=true) but not start (start=false)
  • Command names with spaces now have renamed to have -. IE: actionhero generate action is now actionhero generate-action.
  • See PR #1670 for more information.

Upgrading from v23 to v24


New Config Options which should be added:

  • config.servers.web.automaticRoutes = []
  • config.tasks.retryStuckJobs = false

Config Options which need to be removed:

  • config.servers.web.simpleRouting (spiritually replaced with config.servers.web.automaticRoutes)
  • config.servers.web.queryRouting (removed)

And if you want to use the new Typescript features, change your Actions to return the response you want to send rather than using data.response. data.response will be removed in a future version of Actionhero.


Upgrading from v22 to v23


This release is breaking for 2 reasons:

  1. The logger format for Action and Task errors has changed Pull Request.
  2. The Documentation initializer and related showDocumentation action has been removed in favor of the new swagger action & middleware Pull Request.

If you use automated log ingestion (i.e.: Splunk or a Winston logger) this PR should be helpful, as the error and stack trace will now all be on the same line... but you will need to update your log tools.

If you had been using the Documentation initializer, you can re-build it yourself from api.actions.actions. If you want upgrade your Actionhero project to use the new Swagger documentation tooling, you need to copy in 2 files:

There are also new logging options to add to src/config/api.ts:

  • config.general.enableResponseLogging (bool) toggles this option on (off by default)
  • config.general. filteredResponse (string[]) allow you to filter out certain parts of the response payload from the logs, hiding sensitive data.

Upgrading from v21 to v22


Assuming that you have already migrated your project to Typescript, the only change is create your server.ts and change your package.json scripts to use it. Support for boot.js has also been removed, and you should move that logic into your new server.ts

// in ./src/server.ts
import { Process } from "actionhero";

// load any custom code, configure the env, as needed

async function main() {
  // create a new actionhero process
  const app = new Process();

  // handle unix signals and uncaught exceptions & rejections
  app.registerProcessSignals();

  // start the app!
  // you can pass custom configuration to the process as needed
  await app.start();
}

main();

Your package json should now contain:

  "scripts": {
    "postinstall": "npm run build",
    "dev": "ts-node-dev --no-deps --transpile-only ./src/server.ts",
    "start": "node ./dist/server.js",
    "test": "jest",
    "pretest": "npm run build && npm run lint",
    "build": "tsc --declaration",
    "lint": "prettier --check src/*/** __test__/*/**",
    "pretty": "prettier --write src/*/** __test__/*/**"
  },

Also be sure that your packate.json contains the @types/ioreids devDependency. You can install it with npm install --save-dev @types/ioredis

Tasks can now use input validation.

Full Release Notes: GitHub


Upgrading from v20 to v21


The recommended upgrade to v21 of Actionhero is to move your project to Typescript. Detailed notes can be found on the Typescript Tutorial.

Full Release Notes: GitHub


Upgrading from v19 to v20


Full Release Notes: GitHub

The only change to take note of is that you must now ensure that the working directory (CWD/PWD) in use when you start your Actionhero project is the root. (where /config, /actions, etc) are visible.


Upgrading from v18 to v19


Full Release Notes: GitHub

Configuration

  • in config/tasks.js add config.tasks.stuckWorkerTimeout = 3600000. This will be a 1 hour timeout for stuck/crashed worker processes
  • in config/servers/websocket.js add config.servers.websocket.client.cookieKey = config.servers.web.fingerprintOptions.cookieKey. This will instruct the Actionhero Websocket Clients to share the same cookie as the web server to share a fingerprint, which can be used to share session information.
  • If you plan to use Jest for your tests, and want to test in parallel, you will need to configure your server in the test environment to make use of process.env.JEST_WORKER_ID. Please view config/api.ts, config/redis.ts, config/servers/websocket.ts, and config/servers/web.ts for more information

Upgrading from v19 to v20


Full Release Notes: GitHub

  • In config/api.js add config.general.paths.test = [path.join(__dirname, '/../__tests__')] so that when you generate a new action or task, the related test file can be generated as well.
  • However you run Actionhero, be sure to cd into the root directory of the project before starting the server.

Upgrading from v19 to v20


Full Release Notes: GitHub

  • in config/tasks.js add config.tasks.stuckWorkerTimeout = 3600000. This will be a 1 hour timeout for stuck/crashed worker processes
  • in config/servers/websocket.js add config.servers.websocket.client.cookieKey = config.servers.web.fingerprintOptions.cookieKey. This will instruct the Actionhero Websocket Clients to share the same cookie as the web server to share a fingerprint, which can be used to share session information.
  • If you plan to use Jest for your tests, and want to test in parallel, you will need to configure your server in the test environment to make use of process.env.JEST_WORKER_ID. Please view config/api.ts, config/redis.ts, config/servers/websocket.ts, and config/servers/web.ts for more information

Upgrading from v17 to v18


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

There are many changes to the APIs Actionhero exposes. You can read up on the new syntax on our new documentation website, docs.actionherojs.com

  • Node.js version

    • Node.js v8 and higher is now required. You must update your projects.
  • Actions

    • Actions are now ES6 classes, which extend require('Actionhero').Action.
    • The run method only has one argument now, data and becomes a async method. api can be required globally to your file.
const { Action, api } = require("actionhero");

module.exports = class MyAction extends Action {
  constructor() {
    super();
    this.name = "randomNumber";
    this.description = "I am an API method which will generate a random number";
    this.outputExample = { randomNumber: 0.1234 };
  }

  async run(data) {
    data.response.randomNumber = Math.random();
  }
};
  • Tasks
    • Tasks are now ES6 classes, which extend require('Actionhero').Task.
    • The run method only has one argument now, data and becomes a async method. api can be required globally to your file.
const { api, Task } = require("actionhero");

module.exports = class SendWelcomeMessage extends Task {
  constructor() {
    super();
    this.name = "SendWelcomeEmail";
    this.description = "I send the welcome email to new users";
    this.frequency = 0;
    this.queue = "high";
    this.middleware = [];
  }

  async run(data) {
    await api.sendWelcomeEmail({ address: data.email });
    return true;
  }
};
  • Initializers
    • Initializers are now ES6 classes, which extend require('actionhero').Initializer.
    • The initialize, start, and stop methods now have no arguments and become a async methods. api can be required globally to your file.
const { Actionhero, api } = require("actionhero");

module.exports = class StuffInit extends Actionhero.Initializer {
  constructor() {
    super();
    this.name = "StuffInit";
    this.loadPriority = 1000;
    this.startPriority = 1000;
    this.stopPriority = 1000;
  }

  async initialize() {
    api.StuffInit = {};
    api.StuffInit.doAThing = async () => {};
    api.StuffInit.stopStuff = async () => {};
    api.log("I initialized", "debug", this.name);
  }

  async start() {
    await api.StuffInit.startStuff();
    api.log("I started", "debug", this.name);
  }

  async stop() {
    await api.StuffInit.stopStuff();
    api.log("I stopped", "debug", this.name);
  }
};
  • Servers
    • Servers are now ES6 classes, which extend require('Actionhero').Server.
    • The initialize, start, and stop methods now have no arguments and become a async methods. api can be required globally to your file.
const Actionhero = require("Actionhero");

module.exports = class MyServer extends Actionhero.Server {
  constructor() {
    super();
    this.type = "%%name%%";

    this.attributes = {
      canChat: false,
      logConnections: true,
      logExits: true,
      sendWelcomeMessage: false,
      verbs: [],
    };
    // this.config will be set to equal config.servers[this.type]
  }

  initialize() {
    this.on("connection", (connection) => {});

    this.on("actionComplete", (data) => {});
  }

  start() {
    // this.buildConnection (data)
    // this.processAction (connection)
    // this.processFile (connection)
  }

  stop() {}

  sendMessage(connection, message, messageId) {}

  sendFile(connection, error, fileStream, mime, length, lastModified) {}

  goodbye(connection) {}
};
  • CLI Commands
    • CLI Commands are now ES6 classes, which extend require('Actionhero').CLI.
    • The run method now has one argument, data and becomes a async method. api can be required globally to your file.
const {api, CLI} = require('Actionhero')

module.exports = class RedisKeys extends CLI {
  constructor () {
    super()
    this.name = 'redis keys'
    this.description = 'I list all the keys in redis'
    this.example = 'Actionhero keys --prefix Actionhero'
  }

  inputs () {
    return {
      prefix: {
        required: true,
        default: 'Actionhero',
        note: 'the redis prefix for searching keys'
      }
    }
  }

  async run ({params}) => {
    let keys = await api.redis.clients.client.keys(params.prefix)
    api.log('Found ' + keys.length + 'keys:')
    keys.forEach((k) => { api.log(k) })
  }
}
  • Cache

    • All methods which used to return a callback are now async methods which, when awaited, return a result and throw errors
  • Tasks

    • All methods which used to return a callback are now async methods which, when awaited, return a result and throw errors
  • Chat

    • All methods which used to return a callback are now async methods which, when awaited, return a result and throw errors
  • SpecHelper

    • All methods which used to return a callback are now async methods which, when awaited, return a result and throw errors
const chai = require("chai");
const dirtyChai = require("dirty-chai");
const expect = chai.expect;
chai.use(dirtyChai);

const path = require("path");
const Actionhero = require("Actionhero");
const Actionhero = new Actionhero.Process();
let api;

describe("Action: RandomNumber", () => {
  before(async () => {
    api = await Actionhero.start();
  });
  after(async () => {
    await Actionhero.stop();
  });

  let firstNumber = null;
  it("generates random numbers", async () => {
    let { randomNumber } = await api.specHelper.runAction("randomNumber");
    expect(randomNumber).to.be.at.least(0);
    expect(randomNumber).to.be.at.most(1);
    firstNumber = randomNumber;
  });

  it("is unique / random", async () => {
    let { randomNumber } = await api.specHelper.runAction("randomNumber");
    expect(randomNumber).to.be.at.least(0);
    expect(randomNumber).to.be.at.most(1);
    expect(randomNumber).not.to.equal(firstNumber);
  });
});
  • Utils

    • api.utils.recursiveDirectoryGlob has been removed in favor of the glob package. Use this instead.
    • All methods which used to return a callback are now async methods which, when awaited, return a result and throw errors
  • Plugins

    • Actionhero no longer uses linkfiles to find plugins. If you have any in a plugins directory in your actions, tasks, config, or public folders, delete them.
    • Plugins now need to be defined explicitly in a new ./config/plugins.js config file. You should create one per the example
    • Removed Actionhero link and Actionhero unlink per the above.
    • Added Actionhero generate plugin, a helper which you can use in an empty directory which will create a template plugin project
    • Testing plugins is now simpler. Read more about this on docs.actionherojs.com
  • Clients

    • ActionheroClient (the included client library for browser websocket clients) as been named a more clear ActionheroWebsocketClient to avoid ambiguity.
    • The node sever-sever package has been renamed Actionhero-node-client to help clear up any confusion.

Upgrading from v16 to v17


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

  • Localization (i18n)

  • In ./config/i18n.js be sure to enable objectNotation, or else the new locale file will be gibberish to Actionhero

  • As of this release, Actionhero no longer localizes its log messages. This is done to simplify and speed up the logger methods. There is not mitigation path here without overwriting the api.log() method.

  • Any use of % interpolation should be removed from your logger strings. Favor native JS string templates.

  • Actionhero now ships with locale files by default.

  • You will need to acquire the default locale file and copy it into ./locales/en.json within your project.

  • The error reporters have all been changed to use these new locale file and mustache-style syntax. Update your from the default errors file

  • The welcomeMessage and goodbyeMessage are removed from the config files and Actionhero now references the locale files for these strings. Update yours accordingly.

  • utils

  • api.utils.recursiveDirectoryGlob has been removed in favor of the glob package. Use this instead.


Upgrading from v15 to v16


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

The only breaking changes are related to the capilization of internal methods:

  • api.Connection() rather than api.connection()
  • api.GenericServer() rather than api.genericServer()
  • api.ActionProcessor() rather than api.actionProcessor()
  • require('Actionhero') not require('Actionhero').actionheroPrototype should you be using Actionhero programmatically.

Upgrading from v14 to v15


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

\`Actionhero generateAction --name=[name]\`      -> \`Actionhero generate action --name=[name]\`
\`Actionhero generateInitializer --name=[name]\` -> \`Actionhero generate initializer --name=[name]\`
\`Actionhero generateServer --name=[name]\`      -> \`Actionhero generate server --name=[name]\`
\`Actionhero generateTask --name=[name]\`        -> \`Actionhero generate task --name=[name]\`
  • The Actionhero binary has had it's commands changed.
    • Any deployment or automation tools you use will need to be updated accordingly.
  • Tasks now use middleware instead of plugins.
    • You will need to convert all uses of task plugins to task middleware.

Upgrading from v13 to v14


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

  • Redis Client Configurations have changed drastically. This allows for greater configuration, but at a complexity cost.
    • The easiest way to upgrade your config/redis.js is to take if from the main branch directly and re-apply your configuration.
    • Move config.redis.channel to config.general.channel
    • Move config.redis. rpcTimeout to config.general.rpcTimeout
    • Throughout the code, use config.redis.client rather than api.redis.client

Upgrading from v12 to v13


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

  • Plugins
    • config/plugins.js is removed. Delete yours.
    • Use the new binary command, Actionhero link --name=NameOfPlugin to link your plugins in the new method.
    • Linking plugins will likely create new config files you may need to customize.
  • Locales
    • This release introduced Locales. You will need the new locale config file. The easiest way to upgrade your config/i18n.js is to take if from the main branch.
    • Ensure that config.i18n.updateFiles is true so that your locale files can be generated for the first time.
  • Errors
    • config/errors.js has been completely redone to take advantage of connection.localize. The easiest way to upgrade your config/errors.js is to take if from the main branch.
  • Grunt Removed
    • Grunt is removed from the project. The old Actionhero grunt commands have been moved into the Actionhero binary.
  • Redis configuration
    • package is a reserved keyword in JavaScript. We now use the key pkg in the redis config.

Upgrading from v11 to v12


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

  • Redis configuration
    • Switch from using the redis npm package to ioredis. Change this in your package.json.
  • ioredis handles passwords slightly differently. Read the ioredis documentation to learn more.
  • Stats Removed
    • The api.stats subsection has been removed from Actionhero
    • If you need the stats subsection, you can get get it via plugin

Upgrading from v10 to v11


Full Release Notes: GitHub

Breaking Changes and How to Overcome Them:

  • Action Syntax changed
    • run: function(api, data, next){ data.response.randomNumber = Math.random(); next(error); }
    • Where data contains:
    • data = { connection: connection, action: 'randomNumber', toProcess: true, toRender: true, messageId: 123, params: { action: 'randomNumber', apiVersion: 1 }, actionStartTime: 123, response: {}, }
    • You will need to change all of your actions to use data.connection rather than connection directly.
    • You will need to change all of your actions to use data.response rather than connection.response directly.
  • Middleware syntax has changed to match action's data pattern. You will need to change your middleware accordingly.
  • Removed connection._originalConnection.
  • Websockets:
    • The params of websocket connections should NOT be sticky. All actions will start with connection.params = {}. If you rely on the old behavior, you will need to change your client code.
  • Action Processor:
    • Removed duplicate callback prevention in ActionProcessor. This belongs on the user/implementer to handle.

    Solutions

    Actionhero was built from the ground up to include all the features you expect from a modern API framework.

    Open Source


    The Actionhero server is open source, under the Apache-2 license


    Actionhero runs on Linux, OS X, and Windows


    You always have access to the Actionhero team via Slack and Github



    Premium Training & Review


    We provide support for corporate & nonprofit customers starting at a flat rate of $200/hr. Our services include:


    • Remote training for your team
    • Code Reviews
    • Best Practices Audits
    • Custom plugin & Feature Development

    We have packages appropriate for all company sizes. Contact us to learn more.


    Premium Training & Review


    We provide support for corporate & nonprofit customers starting at a flat rate of $200/hr. Our services include:


    • Remote training for your team
    • Code Reviews
    • Best Practices Workshops
    • Custom plugin & Feature Development

    We have packages appropriate for all company sizes. Contact us to learn more.


    Enterprise


    For larger customers in need of a support contract, we offer an enterprise plan including everything in the Premium plan plus:


    • 24/7 access to core members of the Actionhero Team
    • Emergency response packages
    • Deployment support
    • ...and custom development against Actionhero’s core as needed.