Skip to content

Commit deddc1b

Browse files
chore: move to esm (#4403)
This pull request introduces several significant updates across the codebase, primarily focused on modernizing the Node.js environment, allow code sharing between backend and UI, improving TypeScript usage, and enhancing import consistency. The most notable changes include upgrading to Node.js 22, transitioning all imports to use explicit `.ts` extensions and more precise type-only imports, and updating workflow and configuration files to align with these changes. **Node.js and Tooling Upgrades:** * Upgraded Node.js version from 20 to 22 in both the GitHub Actions workflow (`.github/workflows/test-application.yml`) and `.nvmrc`, ensuring the project uses the latest LTS features and performance improvements. [[1]](diffhunk://#diff-9261914205164383e5e6930bef1909548ba0acc9cd1a241217e88b8dbb730894L21-R21) [[2]](diffhunk://#diff-2c8effdf840690ccb88c7e21e56148978c6adb2c6926530c58ff0e47a9588b44L1-R1) * Updated the workflow to build the backend before starting it, and adjusted process checks and log outputs to match the new start command. [[1]](diffhunk://#diff-9261914205164383e5e6930bef1909548ba0acc9cd1a241217e88b8dbb730894L84-R85) [[2]](diffhunk://#diff-9261914205164383e5e6930bef1909548ba0acc9cd1a241217e88b8dbb730894R95-R108) **TypeScript and Import Improvements:** * Converted all internal imports to use explicit `.ts` extensions and transitioned to type-only imports where appropriate, improving type safety and compatibility with ES module standards. This affects nearly all files in the `api/` directory, including `app.ts`, `config/store.ts`, `lib/Gateway.ts`, and others. [[1]](diffhunk://#diff-fe6a6140f19ab1b0dd1e7eac27de4a8366de9b7c0da27d292f99ba219e5a3e97L1-R31) [[2]](diffhunk://#diff-fe6a6140f19ab1b0dd1e7eac27de4a8366de9b7c0da27d292f99ba219e5a3e97L35-R53) [[3]](diffhunk://#diff-2abb3e2a5c46a2bdec870296307cc957672d574ee97c5c148d9947d3ea9fef1fL3-R7) [[4]](diffhunk://#diff-3e2f08e1ca9a7cf0505f53c191827d903a5b7c23e37099590645c0fe8b34e7a0L1-R30) [[5]](diffhunk://#diff-529b5159720cb6dfa3c4cbdc74cbd4e926b9d4b2ec1f861db52e4464c40dcdbcL1-R4) [[6]](diffhunk://#diff-1181514579645f4a4c65086a3f127004ab212aa1aa37c9b66be0ab1b1e3b8e38L3-R23) [[7]](diffhunk://#diff-9e87e739e41326d30862db11b85ba2fcbdfd938fd95aa1ff4be27b2e12ba83f9L3-R8) [[8]](diffhunk://#diff-89d06f80641536a8b9d0cfc838a6f1528505040ed8509ad4809caf44d0c0114eR1-R20) [[9]](diffhunk://#diff-21e7a2e7559aeee5103b6ea891ae6bdd44cda13256496671cd9b0a349a7ab0fbR1-L85) [[10]](diffhunk://#diff-1ddaf3ef24f2e86d72fe6c6a971f5a10c696daaf5b3702fa16c110cb12d8001aL1-R8) [[11]](diffhunk://#diff-5df935e8cb44f1ec7180e734ca106c3d6f5429654d9ccb6537a61f04431bfc76L1-R1) [[12]](diffhunk://#diff-cce2f94deee2e0540eceb55c4ba5ff08ca38f50568ea2a7eea5b55b5d3804018L4-R4) [[13]](diffhunk://#diff-fe9fabd25eb368d77a1700a270584d420f2631dcbdd691c29638683cc9456244L3-R3) **Configuration and Linting Updates:** * Changed `.prettierrc.js` to use ES module export syntax, aligning with modern JavaScript module standards. * Removed the `watch-files` option from `.mocharc.yml`, possibly to streamline or fix test runner behavior. **Codebase Consistency and Minor Fixes:** * Standardized usage of Node.js built-in modules by using the `node:` prefix (e.g., `node:fs`, `node:path`) across the codebase. [[1]](diffhunk://#diff-fe6a6140f19ab1b0dd1e7eac27de4a8366de9b7c0da27d292f99ba219e5a3e97L1-R31) [[2]](diffhunk://#diff-3e2f08e1ca9a7cf0505f53c191827d903a5b7c23e37099590645c0fe8b34e7a0L1-R30) [[3]](diffhunk://#diff-1181514579645f4a4c65086a3f127004ab212aa1aa37c9b66be0ab1b1e3b8e38L3-R23) [[4]](diffhunk://#diff-89d06f80641536a8b9d0cfc838a6f1528505040ed8509ad4809caf44d0c0114eR1-R20) [[5]](diffhunk://#diff-1ddaf3ef24f2e86d72fe6c6a971f5a10c696daaf5b3702fa16c110cb12d8001aL1-R8) * Added or updated utility exports and constants, such as introducing `deviceConfigPriorityDir` in `Constants.ts` and updating its usage in `config/store.ts`. [[1]](diffhunk://#diff-6e3049fb752155a398da8d028881cd4e16401429ba95c63cb9ccfba85e071d79R48-R49) [[2]](diffhunk://#diff-2abb3e2a5c46a2bdec870296307cc957672d574ee97c5c148d9947d3ea9fef1fL3-R7) These changes collectively modernize the codebase, improve maintainability, and ensure compatibility with the latest Node.js and TypeScript features. **References:** [[1]](diffhunk://#diff-9261914205164383e5e6930bef1909548ba0acc9cd1a241217e88b8dbb730894L21-R21) [[2]](diffhunk://#diff-2c8effdf840690ccb88c7e21e56148978c6adb2c6926530c58ff0e47a9588b44L1-R1) [[3]](diffhunk://#diff-9261914205164383e5e6930bef1909548ba0acc9cd1a241217e88b8dbb730894L84-R85) [[4]](diffhunk://#diff-9261914205164383e5e6930bef1909548ba0acc9cd1a241217e88b8dbb730894R95-R108) [[5]](diffhunk://#diff-fe6a6140f19ab1b0dd1e7eac27de4a8366de9b7c0da27d292f99ba219e5a3e97L1-R31) [[6]](diffhunk://#diff-fe6a6140f19ab1b0dd1e7eac27de4a8366de9b7c0da27d292f99ba219e5a3e97L35-R53) [[7]](diffhunk://#diff-2abb3e2a5c46a2bdec870296307cc957672d574ee97c5c148d9947d3ea9fef1fL3-R7) [[8]](diffhunk://#diff-3e2f08e1ca9a7cf0505f53c191827d903a5b7c23e37099590645c0fe8b34e7a0L1-R30) [[9]](diffhunk://#diff-529b5159720cb6dfa3c4cbdc74cbd4e926b9d4b2ec1f861db52e4464c40dcdbcL1-R4) [[10]](diffhunk://#diff-1181514579645f4a4c65086a3f127004ab212aa1aa37c9b66be0ab1b1e3b8e38L3-R23) [[11]](diffhunk://#diff-9e87e739e41326d30862db11b85ba2fcbdfd938fd95aa1ff4be27b2e12ba83f9L3-R8) [[12]](diffhunk://#diff-89d06f80641536a8b9d0cfc838a6f1528505040ed8509ad4809caf44d0c0114eR1-R20) [[13]](diffhunk://#diff-21e7a2e7559aeee5103b6ea891ae6bdd44cda13256496671cd9b0a349a7ab0fbR1-L85) [[14]](diffhunk://#diff-1ddaf3ef24f2e86d72fe6c6a971f5a10c696daaf5b3702fa16c110cb12d8001aL1-R8) [[15]](diffhunk://#diff-5df935e8cb44f1ec7180e734ca106c3d6f5429654d9ccb6537a61f04431bfc76L1-R1) [[16]](diffhunk://#diff-cce2f94deee2e0540eceb55c4ba5ff08ca38f50568ea2a7eea5b55b5d3804018L4-R4) [[17]](diffhunk://#diff-fe9fabd25eb368d77a1700a270584d420f2631dcbdd691c29638683cc9456244L3-R3) [[18]](diffhunk://#diff-7312296367dd7c725b9beb500d7dab7bbeeb2aa03a1010a3fb2935f826d03fa6L1-R1) [[19]](diffhunk://#diff-751a54bee66ebd0d32763befd5f74abfe8c00f15aeeb61b8509ff3ed8d52ec89L2-L3) [[20]](diffhunk://#diff-6e3049fb752155a398da8d028881cd4e16401429ba95c63cb9ccfba85e071d79R48-R49) Fixes #4401 --------- Co-authored-by: Dominic Griesel <[email protected]>
1 parent 19ec7e0 commit deddc1b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1461
-761
lines changed

.github/workflows/test-application.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Set up Node.js
1919
uses: actions/setup-node@v5
2020
with:
21-
node-version: '20'
21+
node-version: '22'
2222
cache: 'npm'
2323

2424
- name: Install dependencies
@@ -81,7 +81,8 @@ jobs:
8181
8282
- name: Start backend
8383
run: |
84-
nohup npm run server > server.log 2>&1 &
84+
npm run build:server
85+
nohup npm start > server.log 2>&1 &
8586
sleep 25
8687
8788
- name: Test MQTT write and read
@@ -91,17 +92,20 @@ jobs:
9192
docker exec broker mosquitto_sub -h localhost -p 1883 -t "zwave/nodeID_2/106/0/currentValue/3" -C 1
9293
9394
- name: Output backend logs
95+
if: always()
9496
run: |
9597
sleep 5
9698
cat server.log
9799
98100
- name: Output broker logs
101+
if: always()
99102
run: |
100103
docker logs broker
101104
102105
- name: Ensure backend is running
106+
if: always()
103107
run: |
104-
if ! pgrep -f "npm run server" > /dev/null; then
108+
if ! pgrep -f "npm start" > /dev/null; then
105109
echo "Backend crashed!" && exit 1
106110
fi
107111
echo "Backend is running successfully."

.mocharc.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
recursive: true
2-
watch-files:
3-
- 'test/**/*.ts'

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v20.19.4
1+
v22.20.0

.prettierrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = {
1+
export default {
22
semi: false,
33
singleQuote: true,
44
useTabs: true,

api/app.ts

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
1-
import express, { Request, RequestHandler, Response, Router } from 'express'
1+
import type { Request, RequestHandler, Response, Router } from 'express'
2+
import express from 'express'
23
import history from 'connect-history-api-fallback'
34
import cors from 'cors'
45
import csrf from 'csurf'
56
import morgan from 'morgan'
6-
import store, { Settings, User } from './config/store'
7-
import Gateway, { GatewayConfig, GatewayType } from './lib/Gateway'
8-
import jsonStore from './lib/jsonStore'
9-
import * as loggers from './lib/logger'
10-
import MqttClient from './lib/MqttClient'
11-
import SocketManager from './lib/SocketManager'
12-
import ZWaveClient, { CallAPIResult, SensorTypeScale } from './lib/ZwaveClient'
7+
import type { Settings, User } from './config/store.ts'
8+
import store from './config/store.ts'
9+
import type { GatewayConfig } from './lib/Gateway.ts'
10+
import Gateway, { GatewayType } from './lib/Gateway.ts'
11+
import jsonStore from './lib/jsonStore.ts'
12+
import * as loggers from './lib/logger.ts'
13+
import MqttClient from './lib/MqttClient.ts'
14+
import SocketManager from './lib/SocketManager.ts'
15+
import type { CallAPIResult, SensorTypeScale } from './lib/ZwaveClient.ts'
16+
import ZWaveClient from './lib/ZwaveClient.ts'
1317
import multer, { diskStorage } from 'multer'
1418
import extract from 'extract-zip'
1519
import { serverVersion } from '@zwave-js/server'
1620
import archiver from 'archiver'
1721
import rateLimit from 'express-rate-limit'
1822
import session from 'express-session'
19-
import fs, { mkdirp, move, readdir, rm, stat } from 'fs-extra'
20-
import { createServer as createHttpServer, Server as HttpServer } from 'http'
21-
import { createServer as createHttpsServer } from 'https'
23+
import type { Server as HttpServer } from 'node:http'
24+
import { createServer as createHttpServer } from 'node:http'
25+
import { createServer as createHttpsServer } from 'node:https'
2226
import jwt from 'jsonwebtoken'
23-
import path from 'path'
27+
import path from 'node:path'
2428
import sessionStore from 'session-file-store'
25-
import { Socket } from 'socket.io'
26-
import { promisify } from 'util'
29+
import type { Socket } from 'socket.io'
30+
import { promisify } from 'node:util'
2731
import { Driver, libVersion } from 'zwave-js'
2832
import {
2933
defaultPsw,
@@ -32,18 +36,26 @@ import {
3236
snippetsDir,
3337
storeDir,
3438
tmpDir,
35-
} from './config/app'
39+
} from './config/app.ts'
40+
import type { CustomPlugin, PluginConstructor } from './lib/CustomPlugin.ts'
41+
import { createPlugin } from './lib/CustomPlugin.ts'
42+
import { inboundEvents, socketEvents } from './lib/SocketEvents.ts'
43+
import * as utils from './lib/utils.ts'
44+
import backupManager from './lib/BackupManager.ts'
3645
import {
37-
createPlugin,
38-
CustomPlugin,
39-
PluginConstructor,
40-
} from './lib/CustomPlugin'
41-
import { inboundEvents, socketEvents } from './lib/SocketEvents'
42-
import * as utils from './lib/utils'
43-
import backupManager from './lib/BackupManager'
44-
import { readFile, realpath } from 'fs/promises'
46+
readFile,
47+
realpath,
48+
readdir,
49+
stat,
50+
rm,
51+
rename,
52+
writeFile,
53+
lstat,
54+
mkdir,
55+
} from 'node:fs/promises'
4556
import { generate } from 'selfsigned'
46-
import ZnifferManager, { ZnifferConfig } from './lib/ZnifferManager'
57+
import type { ZnifferConfig } from './lib/ZnifferManager.ts'
58+
import ZnifferManager from './lib/ZnifferManager.ts'
4759
import { getAllNamedScaleGroups, getAllSensors } from '@zwave-js/core'
4860

4961
const createCertificate = promisify(generate)
@@ -72,7 +84,7 @@ function multerPromise(
7284

7385
const Storage = diskStorage({
7486
async destination(reqD, file, callback) {
75-
await mkdirp(tmpDir)
87+
await utils.ensureDir(tmpDir)
7688
callback(null, tmpDir)
7789
},
7890
filename(reqF, file, callback) {
@@ -262,7 +274,7 @@ const defaultSnippets: utils.Snippet[] = []
262274

263275
async function loadSnippets() {
264276
const localSnippetsDir = utils.joinPath(false, 'snippets')
265-
await mkdirp(snippetsDir)
277+
await utils.ensureDir(snippetsDir)
266278

267279
const files = await readdir(localSnippetsDir)
268280
for (const file of files) {
@@ -329,8 +341,8 @@ async function loadCertKey(): Promise<{
329341
let cert: string
330342

331343
try {
332-
cert = await fs.readFile(certFile, 'utf8')
333-
key = await fs.readFile(keyFile, 'utf8')
344+
cert = await readFile(certFile, 'utf8')
345+
key = await readFile(keyFile, 'utf8')
334346
} catch (error) {
335347
// noop
336348
}
@@ -349,8 +361,8 @@ async function loadCertKey(): Promise<{
349361
key = result.private
350362
cert = result.cert
351363

352-
await fs.writeFile(utils.joinPath(storeDir, 'key.pem'), key)
353-
await fs.writeFile(utils.joinPath(storeDir, 'cert.pem'), cert)
364+
await writeFile(utils.joinPath(storeDir, 'key.pem'), key)
365+
await writeFile(utils.joinPath(storeDir, 'cert.pem'), cert)
354366
logger.info('New cert and key created')
355367
} catch (error) {
356368
logger.error('Error creating cert and key for HTTPS', error)
@@ -447,14 +459,14 @@ function setupInterceptor() {
447459

448460
async function parseDir(dir: string): Promise<StoreFileEntry[]> {
449461
const toReturn = []
450-
const files = await fs.readdir(dir)
462+
const files = await readdir(dir)
451463
for (const file of files) {
452464
try {
453465
const entry: StoreFileEntry = {
454466
name: path.basename(file),
455467
path: utils.joinPath(dir, file),
456468
}
457-
const stats = await fs.lstat(entry.path)
469+
const stats = await lstat(entry.path)
458470
if (stats.isDirectory()) {
459471
if (entry.path === process.env.ZWAVEJS_EXTERNAL_CONFIG) {
460472
// hide config-db
@@ -1367,18 +1379,18 @@ app.get('/api/store', storeLimiter, isAuthenticated, async function (req, res) {
13671379
if (req.query.path) {
13681380
const reqPath = getSafePath(req)
13691381
// lgtm [js/path-injection]
1370-
let stat = await fs.lstat(reqPath)
1382+
let stat = await lstat(reqPath)
13711383

13721384
// check symlink is secure
13731385
if (stat.isSymbolicLink()) {
13741386
const realPath = await realpath(reqPath)
13751387
getSafePath(realPath)
1376-
stat = await fs.lstat(realPath)
1388+
stat = await lstat(realPath)
13771389
}
13781390

13791391
if (stat.isFile()) {
13801392
// lgtm [js/path-injection]
1381-
data = await fs.readFile(reqPath, 'utf8')
1393+
data = await readFile(reqPath, 'utf8')
13821394
} else {
13831395
// read directory
13841396
// lgtm [js/path-injection]
@@ -1411,7 +1423,7 @@ app.put('/api/store', storeLimiter, isAuthenticated, async function (req, res) {
14111423

14121424
if (!isNew) {
14131425
// lgtm [js/path-injection]
1414-
const stat = await fs.lstat(reqPath)
1426+
const stat = await lstat(reqPath)
14151427

14161428
if (!stat.isFile()) {
14171429
throw Error('Path is not a file')
@@ -1420,10 +1432,10 @@ app.put('/api/store', storeLimiter, isAuthenticated, async function (req, res) {
14201432

14211433
if (!isDirectory) {
14221434
// lgtm [js/path-injection]
1423-
await fs.writeFile(reqPath, req.body.content, 'utf8')
1435+
await writeFile(reqPath, req.body.content, 'utf8')
14241436
} else {
14251437
// lgtm [js/path-injection]
1426-
await fs.mkdir(reqPath)
1438+
await mkdir(reqPath)
14271439
}
14281440

14291441
res.json({ success: true })
@@ -1442,7 +1454,7 @@ app.delete(
14421454
const reqPath = getSafePath(req)
14431455

14441456
// lgtm [js/path-injection]
1445-
await fs.remove(reqPath)
1457+
await rm(reqPath, { recursive: true, force: true })
14461458

14471459
res.json({ success: true })
14481460
} catch (error) {
@@ -1460,7 +1472,7 @@ app.put(
14601472
try {
14611473
const files = req.body.files || []
14621474
for (const f of files) {
1463-
await fs.remove(f)
1475+
await rm(f, { recursive: true, force: true })
14641476
}
14651477
res.json({ success: true })
14661478
} catch (error) {
@@ -1498,7 +1510,7 @@ app.post(
14981510
archive.pipe(res)
14991511

15001512
for (const f of files) {
1501-
const s = await fs.lstat(f)
1513+
const s = await lstat(f)
15021514
const name = f.replace(storeDir, '')
15031515
if (s.isFile()) {
15041516
archive.file(f, { name })
@@ -1559,7 +1571,7 @@ app.post(
15591571
const destinationPath = getSafePath(
15601572
path.join(storeDir, folder, file.originalname),
15611573
)
1562-
await move(file.path, destinationPath)
1574+
await rename(file.path, destinationPath)
15631575
}
15641576

15651577
res.json({ success: true })

api/bin/www.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
/**
55
* Module dependencies.
66
*/
7-
import jsonStore from '../lib/jsonStore'
8-
import store from '../config/store'
9-
import * as conf from '../config/app'
10-
import app, { startServer } from '../app'
7+
import jsonStore from '../lib/jsonStore.ts'
8+
import store from '../config/store.ts'
9+
import * as conf from '../config/app.ts'
10+
import app, { startServer } from '../app.ts'
1111

1212
console.log(
1313
` ______ __ __ _ _____ _ _ _____ \n |___ / \\ \\ / / | |/ ____| | | | |_ _|\n / /____\\ \\ /\\ / /_ ___ _____ | | (___ | | | | | | \n / /______\\ \\/ \\/ / _\' \\ \\ / / _ \\ _ | |\\___ \\ | | | | | | \n / /__ \\ /\\ / (_| |\\ V / __/ | |__| |____) | | |__| |_| |_ \n /_____| \\/ \\/ \\__,_| \\_/ \\___| \\____/|_____/ \\____/|_____|\n`,

api/config/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { joinPath } from '../lib/utils'
1+
import { joinPath } from '../lib/utils.ts'
22
import { config } from 'dotenv'
33

44
config({ path: './.env.app' })

api/config/store.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// config/store.js
22

3-
import { GatewayConfig } from '../lib/Gateway'
4-
import { MqttConfig } from '../lib/MqttClient'
5-
import { ZnifferConfig } from '../lib/ZnifferManager'
6-
import { ZwaveConfig, deviceConfigPriorityDir } from '../lib/ZwaveClient'
3+
import type { GatewayConfig } from '../lib/Gateway.ts'
4+
import type { MqttConfig } from '../lib/MqttClient.ts'
5+
import type { ZnifferConfig } from '../lib/ZnifferManager.ts'
6+
import type { ZwaveConfig } from '../lib/ZwaveClient.ts'
7+
import { deviceConfigPriorityDir } from '../lib/Constants.ts'
78

89
export type StoreKeys = 'settings' | 'scenes' | 'nodes' | 'users'
910

api/hass/configurations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// List of Home-Assistant configuration for MQTT Discovery
22
// https://www.home-assistant.io/docs/mqtt/discovery/
33

4-
import { HassDevice } from '../lib/ZwaveClient'
4+
import type { HassDevice } from '../lib/ZwaveClient.ts'
55

66
type HassDeviceKey =
77
| 'binary_sensor'

api/hass/devices.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Place here repeated patterns
22

3-
import { HassDevice } from '../lib/ZwaveClient'
3+
import type { HassDevice } from '../lib/ZwaveClient.ts'
44

55
const FAN_DIMMER: HassDevice = {
66
type: 'fan',

0 commit comments

Comments
 (0)