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.
For most users, this breaking change will not be noticeable. In actionhero/actionhero#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.
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 moreconfig.servers.web
- it'sconfig.web
now.
An example of the required changes can be seen here.
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
- Remove any
/locales/*
files you have, and move that text content into your Actions and Tasks - Remove any instances if
connection.localize()
in your code - this method is removed
Configuration
- In
src/config/api.ts
:- Add
config.general.welcomeMessage = 'Welcome to the Actionhero API!'
or similar message - Remove
config.general.paths.locale
- Add
- In
src/config/errors.ts
:- Remove all instances of
data.connection.localize
and use regular JS strings
- Remove all instances of
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.
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.
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
andstart
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 nowactionhero generate-action
. - See PR #1670 for more information.
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 withconfig.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.
This release is breaking for 2 reasons:
- The logger format for Action and Task errors has changed Pull Request.
- The
Documentation
initializer and relatedshowDocumentation
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:
- The Swagger public page - swagger.html
- The Swagger Action - swagger.ts
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.
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
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
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.
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 viewconfig/api.ts
,config/redis.ts
,config/servers/websocket.ts
, andconfig/servers/web.ts
for more information
Full Release Notes: GitHub
- In
config/api.js
addconfig.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.
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 viewconfig/api.ts
,config/redis.ts
,config/servers/websocket.ts
, andconfig/servers/web.ts
for more information
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 aasync
method.api
can be required globally to your file.
- Actions are now ES6 classes, which extend
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 aasync
method.api
can be required globally to your file.
- Tasks are now ES6 classes, which extend
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
, andstop
methods now have no arguments and become aasync
methods.api
can be required globally to your file.
- Initializers are now ES6 classes, which extend
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
, andstop
methods now have no arguments and become aasync
methods.api
can be required globally to your file.
- Servers are now ES6 classes, which extend
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 aasync
method.api
can be required globally to your file.
- CLI Commands are now ES6 classes, which extend
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, whenawait
ed, return a result andthrow
errors
- All methods which used to return a callback are now
-
Tasks
- All methods which used to return a callback are now
async
methods which, whenawait
ed, return a result andthrow
errors
- All methods which used to return a callback are now
-
Chat
- All methods which used to return a callback are now
async
methods which, whenawait
ed, return a result andthrow
errors
- All methods which used to return a callback are now
-
SpecHelper
- All methods which used to return a callback are now
async
methods which, whenawait
ed, return a result andthrow
errors
- All methods which used to return a callback are now
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, whenawait
ed, return a result andthrow
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
andActionhero 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
- Actionhero no longer uses linkfiles to find plugins. If you have any in a
-
Clients
ActionheroClient
(the included client library for browser websocket clients) as been named a more clearActionheroWebsocketClient
to avoid ambiguity.- The node sever-sever package has been renamed
Actionhero-node-client
to help clear up any confusion.
Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
-
Localization (i18n)
-
In
./config/i18n.js
be sure to enableobjectNotation
, 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
andgoodbyeMessage
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.
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 thanapi.connection()
api.GenericServer()
rather thanapi.genericServer()
api.ActionProcessor()
rather thanapi.actionProcessor()
require('Actionhero')
notrequire('Actionhero').actionheroPrototype
should you be using Actionhero programmatically.
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.
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
toconfig.general.channel
- Move
config.redis. rpcTimeout
toconfig.general.rpcTimeout
- Throughout the code, use
config.redis.client
rather thanapi.redis.client
- The easiest way to upgrade your
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
istrue
so that your locale files can be generated for the first time.
- This release introduced Locales. You will need the new locale config file. The easiest way to upgrade your
- Errors
config/errors.js
has been completely redone to take advantage ofconnection.localize
. The easiest way to upgrade yourconfig/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 keypkg
in the redis config.
Full Release Notes: GitHub
Breaking Changes and How to Overcome Them:
- Redis configuration
- Switch from using the
redis
npm package toioredis
. Change this in your package.json.
- Switch from using the
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
- The
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 thanconnection
directly. - You will need to change all of your actions to use
data.response
rather thanconnection.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.
- The params of websocket connections should NOT be sticky. All actions will start with
- Action Processor:
- Removed duplicate callback prevention in ActionProcessor. This belongs on the user/implementer to handle.