Simon Hofmann I live and work in Munich, currently with a focus on Node and TypeScript. I worked in Python and Java in the past. My experience spans frontend and backend development, test automation, and cloud and container platforms.

How to organize your nut.js development workflow

4 min read 1302

nut.js Development Workflow

A lot of people claim that you can build anything with JavaScript. There are countless frameworks for web development, from backend tools such as Node, to mobile-oriented libraries like React. You can even build desktop applications using Electron. But is it possible to perform desktop automation with JavaScript?

It sure is!

Using node add-ons, it’s possible to use OS APIs to simulate inputs or retrieve screen content.

However, building and shipping a ready-to-use desktop automation framework for three major platforms requires an elaborate development setup. In this tutorial, I’ll show you how I organize my work on nut.js. We’ll touch upon the following topics.

  • Branching strategy
  • TypeScript
  • Generating documentation
  • Testing
  • CI/CD setup
  • Branch protection

Let’s get started!

Repo setup

When it comes to tooling, you should also address repository setups. While it is possible to do all development on a single branch, I’m a huge fan of GitFlow. I’m employed full-time in software development, so the amount of time I’m able to spend on my personal projects depends heavily on my workload. Whenever I have some energy left in the evening or when I find time for coding on a weekend, I’ll pick up some old task or start a new one if I feel like it. Following the GitFlow, all development happens on feature branches, so no matter what I’m currently working on, I’m always able to seamlessly switch to another task. Develop and master branches only change when:

  • A feature is finished
  • A new stable version is released

This is a huge benefit for contributors since they will always be able to check out either master or develop to get going. I don’t need to tell you how frustrating it is when you clone a project only to realize it’s in an inconsistent state and won’t build.

TypeScript

This part may be a little opinionated, but TypeScript turned into my default when starting new projects. Its type system is really helpful, yet it’s unintrusive — a great combination for everyday use.

While not required, I tend to adjust the default tsconfig.json.

{
  "compilerOptions": {
    "outDir": "./dist",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "strict": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    …
  },
  “include”: [
    “lib/**/*.ts”,
    “index.ts”
  ],
  “exclude”: [
    “node_modules”
  ]
}

The TypeScript config is accompanied by the following scripts.

We made a custom demo for .
No really. Click here to check it out.

…
“scripts”: {
  “clean”: “rm -rf dist”,
  “compile”: “npm run clean && tsc -p .”,
  “prepublishOnly”: “npm run compile”,
  …
},
…

The end result is a setup that fits my needs perfectly. Simple, yet powerful! 💪

Documentation

When developing a framework, you might be tempted to focus solely on features, but if you want people to use your work, you’ll have to provide documentation and samples. Fortunately, there are multiple solutions available that allow you to host your samples and/or documentation.

nut.js uses TypeDoc to automatically generate API documentation, including short examples from doc comments:

/**
 * {@link type} types a sequence of strings or single {@link Key}s via system keyboard
 * @example
 * ```typescript
 *    await keyboard.type(Key.A, Key.S, Key.D, Key.F);
 *    await keyboard.type("Hello, world!");
 * ```
 * @param input Sequence of strings or {@link Key}s to type
 */

Typedoc is configured to output generated documentation to a docs folder in our repository.

"scripts": {
  ...
  "typedoc": "typedoc --options ./typedoc.js --out ./docs lib/"
},

GitHub Pages is able to use this docs folder on the master branch as a source. With just a few clicks, the documentation is live at https://nut-tree.github.io/nut.js/.

For samples, I’m using a separate repository at nut-tree/trailmix. It’s a Lerna monorepo that contains packages for every (current) main aspect of nut.js. Following the monorepo approach makes it easy to update and test against a new release of nut.js since dependencies are managed at root level, which helps keeping samples up to date. And since all samples are designed to be Jest tests, it also enables you to detect errors early on.

Testing

Testing is the most crucial part of nut.js development. We need to make sure nut.js builds and runs on multiple platforms. The only feasible way to do that is to rely heavily on automation. And the only way to reliably automate processes is to have a proper test set up to verify our system’s behaviour.

The testing framework I’m most comfortable with is Jest. It has all the features I’m looking for in a testing framework and plays along nicely with TypeScript using the ts-jest preset.

module.exports = {
  collectCoverageFrom: [
    "index.ts",
    "lib/**/*.ts",
    "!lib/**/*.spec.ts",
    "!<rootDir>/node_modules/",
  ],
  preset: "ts-jest",
  testEnvironment: "node",
  testMatch: process.env.E2E_TEST ?
    ["**/__tests__/?(e2e)/**/*.[jt]s?(x)", "**/?(*.)?(e2e.)+(spec|test).[jt]s?(x)"] :
    ["**/__tests__/!(e2e)/**/*.[jt]s?(x)", "**/!(*.e2e.*)+(spec|test).[jt]s?(x)"],
  testPathIgnorePatterns: [
    "/node_modules/",
    "/dist/",
  ],
};

This single config file enables TypeScript support for Jest, allows me to collect coverage for files I’m interested in, and separates two kinds of tests. nut.js includes unit tests, which can be run every time, and E2E tests, which are meant to be run in a Docker container featuring a certain UI. E2E tests will only be included in a test run if the E2E_TEST environment variable is set.

Tests are simply distinguished by their file name. feature.class.spec.ts contains unit tests for a feature, while feature.class.e2e.spec.ts contains a full E2E test, which depends on a fixed UI.

On CI, all tests are executed in a Docker container to run all available tests.

Continuous integration and deployment

nut.js currently uses two CI systems:

  1. Travis-CI
  2. Appveyor

In total, 16 CI jobs run tests against five supported node versions (10, 11, 12, 13, 14) on three supported platforms (Windows, macOS, and Linux). The 16th job publishes snapshot and stable releases.

On Travis, nut.js imposes a three-stage setup. The first stage builds and tests against the current node LTS release. If sucessful, SonarCloud is used for static code analysis.

In the next stage, we’ll test against the remaining combinations of platform and node versions.

We’ll run a final deploy stage in case of a tagged commit or a build on the develop branch. If we push a new tag, a stable release is published under the default @latest tag. Builds for the develop branch will do a snapshot release under the @next tag. So whenever a feature is finished and merged into develop, a new snapshot release is published. Snapshots are great for fast feedback since users don’t have to wait for the next stable release to test new features.

Branch protection

A nice benefit of having a proper test and CI setup is branch protection. We want to keep our code clean and we don’t want any surprises after merging a pull request. With branch protection enabled, we can enforce certain status checks to pass before merging a PR.

Sonarcloud and Travis/Appveyor are among the available status checks, so if the pull request build fails or our quality gate is missed, a PR won’t be mergeable (unless forced by a repo owner or admin).

You might be thinking to yourself that these settings are only relevant when collaborating with others, but solo developers can benefit from these features too.

Conclusion

The setup I just walked you through has evolved over time. It allows me to maintain nut.js with confidence. Refactorings are covered by tests, releases are automated, and documentation is version controlled and automated.

Every developer has their own setup, but if you’re just getting acquainted with nut.js or looking for a way to get better organized, I hope this walkthrough will give you some inspiration and a solid foundation.

If you’re already experienced in nut.js, what does your setup look like?

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Simon Hofmann I live and work in Munich, currently with a focus on Node and TypeScript. I worked in Python and Java in the past. My experience spans frontend and backend development, test automation, and cloud and container platforms.

Leave a Reply