@noforeignland/signalk-to-noforeignland 1.1.0 → 1.2.0-beta.2
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 +22 -3
- package/package.json +38 -6
- package/.github/workflows/publish.yml +0 -40
- package/CHANGELOG.md +0 -122
- package/PROJECT_STRUCTURE.md +0 -357
- package/doc/beta_install_cerbo.md +0 -127
- package/doc/beta_install_rpi.md +0 -64
- package/index.js +0 -295
- package/lib/ConfigManager.js +0 -216
- package/lib/DataPathEmitter.js +0 -93
- package/lib/DirectoryUtils.js +0 -38
- package/lib/HealthMonitor.js +0 -91
- package/lib/PluginCleanup.js +0 -259
- package/lib/TrackLogger.js +0 -306
- package/lib/TrackMigration.js +0 -110
- package/lib/TrackSender.js +0 -219
package/lib/DataPathEmitter.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
class DataPathEmitter {
|
|
2
|
-
constructor(app, pluginId) {
|
|
3
|
-
this.app = app;
|
|
4
|
-
this.pluginId = pluginId;
|
|
5
|
-
this.currentError = null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Emit SignalK delta for a data path
|
|
10
|
-
*/
|
|
11
|
-
emitDelta(path, value) {
|
|
12
|
-
try {
|
|
13
|
-
const delta = {
|
|
14
|
-
context: 'vessels.self',
|
|
15
|
-
updates: [{
|
|
16
|
-
timestamp: new Date().toISOString(),
|
|
17
|
-
values: [{
|
|
18
|
-
path: path,
|
|
19
|
-
value: value
|
|
20
|
-
}]
|
|
21
|
-
}]
|
|
22
|
-
};
|
|
23
|
-
this.app.handleMessage(this.pluginId, delta);
|
|
24
|
-
} catch (err) {
|
|
25
|
-
this.app.debug(`Failed to emit delta for ${path}:`, err.message);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Update status paths with current state
|
|
31
|
-
*/
|
|
32
|
-
updateStatusPaths(options, lastPosition, lastSuccessfulTransfer, autoSelectedSource) {
|
|
33
|
-
const hasError = this.currentError !== null;
|
|
34
|
-
|
|
35
|
-
if (!hasError) {
|
|
36
|
-
const activeSource = options.filterSource || autoSelectedSource || '';
|
|
37
|
-
const saveTime = lastPosition
|
|
38
|
-
? new Date(lastPosition.currentTime).toLocaleTimeString()
|
|
39
|
-
: 'None since start';
|
|
40
|
-
const transferTime = lastSuccessfulTransfer
|
|
41
|
-
? lastSuccessfulTransfer.toLocaleTimeString()
|
|
42
|
-
: 'None since start';
|
|
43
|
-
const shortStatus = `Save: ${saveTime} | Transfer: ${transferTime}`;
|
|
44
|
-
|
|
45
|
-
this.emitDelta('noforeignland.status', shortStatus);
|
|
46
|
-
this.emitDelta('noforeignland.source', activeSource);
|
|
47
|
-
} else {
|
|
48
|
-
this.emitDelta('noforeignland.status', `ERROR: ${this.currentError}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
this.emitDelta('noforeignland.status_boolean', hasError ? 1 : 0);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Emit savepoint deltas
|
|
56
|
-
*/
|
|
57
|
-
emitSavepoint() {
|
|
58
|
-
const now = new Date();
|
|
59
|
-
this.emitDelta('noforeignland.savepoint', now.toISOString());
|
|
60
|
-
this.emitDelta('noforeignland.savepoint_local', now.toLocaleString());
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Emit API transfer deltas
|
|
65
|
-
*/
|
|
66
|
-
emitApiTransfer(transferTime) {
|
|
67
|
-
this.emitDelta('noforeignland.sent_to_api', transferTime.toISOString());
|
|
68
|
-
this.emitDelta('noforeignland.sent_to_api_local', transferTime.toLocaleString());
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Set error state
|
|
73
|
-
*/
|
|
74
|
-
setError(error) {
|
|
75
|
-
this.currentError = error;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Clear error state
|
|
80
|
-
*/
|
|
81
|
-
clearError() {
|
|
82
|
-
this.currentError = null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get current error
|
|
87
|
-
*/
|
|
88
|
-
getError() {
|
|
89
|
-
return this.currentError;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
module.exports = DataPathEmitter;
|
package/lib/DirectoryUtils.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
|
|
3
|
-
class DirectoryUtils {
|
|
4
|
-
/**
|
|
5
|
-
* Create directory with proper error handling
|
|
6
|
-
*/
|
|
7
|
-
static createDir(dir, app) {
|
|
8
|
-
if (fs.existsSync(dir)) {
|
|
9
|
-
try {
|
|
10
|
-
fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK);
|
|
11
|
-
return true;
|
|
12
|
-
} catch (error) {
|
|
13
|
-
app.debug('[createDir]', error.message);
|
|
14
|
-
throw new Error(`No rights to directory ${dir}`);
|
|
15
|
-
}
|
|
16
|
-
} else {
|
|
17
|
-
try {
|
|
18
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
19
|
-
return true;
|
|
20
|
-
} catch (error) {
|
|
21
|
-
switch (error.code) {
|
|
22
|
-
case 'EACCES':
|
|
23
|
-
case 'EPERM':
|
|
24
|
-
app.debug(`Failed to create ${dir} by Permission denied`);
|
|
25
|
-
throw new Error(`Failed to create ${dir} by Permission denied`);
|
|
26
|
-
case 'ETIMEDOUT':
|
|
27
|
-
app.debug(`Failed to create ${dir} by Operation timed out`);
|
|
28
|
-
throw new Error(`Failed to create ${dir} by Operation timed out`);
|
|
29
|
-
default:
|
|
30
|
-
app.debug(`Error creating directory ${dir}: ${error.message}`);
|
|
31
|
-
throw new Error(`Error creating directory ${dir}: ${error.message}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = DirectoryUtils;
|
package/lib/HealthMonitor.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
class HealthMonitor {
|
|
2
|
-
constructor(app, options) {
|
|
3
|
-
this.app = app;
|
|
4
|
-
this.options = options;
|
|
5
|
-
this.checkInterval = null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Start position health monitoring
|
|
10
|
-
*/
|
|
11
|
-
start(getLastPositionReceived, getAutoSelectedSource, onError, onHealthy) {
|
|
12
|
-
// Periodic health check every 5 minutes
|
|
13
|
-
this.checkInterval = setInterval(() => {
|
|
14
|
-
this.performHealthCheck(
|
|
15
|
-
getLastPositionReceived(),
|
|
16
|
-
getAutoSelectedSource(),
|
|
17
|
-
onError,
|
|
18
|
-
onHealthy
|
|
19
|
-
);
|
|
20
|
-
}, 5 * 60 * 1000);
|
|
21
|
-
|
|
22
|
-
// Initial check after 2 minutes of startup
|
|
23
|
-
setTimeout(() => {
|
|
24
|
-
this.performInitialCheck(
|
|
25
|
-
getLastPositionReceived(),
|
|
26
|
-
getAutoSelectedSource(),
|
|
27
|
-
onError
|
|
28
|
-
);
|
|
29
|
-
}, 2 * 60 * 1000);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Perform periodic health check
|
|
34
|
-
*/
|
|
35
|
-
performHealthCheck(lastPositionReceived, autoSelectedSource, onError, onHealthy) {
|
|
36
|
-
const now = new Date().getTime();
|
|
37
|
-
const timeSinceLastPosition = lastPositionReceived
|
|
38
|
-
? (now - lastPositionReceived) / 1000
|
|
39
|
-
: null;
|
|
40
|
-
|
|
41
|
-
const activeSource = this.options.filterSource || autoSelectedSource || 'any';
|
|
42
|
-
const filterMsg = activeSource !== 'any' ? ` from source '${activeSource}'` : '';
|
|
43
|
-
|
|
44
|
-
if (!lastPositionReceived) {
|
|
45
|
-
const errorMsg = this.options.filterSource
|
|
46
|
-
? `No GNSS position data received from filtered source '${this.options.filterSource}'. Check Expert Settings > Position source device, or leave empty to use any GNSS source.`
|
|
47
|
-
: 'No GNSS position data received. Check that your GNSS is connected and SignalK is receiving navigation.position data.';
|
|
48
|
-
|
|
49
|
-
onError(errorMsg);
|
|
50
|
-
this.app.debug('Position health check: No position data ever received' + filterMsg);
|
|
51
|
-
} else if (timeSinceLastPosition > 300) {
|
|
52
|
-
const errorMsg = this.options.filterSource
|
|
53
|
-
? `No GNSS position data${filterMsg} for ${Math.floor(timeSinceLastPosition / 60)} minutes. Check that source '${this.options.filterSource}' is active, or change/clear Position source device in Expert Settings.`
|
|
54
|
-
: `No GNSS position data${filterMsg} for ${Math.floor(timeSinceLastPosition / 60)} minutes. Check your GNSS connection.`;
|
|
55
|
-
|
|
56
|
-
onError(errorMsg);
|
|
57
|
-
this.app.debug(`Position health check: No position for ${timeSinceLastPosition.toFixed(0)} seconds` + filterMsg);
|
|
58
|
-
} else {
|
|
59
|
-
this.app.debug(`Position health check: OK (last position ${timeSinceLastPosition.toFixed(0)} seconds ago${filterMsg})`);
|
|
60
|
-
onHealthy();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Perform initial health check after startup
|
|
66
|
-
*/
|
|
67
|
-
performInitialCheck(lastPositionReceived, autoSelectedSource, onError) {
|
|
68
|
-
if (!lastPositionReceived) {
|
|
69
|
-
const activeSource = this.options.filterSource || autoSelectedSource || 'any';
|
|
70
|
-
const errorMsg = this.options.filterSource
|
|
71
|
-
? `No GNSS position data received after 2 minutes from filtered source '${this.options.filterSource}'. Check Expert Settings > Position source device. You may need to leave it empty to use any available GNSS source.`
|
|
72
|
-
: 'No GNSS position data received after 2 minutes. Check that your GNSS is connected and SignalK is receiving navigation.position data.';
|
|
73
|
-
|
|
74
|
-
onError(errorMsg);
|
|
75
|
-
this.app.debug('Initial position check: No position data received' +
|
|
76
|
-
(activeSource !== 'any' ? ` from source '${activeSource}'` : ''));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Stop health monitoring
|
|
82
|
-
*/
|
|
83
|
-
stop() {
|
|
84
|
-
if (this.checkInterval) {
|
|
85
|
-
clearInterval(this.checkInterval);
|
|
86
|
-
this.checkInterval = null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
module.exports = HealthMonitor;
|
package/lib/PluginCleanup.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
class PluginCleanup {
|
|
5
|
-
constructor(app) {
|
|
6
|
-
this.app = app;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Cleanup old plugin versions (signalk-to-noforeignland and signalk-to-nfl)
|
|
11
|
-
*/
|
|
12
|
-
async cleanup() {
|
|
13
|
-
try {
|
|
14
|
-
// Detect SignalK directory (standard or Victron Cerbo)
|
|
15
|
-
const victronPath = '/data/conf/signalk';
|
|
16
|
-
const standardPath = process.env.SIGNALK_NODE_CONFIG_DIR ||
|
|
17
|
-
path.join(process.env.HOME || process.env.USERPROFILE, '.signalk');
|
|
18
|
-
|
|
19
|
-
const configDir = fs.existsSync(victronPath) ? victronPath : standardPath;
|
|
20
|
-
this.app.debug(`Using SignalK directory: ${configDir}`);
|
|
21
|
-
|
|
22
|
-
const configPath = path.join(configDir, 'plugin-config-data');
|
|
23
|
-
|
|
24
|
-
// 1. Config Migration - only from signalk-to-noforeignland
|
|
25
|
-
await this.migrateConfig(configPath);
|
|
26
|
-
|
|
27
|
-
// 2. Verify what plugins are actually present
|
|
28
|
-
this.logInstalledPlugins(configDir);
|
|
29
|
-
|
|
30
|
-
// 3. Check and remove old plugins
|
|
31
|
-
return await this.removeOldPlugins(configDir);
|
|
32
|
-
|
|
33
|
-
} catch (err) {
|
|
34
|
-
this.app.debug('Error during old plugin cleanup:', err.message);
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Log which SignalK NFL plugins are currently installed (for debugging)
|
|
41
|
-
*/
|
|
42
|
-
logInstalledPlugins(configDir) {
|
|
43
|
-
const nodeModulesDir = path.join(configDir, 'node_modules');
|
|
44
|
-
const pluginsToCheck = [
|
|
45
|
-
'@noforeignland/signalk-to-noforeignland',
|
|
46
|
-
'signalk-to-noforeignland',
|
|
47
|
-
'signalk-to-nfl'
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
const found = [];
|
|
51
|
-
for (const pluginName of pluginsToCheck) {
|
|
52
|
-
const pluginPath = path.join(nodeModulesDir, pluginName);
|
|
53
|
-
if (fs.existsSync(pluginPath)) {
|
|
54
|
-
const packageJsonPath = path.join(pluginPath, 'package.json');
|
|
55
|
-
let version = 'unknown';
|
|
56
|
-
try {
|
|
57
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
58
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
59
|
-
version = packageJson.version;
|
|
60
|
-
}
|
|
61
|
-
} catch (e) {
|
|
62
|
-
// Ignore version read errors
|
|
63
|
-
}
|
|
64
|
-
found.push(`${pluginName}@${version}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (found.length > 0) {
|
|
69
|
-
this.app.debug(`Installed NFL plugins: ${found.join(', ')}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Migrate config from old plugin ID to new plugin ID
|
|
75
|
-
*
|
|
76
|
-
* Handles config migration from:
|
|
77
|
-
* 1. Old unscoped plugin "signalk-to-noforeignland" (v0.1.x)
|
|
78
|
-
* 2. Beta versions with wrong plugin ID (v1.1.0-beta.1/2/3)
|
|
79
|
-
*
|
|
80
|
-
* Both used config filename: "signalk-to-noforeignland.json"
|
|
81
|
-
* New version uses: "@noforeignland-signalk-to-noforeignland.json"
|
|
82
|
-
*
|
|
83
|
-
* Since both sources use the same filename, we simply copy if it exists.
|
|
84
|
-
*/
|
|
85
|
-
async migrateConfig(configPath) {
|
|
86
|
-
const oldConfigFile = path.join(configPath, 'signalk-to-noforeignland.json');
|
|
87
|
-
const newConfigFile = path.join(configPath, '@noforeignland-signalk-to-noforeignland.json');
|
|
88
|
-
|
|
89
|
-
// Only migrate if old config exists and new config doesn't
|
|
90
|
-
if (fs.existsSync(oldConfigFile) && !fs.existsSync(newConfigFile)) {
|
|
91
|
-
this.app.debug('Migrating configuration from old plugin to new scoped plugin...');
|
|
92
|
-
this.app.debug(` Source: ${oldConfigFile}`);
|
|
93
|
-
this.app.debug(` Target: ${newConfigFile}`);
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
// Copy to new location
|
|
97
|
-
fs.copyFileSync(oldConfigFile, newConfigFile);
|
|
98
|
-
|
|
99
|
-
// Create backup of old config
|
|
100
|
-
const backupFile = `${oldConfigFile}.backup-${Date.now()}`;
|
|
101
|
-
fs.copyFileSync(oldConfigFile, backupFile);
|
|
102
|
-
|
|
103
|
-
this.app.debug('✓ Configuration successfully migrated');
|
|
104
|
-
this.app.debug(` Backup saved: ${backupFile}`);
|
|
105
|
-
} catch (err) {
|
|
106
|
-
this.app.debug(`⨯ Config migration failed: ${err.message}`);
|
|
107
|
-
this.app.debug(' You may need to reconfigure the plugin manually');
|
|
108
|
-
}
|
|
109
|
-
} else if (fs.existsSync(newConfigFile)) {
|
|
110
|
-
this.app.debug('Configuration already in new location');
|
|
111
|
-
|
|
112
|
-
// Check if config has incorrect "configuration" wrapper and fix it
|
|
113
|
-
try {
|
|
114
|
-
const configData = JSON.parse(fs.readFileSync(newConfigFile, 'utf8'));
|
|
115
|
-
if (configData.configuration && typeof configData.configuration === 'object') {
|
|
116
|
-
this.app.debug('Detected nested "configuration" wrapper, unwrapping...');
|
|
117
|
-
|
|
118
|
-
// Unwrap: move properties from configuration object to root
|
|
119
|
-
const unwrapped = {
|
|
120
|
-
...configData.configuration,
|
|
121
|
-
enabled: configData.enabled,
|
|
122
|
-
enableLogging: configData.enableLogging,
|
|
123
|
-
enableDebug: configData.enableDebug
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Backup before fixing
|
|
127
|
-
const fixBackupFile = `${newConfigFile}.backup-unwrap-${Date.now()}`;
|
|
128
|
-
fs.copyFileSync(newConfigFile, fixBackupFile);
|
|
129
|
-
|
|
130
|
-
// Write fixed config
|
|
131
|
-
fs.writeFileSync(newConfigFile, JSON.stringify(unwrapped, null, 2));
|
|
132
|
-
this.app.debug('✓ Configuration unwrapped successfully');
|
|
133
|
-
this.app.debug(` Backup: ${fixBackupFile}`);
|
|
134
|
-
}
|
|
135
|
-
} catch (err) {
|
|
136
|
-
this.app.debug(`⨯ Could not check/fix config structure: ${err.message}`);
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
this.app.debug('No old configuration found, first-time setup');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Remove old plugin directories - with immediate and delayed attempts
|
|
145
|
-
*/
|
|
146
|
-
async removeOldPlugins(configDir) {
|
|
147
|
-
const oldPlugins = [
|
|
148
|
-
{ dir: path.join(configDir, 'node_modules', 'signalk-to-noforeignland'), name: 'signalk-to-noforeignland' },
|
|
149
|
-
{ dir: path.join(configDir, 'node_modules', 'signalk-to-nfl'), name: 'signalk-to-nfl' }
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
const foundOldPlugins = oldPlugins.filter(plugin => fs.existsSync(plugin.dir));
|
|
153
|
-
|
|
154
|
-
if (foundOldPlugins.length === 0) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const pluginNames = foundOldPlugins.map(p => `"${p.name}"`).join(' and ');
|
|
159
|
-
const uninstallCmd = foundOldPlugins.map(p => p.name).join(' ');
|
|
160
|
-
|
|
161
|
-
this.app.debug(`Old plugin(s) detected: ${pluginNames}`);
|
|
162
|
-
|
|
163
|
-
// Immediate removal attempt
|
|
164
|
-
let anyRemovedNow = false;
|
|
165
|
-
const stillPresent = [];
|
|
166
|
-
|
|
167
|
-
for (const plugin of foundOldPlugins) {
|
|
168
|
-
try {
|
|
169
|
-
this.app.debug(`Attempting immediate removal of: ${plugin.name}...`);
|
|
170
|
-
await fs.remove(plugin.dir);
|
|
171
|
-
this.app.debug(`✓ Old plugin "${plugin.name}" removed immediately`);
|
|
172
|
-
anyRemovedNow = true;
|
|
173
|
-
} catch (err) {
|
|
174
|
-
this.app.debug(`Could not remove "${plugin.name}" immediately:`, err.message);
|
|
175
|
-
stillPresent.push(plugin);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (stillPresent.length === 0) {
|
|
180
|
-
this.app.debug('All old plugins removed successfully');
|
|
181
|
-
return 'all_removed';
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Don't show error immediately - log that we're retrying
|
|
185
|
-
const stillPresentNames = stillPresent.map(p => `"${p.name}"`).join(' and ');
|
|
186
|
-
this.app.debug(`Old plugin(s) ${stillPresentNames} still present, will retry removal...`);
|
|
187
|
-
|
|
188
|
-
// Delayed removal attempts (multiple tries with increasing delays)
|
|
189
|
-
return new Promise((resolve) => {
|
|
190
|
-
const delays = [5000, 15000, 30000]; // 5s, 15s, 30s
|
|
191
|
-
let attemptIndex = 0;
|
|
192
|
-
|
|
193
|
-
const attemptRemoval = async () => {
|
|
194
|
-
if (attemptIndex >= delays.length) {
|
|
195
|
-
// Final attempt failed - NOW show error only if plugins still exist
|
|
196
|
-
const remaining = stillPresent.filter(p => fs.existsSync(p.dir));
|
|
197
|
-
if (remaining.length > 0) {
|
|
198
|
-
const remainingNames = remaining.map(p => `"${p.name}"`).join(' and ');
|
|
199
|
-
const remainingCmd = remaining.map(p => p.name).join(' ');
|
|
200
|
-
|
|
201
|
-
this.app.debug(`Could not remove old plugins after ${delays.length} attempts: ${remainingNames}`);
|
|
202
|
-
|
|
203
|
-
// Show error with platform-specific commands
|
|
204
|
-
const isVictronCerbo = configDir === '/data/conf/signalk';
|
|
205
|
-
const cmdPrefix = isVictronCerbo ? 'On Victron Cerbo GX, ' : '';
|
|
206
|
-
|
|
207
|
-
this.app.setPluginError(
|
|
208
|
-
`${cmdPrefix}Old plugin(s) ${remainingNames} detected. ` +
|
|
209
|
-
`Manual removal required: cd ${configDir} && npm uninstall ${remainingCmd}`
|
|
210
|
-
);
|
|
211
|
-
resolve('partial_removal');
|
|
212
|
-
} else {
|
|
213
|
-
this.app.debug('All old plugins eventually removed');
|
|
214
|
-
resolve('all_removed');
|
|
215
|
-
}
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const delay = delays[attemptIndex];
|
|
220
|
-
attemptIndex++;
|
|
221
|
-
|
|
222
|
-
setTimeout(async () => {
|
|
223
|
-
this.app.debug(`Delayed removal attempt ${attemptIndex}/${delays.length}...`);
|
|
224
|
-
|
|
225
|
-
const remaining = [];
|
|
226
|
-
for (const plugin of stillPresent) {
|
|
227
|
-
if (!fs.existsSync(plugin.dir)) {
|
|
228
|
-
this.app.debug(`Plugin "${plugin.name}" already removed`);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
await fs.remove(plugin.dir);
|
|
234
|
-
this.app.debug(`✓ Old plugin "${plugin.name}" removed on attempt ${attemptIndex}`);
|
|
235
|
-
} catch (err) {
|
|
236
|
-
this.app.debug(`Still cannot remove "${plugin.name}":`, err.message);
|
|
237
|
-
remaining.push(plugin);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (remaining.length === 0) {
|
|
242
|
-
this.app.debug('All old plugins successfully removed');
|
|
243
|
-
// Clear the error
|
|
244
|
-
this.app.setPluginStatus('Started (old plugins cleaned up)');
|
|
245
|
-
resolve('all_removed');
|
|
246
|
-
} else {
|
|
247
|
-
stillPresent.length = 0;
|
|
248
|
-
stillPresent.push(...remaining);
|
|
249
|
-
attemptRemoval(); // Next attempt
|
|
250
|
-
}
|
|
251
|
-
}, delay);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
attemptRemoval();
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
module.exports = PluginCleanup;
|