Skip to content

Commit

Permalink
Enable TypeScript (Koenkk#8074)
Browse files Browse the repository at this point in the history
* Enable Typescript

* Fix tests

* Fix tests again

* Automatically (re)build

* Updates

* Updates

* Update shrinkwrap

* Enable sourcemaps
  • Loading branch information
Koenkk authored Jul 21, 2021
1 parent 321b347 commit 7b65dc6
Show file tree
Hide file tree
Showing 25 changed files with 4,846 additions and 987 deletions.
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.eslintignore
.eslintrc.json
.eslintrc.js
.git
.github
.gitignore
Expand All @@ -13,4 +13,5 @@ images
node_modules
scripts
test
coverage
update.sh
45 changes: 45 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

module.exports = {
'env': {
'jest/globals': true,
'es6': true,
'node': true,
},
'extends': ['eslint:recommended', 'google', 'plugin:jest/recommended', 'plugin:jest/style'],
'parserOptions': {
'ecmaVersion': 2018,
'sourceType': 'module',
},
'rules': {
'require-jsdoc': 'off',
'indent': ['error', 4],
'max-len': ['error', {'code': 120}],
'no-prototype-builtins': 'off',
},
'plugins': [
'jest',
],
'overrides': [{
files: ['*.ts'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended'],
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/semi': ['error'],
'array-bracket-spacing': ['error', 'never'],
'indent': ['error', 4],
'max-len': ['error', {'code': 120}],
'no-return-await': 'error',
'object-curly-spacing': ['error', 'never'],
},
}],
};
21 changes: 0 additions & 21 deletions .eslintrc.json

This file was deleted.

2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Build
run: npm run build
- name: Test
run: npm run test-with-coverage
- name: Lint
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pids
*.seed
*.pid.lock

# Compiled source
dist/*

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

Expand Down
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ coverage
.jenkins.yml
.codeclimate.yml
.github
babel.config.js

#linters
.jscsrc
.jshintrc
.eslintrc*
.dockerignore
.eslintignore
.gitignore

#editor settings
.vscode

#src
lib
docs
docker
images
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ Zigbee2MQTT integrates well with (almost) every home automation solution because
### Internal Architecture
Zigbee2MQTT is made up of three modules, each developed in its own Github project. Starting from the hardware (adapter) and moving up; [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) connects to your Zigbee adapter an makes an API available to the higher levels of the stack. For e.g. Texas Instruments hardware, zigbee-herdsman uses the [TI zStack monitoring and test API](https://github.com/koenkk/zigbee-herdsman/raw/master/docs/Z-Stack%20Monitor%20and%20Test%20API.pdf) to communicate with the adapter. Zigbee-herdsman handles the core Zigbee communication. The module [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters) handles the mapping from individual device models to the Zigbee clusters they support. [Zigbee clusters](https://github.com/Koenkk/zigbee-herdsman/raw/master/docs/Zigbee%20Cluster%20Library%20Specification%20v7.pdf) are the layers of the Zigbee protocol on top of the base protocol that define things like how lights, sensors and switches talk to each other over the Zigbee network. Finally, the Zigbee2MQTT module drives zigbee-herdsman and maps the zigbee messages to MQTT messages. Zigbee2MQTT also keeps track of the state of the system. It uses a `database.db` file to store this state; a text file with a JSON database of connected devices and their capabilities.

### Developing
Zigbee2MQTT uses TypeScript (partially for now). Therefore after making changes to files in the `lib/` directory you need to recompile Zigbee2MQTT. This can be done by executing `npm run build`. For faster development instead of running `npm run build` you can run `npm run build-watch` in another terminal session, this will recompile as you change files.

## Supported devices
See [Supported devices](https://www.zigbee2mqtt.io/information/supported_devices.html) to check whether your device is supported. There is quite an extensive list, including devices from vendors like Xiaomi, Ikea, Philips, OSRAM and more.

Expand Down
6 changes: 6 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};
18 changes: 11 additions & 7 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@ RUN apk add --no-cache tzdata eudev tini

COPY package.json ./

# Dependencies
FROM base as dependencies
# Dependencies and build
FROM base as dependencies_and_build

COPY npm-shrinkwrap.json ./
COPY npm-shrinkwrap.json tsconfig.json index.js ./
COPY lib ./lib

RUN apk add --no-cache --virtual .buildtools make gcc g++ python3 linux-headers git && \
npm ci --production && \
npm ci --no-audit --no-optional --no-update-notifier && \
npm run build && \
rm -rf node_modules && \
npm ci --production --no-audit --no-optional --no-update-notifier && \
apk del .buildtools

# Release
FROM base as release

COPY --from=dependencies /app/node_modules ./node_modules
COPY lib ./lib
COPY --from=dependencies_and_build /app/node_modules ./node_modules
COPY --from=dependencies_and_build /app/dist ./dist
COPY LICENSE index.js data/configuration.yaml ./

COPY docker/docker-entrypoint.sh /usr/local/bin/
Expand All @@ -27,7 +31,7 @@ RUN chmod +x /usr/local/bin/docker-entrypoint.sh
RUN mkdir /app/data

ARG COMMIT
RUN echo "{\"hash\": \"$COMMIT\"}" > .hash.json
RUN echo "$COMMIT" > dist/.hash

ENTRYPOINT ["docker-entrypoint.sh"]
CMD [ "/sbin/tini", "--", "node", "index.js"]
1 change: 0 additions & 1 deletion docker/README.md

This file was deleted.

52 changes: 50 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
const semver = require('semver');
const engines = require('./package.json').engines;
const indexJsRestart = 'indexjs.restart';
const fs = require('fs');
const path = require('path');
const {exec} = require('child_process');
const rimraf = require('rimraf');

let controller;
let stopping = false;

const hashFile = path.join('dist', '.hash');

async function restart() {
await stop(indexJsRestart);
await start();
Expand All @@ -16,14 +22,56 @@ async function exit(code, reason) {
}
}

async function currentHash() {
const git = require('git-last-commit');
return new Promise((resolve) => {
git.getLastCommit((err, commit) => {
if (err) resolve('unknown');
else resolve(commit.shortHash);
});
});
}

async function build(reason) {
return new Promise((resolve, reject) => {
process.stdout.write(`Building Zigbee2MQTT... (${reason})`);
rimraf.sync('dist');
exec('npm run build', {cwd: __dirname}, async (err, stdout, stderr) => {
if (err) {
process.stdout.write(', failed\n');
reject(err);
} else {
process.stdout.write(', finished\n');
const hash = await currentHash();
fs.writeFileSync(hashFile, hash);
resolve();
}
});
});
}

async function checkDist() {
if (!fs.existsSync(hashFile)) {
await build('initial build');
}

const distHash = fs.readFileSync(hashFile, 'utf-8');
const hash = await currentHash();
if (hash !== 'unknown' && distHash !== hash) {
await build('hash changed');
}
}

async function start() {
await checkDist();

const version = engines.node;
if (!semver.satisfies(process.version, version)) {
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`); // eslint-disable-line
}

// Validate settings
const settings = require('./lib/util/settings');
const settings = require('./dist/util/settings');
settings.reRead();
const errors = settings.validate();
if (errors.length > 0) {
Expand All @@ -38,7 +86,7 @@ async function start() {
exit(1);
}

const Controller = require('./lib/controller');
const Controller = require('./dist/controller');
controller = new Controller(restart, exit);
await controller.start();
}
Expand Down
2 changes: 1 addition & 1 deletion lib/extension/homeassistant.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const settings = require('../util/settings');
const logger = require('../util/logger');
const utils = require('../util/utils');
const zigbee2mqttVersion = require('../../package.json').version;
const zigbee2mqttVersion = require('../..' + '/package.json').version;
const Extension = require('./extension');
const stringify = require('json-stable-stringify-without-jsonify');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
Expand Down
7 changes: 4 additions & 3 deletions lib/util/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,17 @@ function capitalize(s) {
async function getZigbee2mqttVersion() {
return new Promise((resolve, reject) => {
const git = require('git-last-commit');
const packageJSON = require('../../package.json');
const packageJSON = require('../..' + '/package.json');
const version = packageJSON.version;

git.getLastCommit((err, commit) => {
let commitHash = null;

if (err) {
try {
commitHash = require('../../.hash.json').hash;
commitHash = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', '.hash'), 'utf-8');
} catch (error) {
/* istanbul ignore next */
commitHash = 'unknown';
}
} else {
Expand All @@ -75,7 +76,7 @@ async function getZigbee2mqttVersion() {

async function getDependencyVersion(depend) {
return new Promise((resolve, reject) => {
const packageJSON = require('../../node_modules/'+depend+'/package.json');
const packageJSON = require(path.join(__dirname, '..', '..', 'node_modules', depend, 'package.json'));
const version = packageJSON.version;
resolve({version});
});
Expand Down
17 changes: 10 additions & 7 deletions lib/util/yaml.js → lib/util/yaml.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const yaml = require('js-yaml');
const fs = require('fs');
const equals = require('fast-deep-equal/es6');
import yaml from 'js-yaml';
import fs from 'fs';
import equals from 'fast-deep-equal/es6';
import 'source-map-support/register';

function read(file) {
function read(file: string): Record<string, unknown> {
try {
// eslint-disable-next-line
// @ts-ignore
return yaml.load(fs.readFileSync(file, 'utf8'));
} catch (error) {
if (error.name === 'YAMLException') {
Expand All @@ -14,18 +17,18 @@ function read(file) {
}
}

function readIfExists(file, default_=null) {
function readIfExists(file: string, default_?: Record<string, unknown>): Record<string, unknown> {
return fs.existsSync(file) ? read(file) : default_;
}

function writeIfChanged(file, content) {
function writeIfChanged(file: string, content: Record<string, unknown>): void {
const before = readIfExists(file);
if (!equals(before, content)) {
fs.writeFileSync(file, yaml.dump(content));
}
}

function updateIfChanged(file, key, value) {
function updateIfChanged(file: string, key: string, value: unknown): void {
const content = read(file);
if (content[key] !== value) {
content[key] = value;
Expand Down
Loading

0 comments on commit 7b65dc6

Please sign in to comment.