@noforeignland/signalk-to-noforeignland 1.1.0-beta.3 → 1.1.0
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/CHANGELOG.md +6 -0
- package/README.md +3 -7
- package/doc/beta_install_cerbo.md +14 -3
- package/index.js +2 -11
- package/lib/PluginCleanup.js +125 -27
- package/lib/TrackSender.js +0 -38
- package/package.json +2 -3
- package/cleanup-old-plugin.js +0 -80
- package/doc/dev +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
1.1.0
|
|
2
|
+
* CHANGE: Removed postinstall script - SignalK server will use --ignore-scripts (issue #2181). Runtime PluginCleanup handles all migration.
|
|
2
3
|
* CHANGE: Refactored Project structure, see PROJECT_STRUCTURE.md
|
|
3
4
|
* CHANGE: Use phrase GNSS instead of GPS - Thanks Piotr
|
|
5
|
+
* BUGFIX: Removed unreliable DNS-based internet connectivity test (beta.11) - API retry logic is sufficient
|
|
6
|
+
* BUGFIX: Reverted to beta.3 working configuration (beta.10) - undoing all failed schema experiments from beta.4-9
|
|
7
|
+
* CHANGE: Improved cleanup error handling - only shows error after all retry attempts fail
|
|
8
|
+
* CHANGE: Added installed plugins logging for better debugging
|
|
9
|
+
* CHANGE: Platform-specific cleanup instructions (Cerbo GX vs standard)
|
|
4
10
|
|
|
5
11
|
1.0.1
|
|
6
12
|
* CHANGE: Cleanup previous installs and migrate plugin config, removes depricated old plugins.
|
package/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# Signal K To Noforeignland
|
|
2
2
|
Effortlessly log your boat's movement to **noforeignland.com**
|
|
3
3
|
|
|
4
|
-
## Important for 0.1.x users
|
|
5
|
-
Upgrade to >1.1.0 by installing this plugin from the Appstore again. Your old config will be migrated and old plugins will be automatically removed.
|
|
6
|
-
|
|
7
4
|
## Features
|
|
8
5
|
* Automatically log your position to noforeignland.com
|
|
9
6
|
* Send detailed tracks to log your entire trip and not just your final position
|
|
@@ -14,8 +11,9 @@ Upgrade to >1.1.0 by installing this plugin from the Appstore again. Your old co
|
|
|
14
11
|
* SK data paths about the plugin status for your own dashboard or Node Red coding
|
|
15
12
|
|
|
16
13
|
## Issues
|
|
17
|
-
* Server -> Plugin Config -> Signal K to Noforeignland -> Enable debug log (top right)
|
|
18
|
-
*
|
|
14
|
+
* Enable debug logging: Server -> Plugin Config -> Signal K to Noforeignland -> Enable debug log (top right)
|
|
15
|
+
* Check logs: Server -> Server Log (in SignalK web UI)
|
|
16
|
+
* Report issues on [GitHub](https://github.com/noforeignland/nfl-signalk/issues)
|
|
19
17
|
|
|
20
18
|
## Requirements
|
|
21
19
|
* An internet connection is required in order to update noforeignland.com
|
|
@@ -44,8 +42,6 @@ noforeignland.source - string - string - data source of
|
|
|
44
42
|
notifications.noforeignland.status_boolean - json object - auto created
|
|
45
43
|
```
|
|
46
44
|
|
|
47
|
-
https://github.com/noforeignland/nfl-signalk/issues
|
|
48
|
-
|
|
49
45
|
# Virctron Cerbo GX Users
|
|
50
46
|
|
|
51
47
|
## Limited storage
|
|
@@ -84,9 +84,6 @@ chown -R signalk:signalk /data/conf/signalk/*
|
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
----------------------------------------
|
|
88
|
-
|
|
89
|
-
|
|
90
87
|
# dev tree (unstable) install - NOT RECOMMENDED
|
|
91
88
|
|
|
92
89
|
1. Backup as above
|
|
@@ -111,6 +108,20 @@ chown -R signalk:signalk /data/conf/signalk/*
|
|
|
111
108
|
```
|
|
112
109
|
|
|
113
110
|
3. Restart Server & Check logs
|
|
111
|
+
```
|
|
112
|
+
svc -t /service/signalk-server
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
# Manual Fixes
|
|
116
|
+
|
|
117
|
+
## WARNING: found multiple copies of plugin with id signalk-to-noforeignland at /data/conf/signalk/node_modules/ and /data/conf/signalk/node_modules/
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
cd /data/conf/signalk && npm uninstall signalk-to-noforeignland signalk-to-nfl
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
and than
|
|
124
|
+
|
|
114
125
|
```
|
|
115
126
|
svc -t /service/signalk-server
|
|
116
127
|
```
|
package/index.js
CHANGED
|
@@ -202,17 +202,8 @@ class SignalkToNoforeignland {
|
|
|
202
202
|
if (!hasTrack) {
|
|
203
203
|
return;
|
|
204
204
|
}
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
const hasInternet = await this.trackSender.testInternet();
|
|
208
|
-
if (!hasInternet) {
|
|
209
|
-
const errorMsg = 'No internet connection detected. Unable to send tracking data to NFL. DNS lookups failed - check your internet connection.';
|
|
210
|
-
this.app.debug(errorMsg);
|
|
211
|
-
this.setPluginError(errorMsg);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Send track data
|
|
205
|
+
|
|
206
|
+
// Send track data (has built-in retry logic)
|
|
216
207
|
const success = await this.trackSender.sendTrack();
|
|
217
208
|
|
|
218
209
|
if (success) {
|
package/lib/PluginCleanup.js
CHANGED
|
@@ -13,37 +13,130 @@ class PluginCleanup {
|
|
|
13
13
|
try {
|
|
14
14
|
// Detect SignalK directory (standard or Victron Cerbo)
|
|
15
15
|
const victronPath = '/data/conf/signalk';
|
|
16
|
-
const standardPath = process.env.SIGNALK_NODE_CONFIG_DIR ||
|
|
16
|
+
const standardPath = process.env.SIGNALK_NODE_CONFIG_DIR ||
|
|
17
17
|
path.join(process.env.HOME || process.env.USERPROFILE, '.signalk');
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
const configDir = fs.existsSync(victronPath) ? victronPath : standardPath;
|
|
20
20
|
this.app.debug(`Using SignalK directory: ${configDir}`);
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
const configPath = path.join(configDir, 'plugin-config-data');
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
// 1. Config Migration - only from signalk-to-noforeignland
|
|
25
25
|
await this.migrateConfig(configPath);
|
|
26
|
-
|
|
27
|
-
// 2.
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
|
|
30
33
|
} catch (err) {
|
|
31
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(', ')}`);
|
|
32
70
|
}
|
|
33
71
|
}
|
|
34
72
|
|
|
35
73
|
/**
|
|
36
|
-
* Migrate config from old plugin to new
|
|
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.
|
|
37
84
|
*/
|
|
38
85
|
async migrateConfig(configPath) {
|
|
39
86
|
const oldConfigFile = path.join(configPath, 'signalk-to-noforeignland.json');
|
|
40
87
|
const newConfigFile = path.join(configPath, '@noforeignland-signalk-to-noforeignland.json');
|
|
41
|
-
|
|
88
|
+
|
|
89
|
+
// Only migrate if old config exists and new config doesn't
|
|
42
90
|
if (fs.existsSync(oldConfigFile) && !fs.existsSync(newConfigFile)) {
|
|
43
|
-
this.app.debug('Migrating configuration from old plugin
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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');
|
|
47
140
|
}
|
|
48
141
|
}
|
|
49
142
|
|
|
@@ -87,16 +180,11 @@ class PluginCleanup {
|
|
|
87
180
|
this.app.debug('All old plugins removed successfully');
|
|
88
181
|
return 'all_removed';
|
|
89
182
|
}
|
|
90
|
-
|
|
91
|
-
//
|
|
183
|
+
|
|
184
|
+
// Don't show error immediately - log that we're retrying
|
|
92
185
|
const stillPresentNames = stillPresent.map(p => `"${p.name}"`).join(' and ');
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
this.app.setPluginError(
|
|
96
|
-
`Old plugin(s) ${stillPresentNames} still installed. ` +
|
|
97
|
-
`Will retry removal, or uninstall manually: cd ${configDir} && npm uninstall ${stillPresentCmd}`
|
|
98
|
-
);
|
|
99
|
-
|
|
186
|
+
this.app.debug(`Old plugin(s) ${stillPresentNames} still present, will retry removal...`);
|
|
187
|
+
|
|
100
188
|
// Delayed removal attempts (multiple tries with increasing delays)
|
|
101
189
|
return new Promise((resolve) => {
|
|
102
190
|
const delays = [5000, 15000, 30000]; // 5s, 15s, 30s
|
|
@@ -104,12 +192,22 @@ class PluginCleanup {
|
|
|
104
192
|
|
|
105
193
|
const attemptRemoval = async () => {
|
|
106
194
|
if (attemptIndex >= delays.length) {
|
|
107
|
-
// Final attempt failed
|
|
195
|
+
// Final attempt failed - NOW show error only if plugins still exist
|
|
108
196
|
const remaining = stillPresent.filter(p => fs.existsSync(p.dir));
|
|
109
197
|
if (remaining.length > 0) {
|
|
110
|
-
const remainingNames = remaining.map(p => p.name).join(' ');
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
);
|
|
113
211
|
resolve('partial_removal');
|
|
114
212
|
} else {
|
|
115
213
|
this.app.debug('All old plugins eventually removed');
|
package/lib/TrackSender.js
CHANGED
|
@@ -36,44 +36,6 @@ class TrackSender {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Test internet connectivity
|
|
41
|
-
*/
|
|
42
|
-
async testInternet() {
|
|
43
|
-
const dns = require('dns').promises;
|
|
44
|
-
|
|
45
|
-
this.app.debug('testing internet connection');
|
|
46
|
-
|
|
47
|
-
const timeoutMs = this.options.internetTestTimeout || 2000;
|
|
48
|
-
this.app.debug(`Using internet test timeout: ${timeoutMs}ms`);
|
|
49
|
-
|
|
50
|
-
const dnsServers = [
|
|
51
|
-
{ name: 'Google DNS', ip: '8.8.8.8' },
|
|
52
|
-
{ name: 'Cloudflare DNS', ip: '1.1.1.1' }
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
for (const server of dnsServers) {
|
|
56
|
-
try {
|
|
57
|
-
const startTime = Date.now();
|
|
58
|
-
const result = await Promise.race([
|
|
59
|
-
dns.reverse(server.ip),
|
|
60
|
-
new Promise((_, reject) =>
|
|
61
|
-
setTimeout(() => reject(new Error('DNS timeout')), timeoutMs)
|
|
62
|
-
)
|
|
63
|
-
]);
|
|
64
|
-
const elapsed = Date.now() - startTime;
|
|
65
|
-
|
|
66
|
-
this.app.debug(`internet connection = true, ${server.name} (${server.ip}) is reachable (took ${elapsed}ms)`);
|
|
67
|
-
return true;
|
|
68
|
-
} catch (err) {
|
|
69
|
-
this.app.debug(`${server.name} (${server.ip}) not reachable:`, err.message);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this.app.debug(`internet connection = false, no public DNS servers reachable (timeout was ${timeoutMs}ms)`);
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
39
|
/**
|
|
78
40
|
* Check if track file exists and has content
|
|
79
41
|
*/
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"signalk": {
|
|
4
4
|
"id": "@noforeignland/signalk-to-noforeignland"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.1.0
|
|
6
|
+
"version": "1.1.0",
|
|
7
7
|
"description": "SignalK track logger to noforeignland.com",
|
|
8
8
|
"main": "index.js",
|
|
9
9
|
"keywords": [
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"node": ">=18.0.0"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"test": "echo \"No tests specified\" && exit 0"
|
|
28
|
-
"postinstall": "node cleanup-old-plugin.js"
|
|
27
|
+
"test": "echo \"No tests specified\" && exit 0"
|
|
29
28
|
},
|
|
30
29
|
"dependencies": {
|
|
31
30
|
"cron": "^2.1.0",
|
package/cleanup-old-plugin.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cleanup script for old plugin versions
|
|
5
|
-
* This runs as a standalone script during npm postinstall
|
|
6
|
-
* NOT a class - just a simple executable script
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// Detect SignalK directory (standard ~/.signalk or Victron Cerbo /data/conf/signalk)
|
|
13
|
-
function getSignalKDir() {
|
|
14
|
-
const victronPath = '/data/conf/signalk';
|
|
15
|
-
const homePath = path.join(process.env.HOME || '', '.signalk');
|
|
16
|
-
|
|
17
|
-
// Check Victron path first
|
|
18
|
-
if (fs.existsSync(victronPath)) {
|
|
19
|
-
return victronPath;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return homePath;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const signalkDir = getSignalKDir();
|
|
26
|
-
console.log(`Using SignalK directory: ${signalkDir}`);
|
|
27
|
-
|
|
28
|
-
// 1. Migration (only for signalk-to-noforeignland config)
|
|
29
|
-
try {
|
|
30
|
-
const configPath = path.join(signalkDir, 'plugin-config-data');
|
|
31
|
-
const oldConfig = path.join(configPath, 'signalk-to-noforeignland.json');
|
|
32
|
-
const newConfig = path.join(configPath, '@noforeignland-signalk-to-noforeignland.json');
|
|
33
|
-
|
|
34
|
-
if (fs.existsSync(oldConfig) && !fs.existsSync(newConfig)) {
|
|
35
|
-
fs.copyFileSync(oldConfig, newConfig);
|
|
36
|
-
fs.copyFileSync(oldConfig, `${oldConfig}.backup`);
|
|
37
|
-
console.log('✓ Configuration migrated');
|
|
38
|
-
}
|
|
39
|
-
} catch (e) {
|
|
40
|
-
console.warn('Could not migrate config:', e.message);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 2. Uninstall old plugins (with delay so npm can finish)
|
|
44
|
-
setTimeout(() => {
|
|
45
|
-
try {
|
|
46
|
-
const oldPlugins = [
|
|
47
|
-
{ dir: path.join(signalkDir, 'node_modules', 'signalk-to-noforeignland'), name: 'signalk-to-noforeignland' },
|
|
48
|
-
{ dir: path.join(signalkDir, 'node_modules', 'signalk-to-nfl'), name: 'signalk-to-nfl' }
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
let removedAny = false;
|
|
52
|
-
const failedPlugins = [];
|
|
53
|
-
|
|
54
|
-
for (const plugin of oldPlugins) {
|
|
55
|
-
if (fs.existsSync(plugin.dir)) {
|
|
56
|
-
try {
|
|
57
|
-
console.log(`Removing old plugin "${plugin.name}"...`);
|
|
58
|
-
// Direct deletion is safer than npm uninstall during installation
|
|
59
|
-
fs.rmSync(plugin.dir, { recursive: true, force: true });
|
|
60
|
-
console.log(`✓ Old plugin "${plugin.name}" removed`);
|
|
61
|
-
removedAny = true;
|
|
62
|
-
} catch (e) {
|
|
63
|
-
console.warn(`Could not remove "${plugin.name}":`, e.message);
|
|
64
|
-
failedPlugins.push(plugin.name);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (failedPlugins.length > 0) {
|
|
70
|
-
const uninstallCmd = failedPlugins.join(' ');
|
|
71
|
-
console.warn(`Please run manually: npm uninstall ${uninstallCmd}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!removedAny && failedPlugins.length === 0) {
|
|
75
|
-
console.log('No old plugins found - already clean!');
|
|
76
|
-
}
|
|
77
|
-
} catch (e) {
|
|
78
|
-
console.warn('Error during cleanup:', e.message);
|
|
79
|
-
}
|
|
80
|
-
}, 2000); // Wait 2 seconds for npm to finish
|
package/doc/dev
DELETED
|
File without changes
|