Skip to content

Commit 66ce514

Browse files
Merge pull request #4581 from NativeScript/vladimirov/improve-cleanup
feat: allow executing JS funcitons on cleanup
2 parents 646f7a7 + 3e586f2 commit 66ce514

File tree

6 files changed

+183
-20
lines changed

6 files changed

+183
-20
lines changed

lib/definitions/cleanup-service.d.ts

+16
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,20 @@ interface ICleanupService extends IShouldDispose, IDisposable {
4040
* @returns {Promise<void>}
4141
*/
4242
removeCleanupDeleteAction(filePath: string): Promise<void>;
43+
44+
/**
45+
* Adds JS file to be required and executed during cleanup.
46+
* NOTE: The JS file will be required in a new child process, so you can pass timeout for the execution.
47+
* In the child process you can use all injected dependencies of CLI.
48+
* @param {IJSCommand} jsCommand Information about the JS file to be required and the data that should be passed to it.
49+
* @returns {Promise<void>}
50+
*/
51+
addCleanupJS(jsCommand: IJSCommand): Promise<void>;
52+
53+
/**
54+
* Removes JS file to be required and executed during cleanup.
55+
* @param {IJSCommand} filePath jsCommand Information about the JS file to be required and the data that should not be passed to it.
56+
* @returns {Promise<void>}
57+
*/
58+
removeCleanupJS(jsCommand: IJSCommand): Promise<void>;
4359
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env node
2+
3+
// NOTE: This file is used to call JS functions when cleaning resources used by CLI, after the CLI is killed.
4+
// The instances here are not shared with the ones in main CLI process.
5+
import * as fs from "fs";
6+
import * as uuid from "uuid";
7+
import { FileLogService } from "./file-log-service";
8+
9+
const pathToBootstrap = process.argv[2];
10+
if (!pathToBootstrap || !fs.existsSync(pathToBootstrap)) {
11+
throw new Error("Invalid path to bootstrap.");
12+
}
13+
14+
// After requiring the bootstrap we can use $injector
15+
require(pathToBootstrap);
16+
17+
const logFile = process.argv[3];
18+
const jsFilePath = process.argv[4];
19+
20+
const fileLogService = $injector.resolve<IFileLogService>(FileLogService, { logFile });
21+
const uniqueId = uuid.v4();
22+
fileLogService.logData({ message: `Initializing Cleanup process for path: ${jsFilePath} Unique id: ${uniqueId}` });
23+
24+
if (!fs.existsSync(jsFilePath)) {
25+
throw new Error(`Unable to find file ${jsFilePath}. Ensure it exists.`);
26+
}
27+
28+
let data: any;
29+
try {
30+
data = process.argv[5] && JSON.parse(process.argv[5]);
31+
} catch (err) {
32+
throw new Error(`Unable to parse data from argv ${process.argv[5]}.`);
33+
}
34+
35+
const logMessage = (msg: string, type?: FileLogMessageType): void => {
36+
fileLogService.logData({ message: `[${uniqueId}] ${msg}`, type });
37+
};
38+
39+
/* tslint:disable:no-floating-promises */
40+
(async () => {
41+
try {
42+
logMessage(`Requiring file ${jsFilePath}`);
43+
44+
const func = require(jsFilePath);
45+
if (func && typeof func === "function") {
46+
try {
47+
logMessage(`Passing data: ${JSON.stringify(data)} to the default function exported by currently required file ${jsFilePath}`);
48+
await func(data);
49+
logMessage(`Finished execution with data: ${JSON.stringify(data)} to the default function exported by currently required file ${jsFilePath}`);
50+
} catch (err) {
51+
logMessage(`Unable to execute action of file ${jsFilePath} when passed data is ${JSON.stringify(data)}. Error is: ${err}.`, FileLogMessageType.Error);
52+
}
53+
}
54+
} catch (err) {
55+
logMessage(`Unable to require file: ${jsFilePath}. Error is: ${err}.`, FileLogMessageType.Error);
56+
}
57+
})();
58+
/* tslint:enable:no-floating-promises */
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
interface ISpawnCommandInfo {
1+
interface ITimeout {
2+
/**
3+
* Timeout to execute the action.
4+
*/
5+
timeout?: number;
6+
}
7+
8+
interface IFilePath {
9+
/**
10+
* Path to file/directory to be deleted or required
11+
*/
12+
filePath: string;
13+
}
14+
15+
interface ISpawnCommandInfo extends ITimeout {
216
/**
317
* Executable to be started.
418
*/
@@ -8,11 +22,6 @@ interface ISpawnCommandInfo {
822
* Arguments that will be passed to the child process
923
*/
1024
args: string[];
11-
12-
/**
13-
* Timeout to execute the action.
14-
*/
15-
timeout?: number;
1625
}
1726

1827
interface ICleanupMessageBase {
@@ -29,9 +38,12 @@ interface ISpawnCommandCleanupMessage extends ICleanupMessageBase {
2938
commandInfo: ISpawnCommandInfo;
3039
}
3140

32-
interface IDeleteFileCleanupMessage extends ICleanupMessageBase {
33-
/**
34-
* Path to file/directory to be deleted.
35-
*/
36-
filePath: string;
41+
interface IFileCleanupMessage extends ICleanupMessageBase, IFilePath { }
42+
43+
interface IJSCommand extends ITimeout, IFilePath {
44+
data: IDictionary<any>;
3745
}
46+
47+
interface IJSCleanupMessage extends ICleanupMessageBase {
48+
jsCommand: IJSCommand;
49+
}

lib/detached-processes/cleanup-process.ts

+64-6
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,47 @@ fileLogService.logData({ message: "Initializing Cleanup process." });
1919

2020
const commandsInfos: ISpawnCommandInfo[] = [];
2121
const filesToDelete: string[] = [];
22+
const jsCommands: IJSCommand[] = [];
23+
24+
const executeJSCleanup = async (jsCommand: IJSCommand) => {
25+
const $childProcess = $injector.resolve<IChildProcess>("childProcess");
26+
27+
try {
28+
fileLogService.logData({ message: `Start executing action for file: ${jsCommand.filePath} and data ${JSON.stringify(jsCommand.data)}` });
29+
30+
await $childProcess.trySpawnFromCloseEvent(process.execPath, [path.join(__dirname, "cleanup-js-subprocess.js"), pathToBootstrap, logFile, jsCommand.filePath, JSON.stringify(jsCommand.data)], {}, { throwError: true, timeout: jsCommand.timeout || 3000 });
31+
fileLogService.logData({ message: `Finished xecuting action for file: ${jsCommand.filePath} and data ${JSON.stringify(jsCommand.data)}` });
32+
33+
} catch (err) {
34+
fileLogService.logData({ message: `Unable to execute action for file ${jsCommand.filePath} with data ${JSON.stringify(jsCommand.data)}. Error is: ${err}.`, type: FileLogMessageType.Error });
35+
}
36+
};
2237

2338
const executeCleanup = async () => {
2439
const $childProcess = $injector.resolve<IChildProcess>("childProcess");
40+
41+
for (const jsCommand of jsCommands) {
42+
await executeJSCleanup(jsCommand);
43+
}
44+
2545
for (const commandInfo of commandsInfos) {
2646
try {
2747
fileLogService.logData({ message: `Start executing command: ${JSON.stringify(commandInfo)}` });
2848

2949
await $childProcess.trySpawnFromCloseEvent(commandInfo.command, commandInfo.args, {}, { throwError: true, timeout: commandInfo.timeout || 3000 });
3050
fileLogService.logData({ message: `Successfully executed command: ${JSON.stringify(commandInfo)}` });
3151
} catch (err) {
32-
fileLogService.logData({ message: `Unable to execute command: ${JSON.stringify(commandInfo)}`, type: FileLogMessageType.Error });
52+
fileLogService.logData({ message: `Unable to execute command: ${JSON.stringify(commandInfo)}. Error is: ${err}.`, type: FileLogMessageType.Error });
3353
}
3454
}
3555

3656
if (filesToDelete.length) {
37-
fileLogService.logData({ message: `Deleting files ${filesToDelete.join(" ")}` });
38-
shelljs.rm("-Rf", filesToDelete);
57+
try {
58+
fileLogService.logData({ message: `Deleting files ${filesToDelete.join(" ")}` });
59+
shelljs.rm("-Rf", filesToDelete);
60+
} catch (err) {
61+
fileLogService.logData({ message: `Unable to delete files: ${JSON.stringify(filesToDelete)}. Error is: ${err}.`, type: FileLogMessageType.Error });
62+
}
3963
}
4064

4165
fileLogService.logData({ message: `cleanup-process finished` });
@@ -56,7 +80,7 @@ const removeCleanupAction = (commandInfo: ISpawnCommandInfo): void => {
5680
_.remove(commandsInfos, currentCommandInfo => _.isEqual(currentCommandInfo, commandInfo));
5781
fileLogService.logData({ message: `cleanup-process removed command for execution: ${JSON.stringify(commandInfo)}` });
5882
} else {
59-
fileLogService.logData({ message: `cleanup-process cannot remove command for execution as it has note been added before: ${JSON.stringify(commandInfo)}` });
83+
fileLogService.logData({ message: `cleanup-process cannot remove command for execution as it has not been added before: ${JSON.stringify(commandInfo)}` });
6084
}
6185
};
6286

@@ -82,6 +106,32 @@ const removeDeleteAction = (filePath: string): void => {
82106
}
83107
};
84108

109+
const addJSFile = (jsCommand: IJSCommand): void => {
110+
const fullPath = path.resolve(jsCommand.filePath);
111+
112+
jsCommand.filePath = fullPath;
113+
114+
if (_.some(jsCommands, currentJSCommand => _.isEqual(currentJSCommand, jsCommand))) {
115+
fileLogService.logData({ message: `cleanup-process will not add JS file for execution as it has been added already: ${JSON.stringify(jsCommand)}` });
116+
} else {
117+
fileLogService.logData({ message: `cleanup-process added JS file for execution: ${JSON.stringify(jsCommand)}` });
118+
jsCommands.push(jsCommand);
119+
}
120+
};
121+
122+
const removeJSFile = (jsCommand: IJSCommand): void => {
123+
const fullPath = path.resolve(jsCommand.filePath);
124+
125+
jsCommand.filePath = fullPath;
126+
127+
if (_.some(jsCommands, currentJSCommand => _.isEqual(currentJSCommand, jsCommand))) {
128+
_.remove(jsCommands, currentJSCommand => _.isEqual(currentJSCommand, jsCommand));
129+
fileLogService.logData({ message: `cleanup-process removed JS action for execution: ${JSON.stringify(jsCommand)}` });
130+
} else {
131+
fileLogService.logData({ message: `cleanup-process cannot remove JS action for execution as it has not been added before: ${JSON.stringify(jsCommand)}` });
132+
}
133+
};
134+
85135
process.on("message", async (cleanupProcessMessage: ICleanupMessageBase) => {
86136
fileLogService.logData({ message: `cleanup-process received message of type: ${JSON.stringify(cleanupProcessMessage)}` });
87137

@@ -93,10 +143,18 @@ process.on("message", async (cleanupProcessMessage: ICleanupMessageBase) => {
93143
removeCleanupAction((<ISpawnCommandCleanupMessage>cleanupProcessMessage).commandInfo);
94144
break;
95145
case CleanupProcessMessage.AddDeleteFileAction:
96-
addDeleteAction((<IDeleteFileCleanupMessage>cleanupProcessMessage).filePath);
146+
addDeleteAction((<IFileCleanupMessage>cleanupProcessMessage).filePath);
97147
break;
98148
case CleanupProcessMessage.RemoveDeleteFileAction:
99-
removeDeleteAction((<IDeleteFileCleanupMessage>cleanupProcessMessage).filePath);
149+
removeDeleteAction((<IFileCleanupMessage>cleanupProcessMessage).filePath);
150+
break;
151+
case CleanupProcessMessage.AddJSFileToRequire:
152+
const jsCleanupMessage = <IJSCleanupMessage>cleanupProcessMessage;
153+
addJSFile(jsCleanupMessage.jsCommand);
154+
break;
155+
case CleanupProcessMessage.RemoveJSFileToRequire:
156+
const msgToRemove = <IJSCleanupMessage>cleanupProcessMessage;
157+
removeJSFile(msgToRemove.jsCommand);
100158
break;
101159
default:
102160
fileLogService.logData({ message: `Unable to handle message of type ${cleanupProcessMessage.messageType}. Full message is ${JSON.stringify(cleanupProcessMessage)}`, type: FileLogMessageType.Error });

lib/detached-processes/detached-process-enums.d.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,17 @@ declare const enum CleanupProcessMessage {
4040
AddDeleteFileAction = "AddDeleteFileAction",
4141

4242
/**
43-
* This type of message defines the cleanup procedure should not delete previously specified file.
43+
* This type of message defines that the cleanup procedure should not delete previously specified file.
4444
*/
4545
RemoveDeleteFileAction = "RemoveDeleteFileAction",
4646

47+
/**
48+
* This type of message defines that the cleanup procedure will require the specified JS file, which should execute some action.
49+
*/
50+
AddJSFileToRequire = "AddJSFileToRequire",
51+
52+
/**
53+
* This type of message defines that the cleanup procedure will not require the previously specified JS file.
54+
*/
55+
RemoveJSFileToRequire = "RemoveJSFileToRequire",
4756
}

lib/services/cleanup-service.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,22 @@ export class CleanupService implements ICleanupService {
2727

2828
public async addCleanupDeleteAction(filePath: string): Promise<void> {
2929
const cleanupProcess = await this.getCleanupProcess();
30-
cleanupProcess.send(<IDeleteFileCleanupMessage>{ messageType: CleanupProcessMessage.AddDeleteFileAction, filePath });
30+
cleanupProcess.send(<IFileCleanupMessage>{ messageType: CleanupProcessMessage.AddDeleteFileAction, filePath });
3131
}
3232

3333
public async removeCleanupDeleteAction(filePath: string): Promise<void> {
3434
const cleanupProcess = await this.getCleanupProcess();
35-
cleanupProcess.send(<IDeleteFileCleanupMessage>{ messageType: CleanupProcessMessage.RemoveDeleteFileAction, filePath });
35+
cleanupProcess.send(<IFileCleanupMessage>{ messageType: CleanupProcessMessage.RemoveDeleteFileAction, filePath });
36+
}
37+
38+
public async addCleanupJS(jsCommand: IJSCommand): Promise<void> {
39+
const cleanupProcess = await this.getCleanupProcess();
40+
cleanupProcess.send(<IJSCleanupMessage>{ messageType: CleanupProcessMessage.AddJSFileToRequire, jsCommand });
41+
}
42+
43+
public async removeCleanupJS(jsCommand: IJSCommand): Promise<void> {
44+
const cleanupProcess = await this.getCleanupProcess();
45+
cleanupProcess.send(<IJSCleanupMessage>{ messageType: CleanupProcessMessage.RemoveJSFileToRequire, jsCommand});
3646
}
3747

3848
@exported("cleanupService")

0 commit comments

Comments
 (0)