iobroker.staticsfilefolder 0.0.23 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,6 +64,15 @@ Once configured and the instance is running (green status):
64
64
  Placeholder for the next version (at the beginning of the line):
65
65
  ### **WORK IN PROGRESS**
66
66
  -->
67
+ ### 0.0.26
68
+ * Regenerated lockfile cleanly to resolve npm ci errors on GitHub Actions workflow.
69
+
70
+ ### 0.0.25
71
+ * Resolved ESLint peer dependency warnings, linked CHANGELOG_OLD in README, updated check-and-lint workflow to Node 24, and added major auto-merge rules for GitHub actions.
72
+
73
+ ### 0.0.24
74
+ * Resolved all remaining repochecker warnings and suggestions, migrated to standard automerge-dependabot workflow, and updated to ESLint v9 with flat config.
75
+
67
76
  ### 0.0.23
68
77
  * Added live rendering / auto-refresh in file explorer on file additions/deletions.
69
78
 
@@ -94,6 +103,8 @@ Once configured and the instance is running (green status):
94
103
  ### 0.0.1
95
104
  * (gokturk413) initial release: SPA, Offline document viewers, Chokidar file observer.
96
105
 
106
+ [Older changelog entries](CHANGELOG_OLD.md)
107
+
97
108
  ## License
98
109
  MIT License
99
110
 
package/io-package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "staticsfilefolder",
4
- "version": "0.0.23",
4
+ "version": "0.0.26",
5
5
  "news": {
6
- "0.0.23": {
7
- "en": "Added live rendering / auto-refresh in file explorer on file additions/deletions.",
8
- "de": "Live-Rendering / automatische Aktualisierung im Datei-Explorer bei Hinzufügung/Löschung von Dateien hinzugefügt.",
9
- "ru": "Добавлено живое отображение / автообновление в проводнике файлов при добавлении/удалении файлов.",
10
- "pt": "Adicionada renderização ao vivo / atualização automática no explorador de arquivos ao adicionar/excluir arquivos.",
11
- "nl": "Live rendering / auto-refresh toegevoegd in de bestandsbrowser bij toevoeging/verwijdering van bestanden.",
12
- "fr": "Ajout du rendu en direct / de l'actualisation automatique dans l'explorateur de fichiers lors de l'ajout/suppression de fichiers.",
13
- "it": "Aggiunto rendering live / aggiornamento automatico in Esplora file all'aggiunta/eliminazione di file.",
14
- "es": "Se agregó renderizado en vivo / actualización automática en el explorador de archivos al agregar o eliminar archivos.",
15
- "pl": "Dodano renderowanie na żywo / automatyczne odświeżanie w eksploratorze plików przy dodawaniu/usuwaniu plików.",
16
- "uk": "Додано живе відображення / автооновлення в провіднику файлів при додаванні/вилученні файлів.",
17
- "zh-cn": "在文件资源管理器中添加了在添加/删除文件时的实时渲染/自动刷新。"
6
+ "0.0.26": {
7
+ "en": "Regenerated lockfile cleanly to resolve npm ci errors on GitHub Actions workflow.",
8
+ "de": "Lockfile sauber regeneriert, um npm-ci-Fehler im GitHub Actions-Workflow zu beheben.",
9
+ "ru": "Чистая регенерация lockfile для устранения ошибок npm ci в рабочем процессе GitHub Actions.",
10
+ "pt": "Regenerado o lockfile de forma limpa para resolver erros do npm ci no fluxo de trabalho do GitHub Actions.",
11
+ "nl": "Lockfile schoon geregenereerd om npm ci-fouten in de GitHub Actions-workflow op te lossen.",
12
+ "fr": "Régénération propre du fichier lockfile pour résoudre les erreurs npm ci dans le flux de travail GitHub Actions.",
13
+ "it": "Rigenerato in modo pulito il lockfile per risolvere gli errori di npm ci nel flusso di lavoro di GitHub Actions.",
14
+ "es": "Se regeneró el archivo lockfile de forma limpia para resolver los errores de npm ci en el flujo de trabajo de GitHub Actions.",
15
+ "pl": "Czysto zregenerowano plik blokady lockfile, aby rozwiązać błędy npm ci w przepływie pracy GitHub Actions.",
16
+ "uk": "Чиста регенерація lockfile для усунення помилок npm ci у робочому процесі GitHub Actions.",
17
+ "zh-cn": "干净地重新生成 lockfile,以解决 GitHub Actions 工作流中的 npm ci 错误。"
18
18
  },
19
19
  "0.0.15": {
20
20
  "en": "Reverted minimum Node.js engine requirement to 20 to support GitHub Actions runners.",
@@ -99,8 +99,9 @@
99
99
  "gokturk413 <gokturk413@gmail.com>"
100
100
  ],
101
101
  "keywords": [
102
- "ioBroker",
103
- "Static Files Directory Browsing"
102
+ "static-files",
103
+ "directory-browsing",
104
+ "file-viewer"
104
105
  ],
105
106
  "licenseInformation": {
106
107
  "type": "free",
@@ -1,19 +1,20 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
1
2
  // This file extends the AdapterConfig type from "@types/iobroker"
2
3
  // using the actual properties present in io-package.json
3
4
  // in order to provide typings for adapter.config properties
4
5
 
5
- import { native } from "../io-package.json";
6
+ import type { native } from '../io-package.json';
6
7
 
7
8
  type _AdapterConfig = typeof native;
8
9
 
9
10
  // Augment the globally declared type ioBroker.AdapterConfig
10
11
  declare global {
11
- namespace ioBroker {
12
- interface AdapterConfig extends _AdapterConfig {
13
- // Do not enter anything here!
14
- }
15
- }
12
+ namespace ioBroker {
13
+ interface AdapterConfig extends _AdapterConfig {
14
+ // Do not enter anything here!
15
+ }
16
+ }
16
17
  }
17
18
 
18
19
  // this is required so the above AdapterConfig is found by TypeScript / type checking
19
- export {};
20
+ export {};
package/lib/web.js CHANGED
@@ -1,88 +1,91 @@
1
- const express = require("express");
2
- const path = require("node:path");
3
- const fs = require("node:fs");
1
+ const express = require('express');
2
+ const path = require('node:path');
3
+ const fs = require('node:fs');
4
4
 
5
+ /**
6
+ * Extension class to serve static files and configuration API endpoints.
7
+ */
5
8
  class ExtensionExample {
6
- /**
9
+ /**
7
10
  * @param {object} server http or https node.js object
8
11
  * @param {object} webSettings settings of the web server
9
12
  * @param {object} adapter web adapter object
10
13
  * @param {object} instanceSettings instance object with common and native
11
14
  * @param {object} app express application
12
15
  */
13
- constructor(server, webSettings, adapter, instanceSettings, app) {
14
- this.app = app;
15
- this.adapter = adapter;
16
- this.config = instanceSettings ? instanceSettings.native : {};
17
- this.config.route = this.config.route || "demo";
16
+ constructor(server, webSettings, adapter, instanceSettings, app) {
17
+ this.app = app;
18
+ this.adapter = adapter;
19
+ this.config = instanceSettings ? instanceSettings.native : {};
20
+ this.config.route = this.config.route || 'demo';
18
21
 
19
- try {
20
- adapter.log.info("Install extension on /" + this.config.route);
22
+ try {
23
+ adapter.log.info(`Install extension on /${this.config.route}`);
21
24
 
22
- // Serve our SPA static files from www folder
23
- const wwwPath = path.join(__dirname, "../www");
24
- this.app.use("/" + this.config.route, express.static(wwwPath));
25
+ // Serve our SPA static files from www folder
26
+ const wwwPath = path.join(__dirname, '../www');
27
+ this.app.use(`/${this.config.route}`, express.static(wwwPath));
25
28
 
26
- // Serve the reports directory so files can be accessed via URL
27
- if (this.config.dirname) {
28
- this.app.use("/" + this.config.route + "/files", express.static(this.config.dirname));
29
- }
29
+ // Serve the reports directory so files can be accessed via URL
30
+ if (this.config.dirname) {
31
+ this.app.use(`/${this.config.route}/files`, express.static(this.config.dirname));
32
+ }
30
33
 
31
- // API Endpoint for configuration and system language
32
- this.app.get("/" + this.config.route + "/api/config", async (req, res) => {
33
- try {
34
- const systemConfig = await this.adapter.getForeignObjectAsync("system.config");
35
- const systemLanguage = (systemConfig && systemConfig.common && systemConfig.common.language) || "en";
36
- res.json({ success: true, language: systemLanguage });
37
- } catch (e) {
38
- res.json({ success: false, language: "en" });
39
- }
40
- });
34
+ // API Endpoint for configuration and system language
35
+ this.app.get(`/${this.config.route}/api/config`, async (req, res) => {
36
+ try {
37
+ const systemConfig = await this.adapter.getForeignObjectAsync('system.config');
38
+ const systemLanguage =
39
+ (systemConfig && systemConfig.common && systemConfig.common.language) || 'en';
40
+ res.json({ success: true, language: systemLanguage });
41
+ } catch {
42
+ res.json({ success: false, language: 'en' });
43
+ }
44
+ });
41
45
 
42
- // API Endpoint for listing files
43
- this.app.get("/" + this.config.route + "/api/list", (req, res) => {
44
- try {
45
- const relPath = req.query.path || "";
46
- if (relPath.includes("..")) {
47
- return res.status(400).json({error: "Invalid path"});
48
- }
46
+ // API Endpoint for listing files
47
+ this.app.get(`/${this.config.route}/api/list`, (req, res) => {
48
+ try {
49
+ const relPath = req.query.path || '';
50
+ if (relPath.includes('..')) {
51
+ return res.status(400).json({ error: 'Invalid path' });
52
+ }
49
53
 
50
- const targetDir = path.join(this.config.dirname, relPath);
54
+ const targetDir = path.join(this.config.dirname, relPath);
51
55
 
52
- if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
53
- return res.status(404).json({error: "Directory not found"});
54
- }
56
+ if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
57
+ return res.status(404).json({ error: 'Directory not found' });
58
+ }
55
59
 
56
- const items = fs.readdirSync(targetDir);
57
- const result = [];
58
- for (const item of items) {
59
- try {
60
- const itemPath = path.join(targetDir, item);
61
- const stat = fs.statSync(itemPath);
62
- result.push({
63
- name: item,
64
- isDirectory: stat.isDirectory(),
65
- size: stat.size,
66
- mtime: stat.mtimeMs,
67
- birthtime: stat.birthtimeMs,
68
- ext: path.extname(item).toLowerCase()
69
- });
70
- } catch (err) {
71
- // ignore errors
72
- }
73
- }
60
+ const items = fs.readdirSync(targetDir);
61
+ const result = [];
62
+ for (const item of items) {
63
+ try {
64
+ const itemPath = path.join(targetDir, item);
65
+ const stat = fs.statSync(itemPath);
66
+ result.push({
67
+ name: item,
68
+ isDirectory: stat.isDirectory(),
69
+ size: stat.size,
70
+ mtime: stat.mtimeMs,
71
+ birthtime: stat.birthtimeMs,
72
+ ext: path.extname(item).toLowerCase(),
73
+ });
74
+ } catch {
75
+ // ignore errors
76
+ }
77
+ }
74
78
 
75
- res.json({ success: true, items: result, path: relPath });
76
- } catch(e) {
77
- adapter.log.error("Error in /api/list: " + e);
78
- res.status(500).json({error: e.toString()});
79
- }
80
- });
81
-
82
- } catch (err) {
83
- adapter.log.error("Error during Extension init: " + err.stack);
84
- }
85
- }
79
+ res.json({ success: true, items: result, path: relPath });
80
+ } catch (e) {
81
+ adapter.log.error(`Error in /api/list: ${e}`);
82
+ res.status(500).json({ error: e.toString() });
83
+ }
84
+ });
85
+ } catch (err) {
86
+ adapter.log.error(`Error during Extension init: ${err.stack}`);
87
+ }
88
+ }
86
89
  }
87
90
 
88
- module.exports = ExtensionExample;
91
+ module.exports = ExtensionExample;
package/main.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
3
  /*
4
4
  * Created with @iobroker/create-adapter v2.6.2
@@ -6,133 +6,140 @@
6
6
 
7
7
  // The adapter-core module gives you access to the core ioBroker functions
8
8
  // you need to create an adapter
9
- const utils = require("@iobroker/adapter-core");
9
+ const utils = require('@iobroker/adapter-core');
10
10
 
11
11
  // Load your modules here, e.g.:
12
12
  // const fs = require("fs");
13
13
 
14
14
  class Staticsfilefolder extends utils.Adapter {
15
-
16
- /**
17
- * @param {Partial<utils.AdapterOptions>} [options={}]
18
- */
19
- constructor(options) {
20
- super({
21
- ...options,
22
- name: "staticsfilefolder",
23
- });
24
- this.on("ready", this.onReady.bind(this));
25
- this.on("stateChange", this.onStateChange.bind(this));
26
- this.on("unload", this.onUnload.bind(this));
27
-
28
- this.watcher = null;
29
- }
30
-
31
- /**
32
- * Is called when databases are connected and adapter received configuration.
33
- */
34
- async onReady() {
35
- // Create state for latest file url
36
- await this.setObjectNotExistsAsync("latest_file_url", {
37
- type: "state",
38
- common: {
39
- name: "Latest File URL",
40
- type: "string",
41
- role: "url",
42
- read: true,
43
- write: false,
44
- },
45
- native: {},
46
- });
47
-
48
- // Create state for the latest file path
49
- await this.setObjectNotExistsAsync("latest_file_path", {
50
- type: "state",
51
- common: {
52
- name: "Latest File Path",
53
- type: "string",
54
- role: "value",
55
- read: true,
56
- write: false,
57
- },
58
- native: {},
59
- });
60
-
61
- if (this.config.dirname) {
62
- this.log.info("Starting file observer on: " + this.config.dirname);
63
- try {
64
- const chokidar = require("chokidar");
65
- const path = require("node:path");
66
-
67
- this.watcher = chokidar.watch(this.config.dirname, {
68
- ignored: /(^|[/\\])\../, // ignore dotfiles
69
- persistent: true,
70
- ignoreInitial: true // do not fire for existing files on startup
71
- });
72
-
73
- this.watcher.on("add", async (filePath) => {
74
- this.log.info(`New file detected: ${filePath}`);
75
-
76
- // Calculate relative path for URL
77
- const relativePath = path.relative(this.config.dirname, filePath).replace(/\\/g, "/");
78
- const route = this.config.route || "demo";
79
-
80
- // Assuming the web adapter serves this extension under the web server's port
81
- // and relative path is encoded.
82
- const fileUrl = `/${route}/files/${encodeURI(relativePath)}`;
83
-
84
- await this.setStateAsync("latest_file_url", { val: fileUrl, ack: true });
85
- await this.setStateAsync("latest_file_path", { val: filePath, ack: true });
86
- });
87
-
88
- this.watcher.on("error", error => this.log.error(`Watcher error: ${error}`));
89
- } catch (e) {
90
- this.log.error("Failed to start file observer: " + e);
91
- }
92
- } else {
93
- this.log.warn("Directory name not configured. File observer will not start.");
94
- }
95
- }
96
-
97
- /**
98
- * Is called when adapter shuts down - callback has to be called under any circumstances!
99
- * @param {() => void} callback
100
- */
101
- onUnload(callback) {
102
- try {
103
- if (this.watcher) {
104
- this.watcher.close();
105
- this.watcher = null;
106
- }
107
- callback();
108
- } catch (e) {
109
- callback();
110
- }
111
- }
112
-
113
- /**
114
- * Is called if a subscribed state changes
115
- * @param {string} id
116
- * @param {ioBroker.State | null | undefined} state
117
- */
118
- onStateChange(id, state) {
119
- if (state) {
120
- // The state was changed
121
- this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
122
- } else {
123
- // The state was deleted
124
- this.log.info(`state ${id} deleted`);
125
- }
126
- }
15
+ /**
16
+ * @param {Partial<utils.AdapterOptions>} [options] The adapter options.
17
+ */
18
+ constructor(options) {
19
+ super({
20
+ ...options,
21
+ name: 'staticsfilefolder',
22
+ });
23
+ this.on('ready', this.onReady.bind(this));
24
+ this.on('stateChange', this.onStateChange.bind(this));
25
+ this.on('unload', this.onUnload.bind(this));
26
+
27
+ this.watcher = null;
28
+ }
29
+
30
+ /**
31
+ * Is called when databases are connected and adapter received configuration.
32
+ */
33
+ async onReady() {
34
+ // Create state for latest file url
35
+ await this.setObjectNotExistsAsync('latest_file_url', {
36
+ type: 'state',
37
+ common: {
38
+ name: 'Latest File URL',
39
+ type: 'string',
40
+ role: 'url',
41
+ read: true,
42
+ write: false,
43
+ },
44
+ native: {},
45
+ });
46
+
47
+ // Create state for the latest file path
48
+ await this.setObjectNotExistsAsync('latest_file_path', {
49
+ type: 'state',
50
+ common: {
51
+ name: 'Latest File Path',
52
+ type: 'string',
53
+ role: 'value',
54
+ read: true,
55
+ write: false,
56
+ },
57
+ native: {},
58
+ });
59
+
60
+ if (this.config.dirname) {
61
+ this.log.info(`Starting file observer on: ${this.config.dirname}`);
62
+ try {
63
+ const chokidar = require('chokidar');
64
+ const path = require('node:path');
65
+
66
+ this.watcher = chokidar.watch(this.config.dirname, {
67
+ ignored: /(^|[/\\])\../, // ignore dotfiles
68
+ persistent: true,
69
+ ignoreInitial: true, // do not fire for existing files on startup
70
+ });
71
+
72
+ this.watcher.on('add', async filePath => {
73
+ this.log.info(`New file detected: ${filePath}`);
74
+
75
+ // Calculate relative path for URL
76
+ const relativePath = path.relative(this.config.dirname, filePath).replace(/\\/g, '/');
77
+ const route = this.config.route || 'demo';
78
+
79
+ // Assuming the web adapter serves this extension under the web server's port
80
+ // and relative path is encoded.
81
+ const fileUrl = `/${route}/files/${encodeURI(relativePath)}`;
82
+
83
+ await this.setStateAsync('latest_file_url', {
84
+ val: fileUrl,
85
+ ack: true,
86
+ });
87
+ await this.setStateAsync('latest_file_path', {
88
+ val: filePath,
89
+ ack: true,
90
+ });
91
+ });
92
+
93
+ this.watcher.on('error', error => this.log.error(`Watcher error: ${error}`));
94
+ } catch (e) {
95
+ this.log.error(`Failed to start file observer: ${e}`);
96
+ }
97
+ } else {
98
+ this.log.warn('Directory name not configured. File observer will not start.');
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Is called when adapter shuts down - callback has to be called under any circumstances!
104
+ *
105
+ * @param {() => void} callback The callback function to call after unloading.
106
+ */
107
+ onUnload(callback) {
108
+ try {
109
+ if (this.watcher) {
110
+ this.watcher.close();
111
+ this.watcher = null;
112
+ }
113
+ callback();
114
+ } catch {
115
+ callback();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Is called if a subscribed state changes
121
+ *
122
+ * @param {string} id The state ID.
123
+ * @param {ioBroker.State | null | undefined} state The state object.
124
+ */
125
+ onStateChange(id, state) {
126
+ if (state) {
127
+ // The state was changed
128
+ this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
129
+ } else {
130
+ // The state was deleted
131
+ this.log.info(`state ${id} deleted`);
132
+ }
133
+ }
127
134
  }
128
135
 
129
136
  if (require.main !== module) {
130
- // Export the constructor in compact mode
131
- /**
132
- * @param {Partial<utils.AdapterOptions>} [options={}]
133
- */
134
- module.exports = (options) => new Staticsfilefolder(options);
137
+ // Export the constructor in compact mode
138
+ /**
139
+ * @param {Partial<utils.AdapterOptions>} [options] The adapter options.
140
+ */
141
+ module.exports = options => new Staticsfilefolder(options);
135
142
  } else {
136
- // otherwise start the instance directly
137
- new Staticsfilefolder();
138
- }
143
+ // otherwise start the instance directly
144
+ new Staticsfilefolder();
145
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.staticsfilefolder",
3
- "version": "0.0.23",
3
+ "version": "0.0.26",
4
4
  "description": "Static Files Folder",
5
5
  "author": {
6
6
  "name": "gokturk413",
@@ -29,14 +29,14 @@
29
29
  "@alcalzone/release-script-plugin-iobroker": "^5.2.0",
30
30
  "@alcalzone/release-script-plugin-license": "^5.2.0",
31
31
  "@alcalzone/release-script-plugin-manual-review": "^5.2.0",
32
- "@iobroker/adapter-dev": "^1.3.0",
33
- "@iobroker/testing": "^5.2.2",
34
- "@tsconfig/node22": "^22.0.0",
32
+ "@iobroker/adapter-dev": "^1.5.0",
33
+ "@iobroker/eslint-config": "^2.3.4",
34
+ "@iobroker/testing": "^5.3.0",
35
+ "@tsconfig/node22": "^22.0.5",
35
36
  "@types/node": "^18.19.24",
36
37
  "@types/proxyquire": "^1.3.31",
37
- "eslint": "^8.57.0",
38
38
  "proxyquire": "^2.1.3",
39
- "typescript": "~5.0.4"
39
+ "typescript": "^5.5.4"
40
40
  },
41
41
  "main": "main.js",
42
42
  "files": [