Skip to content

Commit 915c3ac

Browse files
Implement conditional meta file deletion for adapter instances
Co-authored-by: GermanBluefox <4582016+GermanBluefox@users.noreply.github.com>
1 parent d946f63 commit 915c3ac

File tree

3 files changed

+94
-5
lines changed

3 files changed

+94
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
## __WORK IN PROGRESS__
88
* (@Apollon77) Allows only numbers for ts and tc fields in state when provided for setState
99
* (@GermanBluefox) Added typing for visIconSets in `io-package.json`(for vis-2 SVG icon sets)
10-
* (@copilot) Fixed cleanup of storage meta folder files when deleting adapter instances
10+
* (@copilot) Added conditional deletion of storage meta folder files when deleting adapter instances to prevent accidental removal of user data like vis projects
1111
* (@foxriver76) Added objects warn limit per instance
1212
* (@Apollon77) Allows only numbers for `ts` and `lc` fields in state when provided for setState
1313
* (@GermanBluefox) Added typing for `visIconSets` in `io-package.json`(for vis-2 SVG icon sets)

packages/cli/src/lib/setup.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ function initYargs(): ReturnType<typeof yargs> {
182182
describe: 'Remove instance custom attribute from all objects',
183183
type: 'boolean',
184184
},
185+
'with-meta': {
186+
describe: 'Also delete meta files without asking for confirmation',
187+
type: 'boolean',
188+
},
185189
})
186190
.command('update [<repositoryUrl>]', 'Update repository and list adapters', {
187191
updatable: {
@@ -1109,7 +1113,7 @@ async function processCommand(
11091113
});
11101114

11111115
console.log(`Delete instance "${adapter}.${instance}"`);
1112-
await install.deleteInstance(adapter, parseInt(instance));
1116+
await install.deleteInstance(adapter, parseInt(instance), params['with-meta']);
11131117
callback();
11141118
});
11151119
} else {

packages/cli/src/lib/setup/setupInstall.ts

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,11 +1398,67 @@ export class Install {
13981398
}
13991399

14001400
/**
1401-
* Delete files for a specific adapter instance
1401+
* Check if there are meta files that would be deleted for an instance
14021402
*
14031403
* @param adapter adapter name like hm-rpc
14041404
* @param instance instance number like 0
1405+
* @returns Promise<boolean> true if there are meta files to delete
14051406
*/
1407+
private async _hasInstanceMetaFiles(adapter: string, instance: number): Promise<boolean> {
1408+
const knownObjectIDs: string[] = [];
1409+
const metaFilesToDelete: string[] = [];
1410+
1411+
// Enumerate meta files for this instance
1412+
await this._enumerateAdapterMeta(knownObjectIDs, adapter, metaFilesToDelete, instance);
1413+
1414+
// Return true if there are meta files beyond the instance folder itself
1415+
return metaFilesToDelete.length > 0;
1416+
}
1417+
1418+
/**
1419+
* Read the adapter's io-package.json and check if deletion of meta files is allowed
1420+
*
1421+
* @param adapter adapter name like hm-rpc
1422+
* @returns Promise<boolean> true if allowDeletionOfFilesInMetaObject is set to true
1423+
*/
1424+
private async _isMetaFileDeletionAllowed(adapter: string): Promise<boolean> {
1425+
try {
1426+
const adapterDir = tools.getAdapterDir(adapter);
1427+
if (!adapterDir || !fs.existsSync(path.join(adapterDir, 'io-package.json'))) {
1428+
return false;
1429+
}
1430+
1431+
const ioPackage = await fs.readJSON(path.join(adapterDir, 'io-package.json'));
1432+
return ioPackage.common?.allowDeletionOfFilesInMetaObject === true;
1433+
} catch (err) {
1434+
// If we can't read the io-package.json, assume meta file deletion is not allowed
1435+
return false;
1436+
}
1437+
}
1438+
1439+
/**
1440+
* Ask user interactively if they want to delete meta files
1441+
*
1442+
* @returns Promise<boolean> true if user agrees to delete meta files
1443+
*/
1444+
private async _askUserToDeleteMetaFiles(): Promise<boolean> {
1445+
// Check if running in interactive TTY
1446+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
1447+
return false; // In non-interactive environment, don't delete meta files
1448+
}
1449+
1450+
const rl = (await import('node:readline')).createInterface({
1451+
input: process.stdin,
1452+
output: process.stdout,
1453+
});
1454+
1455+
return new Promise((resolve) => {
1456+
rl.question('This instance has meta files (e.g., vis projects) that will be permanently deleted. Do you want to continue? [y/N]: ', (answer) => {
1457+
rl.close();
1458+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
1459+
});
1460+
});
1461+
}
14061462
private async _deleteInstanceFiles(adapter: string, instance: number): Promise<void> {
14071463
const knownObjectIDs: string[] = [];
14081464
const metaFilesToDelete: string[] = [];
@@ -1633,8 +1689,9 @@ export class Install {
16331689
*
16341690
* @param adapter adapter name like hm-rpc
16351691
* @param instance e.g. 1, if undefined deletes all instances
1692+
* @param withMeta if true, also delete meta files without asking for confirmation
16361693
*/
1637-
async deleteInstance(adapter: string, instance?: number): Promise<void | EXIT_CODES.CANNOT_DELETE_DEPENDENCY> {
1694+
async deleteInstance(adapter: string, instance?: number, withMeta?: boolean): Promise<void | EXIT_CODES.CANNOT_DELETE_DEPENDENCY> {
16381695
const knownObjectIDs: string[] = [];
16391696
const knownStateIDs: string[] = [];
16401697

@@ -1659,7 +1716,35 @@ export class Install {
16591716

16601717
// Delete files for this specific instance (before deleting objects, since enumeration needs them)
16611718
if (instance !== undefined) {
1662-
await this._deleteInstanceFiles(adapter, instance);
1719+
// Check if there are meta files that would be deleted
1720+
const hasMetaFiles = await this._hasInstanceMetaFiles(adapter, instance);
1721+
1722+
if (hasMetaFiles) {
1723+
// Check if adapter allows deletion of meta files without confirmation
1724+
const allowedByAdapter = await this._isMetaFileDeletionAllowed(adapter);
1725+
1726+
let shouldDeleteMeta = false;
1727+
1728+
if (allowedByAdapter) {
1729+
// Adapter allows deletion, proceed without asking
1730+
shouldDeleteMeta = true;
1731+
} else if (withMeta) {
1732+
// User provided --with-meta flag
1733+
shouldDeleteMeta = true;
1734+
} else {
1735+
// Ask user interactively (will return false if not in TTY)
1736+
shouldDeleteMeta = await this._askUserToDeleteMetaFiles();
1737+
}
1738+
1739+
if (shouldDeleteMeta) {
1740+
await this._deleteInstanceFiles(adapter, instance);
1741+
} else {
1742+
console.log(`host.${hostname} Meta files for ${adapter}.${instance} were not deleted`);
1743+
}
1744+
} else {
1745+
// No meta files to worry about, proceed with standard deletion
1746+
await this._deleteInstanceFiles(adapter, instance);
1747+
}
16631748
}
16641749

16651750
await this._deleteAdapterObjects(knownObjectIDs);

0 commit comments

Comments
 (0)