chore: Reduce lint exceptions in ctl (#37643)

Fix linting exceptions in `ctl`.


## Automation

/test sanity

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!WARNING]
> Tests have not run on the HEAD
1f2242abcfac193fb321dee8d64cb194dea0f803 yet
> <hr>Fri, 22 Nov 2024 10:06:23 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

- **New Features**
- Enhanced backup and restore processes with improved user prompts and
error handling.
- Added support for optional command-line flags during database imports.

- **Bug Fixes**
- Improved error handling for various operations, including database
exports and imports.
	- Enhanced logging for backup errors to provide more context.

- **Documentation**
- Updated user prompts and error messages for clarity during backup and
restore operations.

- **Tests**
- Expanded test coverage for backup functionalities and utility
functions to ensure robust error handling and output validation.

- **Chores**
	- Updated dependencies to enhance TypeScript development experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Shrikant Sharat Kandula 2024-11-22 15:57:56 +05:30 committed by GitHub
parent d87f7ccd62
commit 0685d335ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 145 additions and 25 deletions

View File

@ -36,6 +36,7 @@
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/jest": "^29.2.3", "@types/jest": "^29.2.3",
"@types/node": "*",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/readline-sync": "^1.4.8", "@types/readline-sync": "^1.4.8",
"jest": "^29.3.1", "jest": "^29.3.1",

View File

@ -1,16 +1,6 @@
{ {
"extends": ["../../../../.eslintrc.base.json"], "extends": ["../../.eslintrc.json"],
"rules": { "rules": {
"@typescript-eslint/ban-ts-comment": "off", "no-console": "off"
"@typescript-eslint/prefer-nullish-coalescing": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/no-explicit-any": "off",
"testing-library/no-debugging-utils": "off",
"@typescript-eslint/no-var-requires": "off",
"padding-line-between-statements": "off",
"no-console": "off",
"@typescript-eslint/promise-function-async": "off",
"@typescript-eslint/no-unused-vars": "off",
"sort-destructure-keys/sort-destructure-keys": "off"
} }
} }

View File

@ -6,7 +6,6 @@ jest.mock("./utils", () => ({
import * as backup from "./backup"; import * as backup from "./backup";
import * as Constants from "./constants"; import * as Constants from "./constants";
import os from "os"; import os from "os";
// @ts-ignore
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import * as utils from "./utils"; import * as utils from "./utils";
import readlineSync from "readline-sync"; import readlineSync from "readline-sync";
@ -21,16 +20,19 @@ describe("Backup Tests", () => {
test("Available Space in /appsmith-stacks volume in Bytes", async () => { test("Available Space in /appsmith-stacks volume in Bytes", async () => {
const res = expect(await backup.getAvailableBackupSpaceInBytes("/")); const res = expect(await backup.getAvailableBackupSpaceInBytes("/"));
res.toBeGreaterThan(1024 * 1024); res.toBeGreaterThan(1024 * 1024);
}); });
it("Check the constant is 2 GB", () => { it("Check the constant is 2 GB", () => {
const size = 2 * 1024 * 1024 * 1024; const size = 2 * 1024 * 1024 * 1024;
expect(Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES).toBe(size); expect(Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES).toBe(size);
}); });
it("Should throw Error when the available size is below MIN_REQUIRED_DISK_SPACE_IN_BYTES", () => { it("Should throw Error when the available size is below MIN_REQUIRED_DISK_SPACE_IN_BYTES", () => {
const size = Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES - 1; const size = Constants.MIN_REQUIRED_DISK_SPACE_IN_BYTES - 1;
expect(() => backup.checkAvailableBackupSpace(size)).toThrow(); expect(() => backup.checkAvailableBackupSpace(size)).toThrow();
}); });
@ -48,12 +50,14 @@ describe("Backup Tests", () => {
os.tmpdir = jest.fn().mockReturnValue("temp/dir"); os.tmpdir = jest.fn().mockReturnValue("temp/dir");
fsPromises.mkdtemp = jest.fn().mockImplementation((a) => a); fsPromises.mkdtemp = jest.fn().mockImplementation((a) => a);
const res = await backup.generateBackupRootPath(); const res = await backup.generateBackupRootPath();
expect(res).toBe("temp/dir/appsmithctl-backup-"); expect(res).toBe("temp/dir/appsmithctl-backup-");
}); });
test("Test backup contents path generation", () => { test("Test backup contents path generation", () => {
const root = "/rootDir"; const root = "/rootDir";
const timestamp = "0000-00-0T00-00-00.00Z"; const timestamp = "0000-00-0T00-00-00.00Z";
expect(backup.getBackupContentsPath(root, timestamp)).toBe( expect(backup.getBackupContentsPath(root, timestamp)).toBe(
"/rootDir/appsmith-backup-0000-00-0T00-00-00.00Z", "/rootDir/appsmith-backup-0000-00-0T00-00-00.00Z",
); );
@ -65,6 +69,7 @@ describe("Backup Tests", () => {
const cmd = const cmd =
"mongodump --uri=mongodb://username:password@host/appsmith --archive=/dest/mongodb-data.gz --gzip"; "mongodump --uri=mongodb://username:password@host/appsmith --archive=/dest/mongodb-data.gz --gzip";
const res = await backup.executeMongoDumpCMD(dest, appsmithMongoURI); const res = await backup.executeMongoDumpCMD(dest, appsmithMongoURI);
expect(res).toBe(cmd); expect(res).toBe(cmd);
console.log(res); console.log(res);
}); });
@ -86,6 +91,7 @@ describe("Backup Tests", () => {
const dest = "/destdir"; const dest = "/destdir";
const cmd = "ln -s /appsmith-stacks/git-storage /destdir/git-storage"; const cmd = "ln -s /appsmith-stacks/git-storage /destdir/git-storage";
const res = await backup.executeCopyCMD(gitRoot, dest); const res = await backup.executeCopyCMD(gitRoot, dest);
expect(res).toBe(cmd); expect(res).toBe(cmd);
console.log(res); console.log(res);
}); });
@ -99,6 +105,7 @@ describe("Backup Tests", () => {
} }
}); });
const res = await utils.getCurrentAppsmithVersion(); const res = await utils.getCurrentAppsmithVersion();
expect(res).toBe("v0.0.0-SNAPSHOT"); expect(res).toBe("v0.0.0-SNAPSHOT");
}); });
@ -130,10 +137,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 4 and there are 5 files", async () => { test("Cleanup Backups when limit is 4 and there are 5 files", async () => {
const backupArchivesLimit = 4; const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a)); fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2", "file3", "file4", "file5"]; const backupFiles = ["file1", "file2", "file3", "file4", "file5"];
const expectedBackupFiles = ["file2", "file3", "file4", "file5"]; const expectedBackupFiles = ["file2", "file3", "file4", "file5"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit); const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res); console.log(res);
expect(res).toEqual(expectedBackupFiles); expect(res).toEqual(expectedBackupFiles);
@ -141,10 +150,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 2 and there are 5 files", async () => { test("Cleanup Backups when limit is 2 and there are 5 files", async () => {
const backupArchivesLimit = 2; const backupArchivesLimit = 2;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a)); fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2", "file3", "file4", "file5"]; const backupFiles = ["file1", "file2", "file3", "file4", "file5"];
const expectedBackupFiles = ["file4", "file5"]; const expectedBackupFiles = ["file4", "file5"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit); const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res); console.log(res);
expect(res).toEqual(expectedBackupFiles); expect(res).toEqual(expectedBackupFiles);
@ -152,10 +163,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 4 and there are 4 files", async () => { test("Cleanup Backups when limit is 4 and there are 4 files", async () => {
const backupArchivesLimit = 4; const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a)); fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2", "file3", "file4"]; const backupFiles = ["file1", "file2", "file3", "file4"];
const expectedBackupFiles = ["file1", "file2", "file3", "file4"]; const expectedBackupFiles = ["file1", "file2", "file3", "file4"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit); const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res); console.log(res);
expect(res).toEqual(expectedBackupFiles); expect(res).toEqual(expectedBackupFiles);
@ -163,10 +176,12 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 4 and there are 2 files", async () => { test("Cleanup Backups when limit is 4 and there are 2 files", async () => {
const backupArchivesLimit = 4; const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a)); fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1", "file2"]; const backupFiles = ["file1", "file2"];
const expectedBackupFiles = ["file1", "file2"]; const expectedBackupFiles = ["file1", "file2"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit); const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res); console.log(res);
expect(res).toEqual(expectedBackupFiles); expect(res).toEqual(expectedBackupFiles);
@ -174,26 +189,31 @@ describe("Backup Tests", () => {
test("Cleanup Backups when limit is 2 and there is 1 file", async () => { test("Cleanup Backups when limit is 2 and there is 1 file", async () => {
const backupArchivesLimit = 4; const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a)); fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = ["file1"]; const backupFiles = ["file1"];
const expectedBackupFiles = ["file1"]; const expectedBackupFiles = ["file1"];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit); const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res); console.log(res);
expect(res).toEqual(expectedBackupFiles); expect(res).toEqual(expectedBackupFiles);
}); });
test("Cleanup Backups when limit is 2 and there is no file", async () => { test("Cleanup Backups when limit is 2 and there is no file", async () => {
const backupArchivesLimit = 4; const backupArchivesLimit = 4;
fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a)); fsPromises.rm = jest.fn().mockImplementation(async (a) => console.log(a));
const backupFiles = []; const backupFiles = [];
const expectedBackupFiles = []; const expectedBackupFiles = [];
const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit); const res = await backup.removeOldBackups(backupFiles, backupArchivesLimit);
console.log(res); console.log(res);
expect(res).toEqual(expectedBackupFiles); expect(res).toEqual(expectedBackupFiles);
}); });
test("Test get encryption password from user prompt when both passwords are the same", async () => { test("Test get encryption password from user prompt when both passwords are the same", async () => {
const password = "password#4321"; const password = "password#4321";
readlineSync.question = jest.fn().mockImplementation(() => password); readlineSync.question = jest.fn().mockImplementation(() => password);
const password_res = backup.getEncryptionPasswordFromUser(); const password_res = backup.getEncryptionPasswordFromUser();
@ -202,10 +222,12 @@ describe("Backup Tests", () => {
test("Test get encryption password from user prompt when both passwords are the different", async () => { test("Test get encryption password from user prompt when both passwords are the different", async () => {
const password = "password#4321"; const password = "password#4321";
readlineSync.question = jest.fn().mockImplementation((a) => { readlineSync.question = jest.fn().mockImplementation((a) => {
if (a == "Enter the above password again: ") { if (a == "Enter the above password again: ") {
return "pass"; return "pass";
} }
return password; return password;
}); });
const password_res = backup.getEncryptionPasswordFromUser(); const password_res = backup.getEncryptionPasswordFromUser();
@ -233,6 +255,7 @@ describe("Backup Tests", () => {
archivePath, archivePath,
encryptionPassword, encryptionPassword,
); );
console.log(res); console.log(res);
expect(res).toEqual("/rootDir/appsmith-backup-0000-00-0T00-00-00.00Z.enc"); expect(res).toEqual("/rootDir/appsmith-backup-0000-00-0T00-00-00.00Z.enc");
}); });
@ -243,6 +266,7 @@ test("Get DB name from Mongo URI 1", async () => {
"mongodb+srv://admin:password@test.cluster.mongodb.net/my_db_name?retryWrites=true&minPoolSize=1&maxPoolSize=10&maxIdleTimeMS=900000&authSource=admin"; "mongodb+srv://admin:password@test.cluster.mongodb.net/my_db_name?retryWrites=true&minPoolSize=1&maxPoolSize=10&maxIdleTimeMS=900000&authSource=admin";
const expectedDBName = "my_db_name"; const expectedDBName = "my_db_name";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri); const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName); expect(dbName).toEqual(expectedDBName);
}); });
@ -251,6 +275,7 @@ test("Get DB name from Mongo URI 2", async () => {
"mongodb+srv://admin:password@test.cluster.mongodb.net/test123?retryWrites=true&minPoolSize=1&maxPoolSize=10&maxIdleTimeMS=900000&authSource=admin"; "mongodb+srv://admin:password@test.cluster.mongodb.net/test123?retryWrites=true&minPoolSize=1&maxPoolSize=10&maxIdleTimeMS=900000&authSource=admin";
const expectedDBName = "test123"; const expectedDBName = "test123";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri); const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName); expect(dbName).toEqual(expectedDBName);
}); });
@ -259,6 +284,7 @@ test("Get DB name from Mongo URI 3", async () => {
"mongodb+srv://admin:password@test.cluster.mongodb.net/test123"; "mongodb+srv://admin:password@test.cluster.mongodb.net/test123";
const expectedDBName = "test123"; const expectedDBName = "test123";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri); const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName); expect(dbName).toEqual(expectedDBName);
}); });
@ -266,5 +292,6 @@ test("Get DB name from Mongo URI 4", async () => {
const mongodb_uri = "mongodb://appsmith:pAssW0rd!@localhost:27017/appsmith"; const mongodb_uri = "mongodb://appsmith:pAssW0rd!@localhost:27017/appsmith";
const expectedDBName = "appsmith"; const expectedDBName = "appsmith";
const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri); const dbName = utils.getDatabaseNameFromMongoURI(mongodb_uri);
expect(dbName).toEqual(expectedDBName); expect(dbName).toEqual(expectedDBName);
}); });

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import path from "path"; import path from "path";
import os from "os"; import os from "os";
@ -23,6 +22,7 @@ export async function run() {
console.log("Available free space at /appsmith-stacks"); console.log("Available free space at /appsmith-stacks");
const availSpaceInBytes = const availSpaceInBytes =
getAvailableBackupSpaceInBytes("/appsmith-stacks"); getAvailableBackupSpaceInBytes("/appsmith-stacks");
console.log("\n"); console.log("\n");
checkAvailableBackupSpace(availSpaceInBytes); checkAvailableBackupSpace(availSpaceInBytes);
@ -43,26 +43,32 @@ export async function run() {
tty.isatty((process.stdout as any).fd) tty.isatty((process.stdout as any).fd)
) { ) {
encryptionPassword = getEncryptionPasswordFromUser(); encryptionPassword = getEncryptionPasswordFromUser();
if (encryptionPassword == -1) { if (encryptionPassword == -1) {
throw new Error( throw new Error(
"Backup process aborted because a valid enctyption password could not be obtained from the user", "Backup process aborted because a valid enctyption password could not be obtained from the user",
); );
} }
encryptArchive = true; encryptArchive = true;
} }
await exportDockerEnvFile(backupContentsPath, encryptArchive); await exportDockerEnvFile(backupContentsPath, encryptArchive);
archivePath = await createFinalArchive(backupRootPath, timestamp); archivePath = await createFinalArchive(backupRootPath, timestamp);
// shell.exec("openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -in " + archivePath + " -out " + archivePath + ".enc"); // shell.exec("openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -in " + archivePath + " -out " + archivePath + ".enc");
if (encryptArchive) { if (encryptArchive) {
const encryptedArchivePath = await encryptBackupArchive( const encryptedArchivePath = await encryptBackupArchive(
archivePath, archivePath,
encryptionPassword, encryptionPassword,
); );
await logger.backup_info( await logger.backup_info(
"Finished creating an encrypted a backup archive at " + "Finished creating an encrypted a backup archive at " +
encryptedArchivePath, encryptedArchivePath,
); );
if (archivePath != null) { if (archivePath != null) {
await fsPromises.rm(archivePath, { recursive: true, force: true }); await fsPromises.rm(archivePath, { recursive: true, force: true });
} }
@ -94,6 +100,7 @@ export async function run() {
if (command_args.includes("--error-mail")) { if (command_args.includes("--error-mail")) {
const currentTS = new Date().getTime(); const currentTS = new Date().getTime();
const lastMailTS = await utils.getLastBackupErrorMailSentInMilliSec(); const lastMailTS = await utils.getLastBackupErrorMailSentInMilliSec();
if ( if (
lastMailTS + lastMailTS +
Constants.DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC < Constants.DURATION_BETWEEN_BACKUP_ERROR_MAILS_IN_MILLI_SEC <
@ -107,11 +114,13 @@ export async function run() {
if (backupRootPath != null) { if (backupRootPath != null) {
await fsPromises.rm(backupRootPath, { recursive: true, force: true }); await fsPromises.rm(backupRootPath, { recursive: true, force: true });
} }
if (encryptArchive) { if (encryptArchive) {
if (archivePath != null) { if (archivePath != null) {
await fsPromises.rm(archivePath, { recursive: true, force: true }); await fsPromises.rm(archivePath, { recursive: true, force: true });
} }
} }
await postBackupCleanup(); await postBackupCleanup();
process.exit(errorCode); process.exit(errorCode);
} }
@ -119,6 +128,7 @@ export async function run() {
export async function encryptBackupArchive(archivePath, encryptionPassword) { export async function encryptBackupArchive(archivePath, encryptionPassword) {
const encryptedArchivePath = archivePath + ".enc"; const encryptedArchivePath = archivePath + ".enc";
await utils.execCommand([ await utils.execCommand([
"openssl", "openssl",
"enc", "enc",
@ -133,11 +143,16 @@ export async function encryptBackupArchive(archivePath, encryptionPassword) {
"-k", "-k",
encryptionPassword, encryptionPassword,
]); ]);
return encryptedArchivePath; return encryptedArchivePath;
} }
export function getEncryptionPasswordFromUser() { export function getEncryptionPasswordFromUser() {
for (const _ of [1, 2, 3]) { for (const attempt of [1, 2, 3]) {
if (attempt > 1) {
console.log("Retry attempt", attempt);
}
const encryptionPwd1 = readlineSync.question( const encryptionPwd1 = readlineSync.question(
"Enter a password to encrypt the backup archive: ", "Enter a password to encrypt the backup archive: ",
{ hideEchoBack: true }, { hideEchoBack: true },
@ -146,10 +161,12 @@ export function getEncryptionPasswordFromUser() {
"Enter the above password again: ", "Enter the above password again: ",
{ hideEchoBack: true }, { hideEchoBack: true },
); );
if (encryptionPwd1 === encryptionPwd2) { if (encryptionPwd1 === encryptionPwd2) {
if (encryptionPwd1) { if (encryptionPwd1) {
return encryptionPwd1; return encryptionPwd1;
} }
console.error( console.error(
"Invalid input. Empty password is not allowed, please try again.", "Invalid input. Empty password is not allowed, please try again.",
); );
@ -157,9 +174,11 @@ export function getEncryptionPasswordFromUser() {
console.error("The passwords do not match, please try again."); console.error("The passwords do not match, please try again.");
} }
} }
console.error( console.error(
"Aborting backup process, failed to obtain valid encryption password.", "Aborting backup process, failed to obtain valid encryption password.",
); );
return -1; return -1;
} }
@ -185,6 +204,7 @@ async function createManifestFile(path) {
appsmithVersion: version, appsmithVersion: version,
dbName: utils.getDatabaseNameFromMongoURI(utils.getDburl()), dbName: utils.getDatabaseNameFromMongoURI(utils.getDburl()),
}; };
await fsPromises.writeFile( await fsPromises.writeFile(
path + "/manifest.json", path + "/manifest.json",
JSON.stringify(manifest_data), JSON.stringify(manifest_data),
@ -198,6 +218,7 @@ async function exportDockerEnvFile(destFolder, encryptArchive) {
{ encoding: "utf8" }, { encoding: "utf8" },
); );
let cleaned_content = removeSensitiveEnvData(content); let cleaned_content = removeSensitiveEnvData(content);
if (encryptArchive) { if (encryptArchive) {
cleaned_content += cleaned_content +=
"\nAPPSMITH_ENCRYPTION_SALT=" + "\nAPPSMITH_ENCRYPTION_SALT=" +
@ -205,6 +226,7 @@ async function exportDockerEnvFile(destFolder, encryptArchive) {
"\nAPPSMITH_ENCRYPTION_PASSWORD=" + "\nAPPSMITH_ENCRYPTION_PASSWORD=" +
process.env.APPSMITH_ENCRYPTION_PASSWORD; process.env.APPSMITH_ENCRYPTION_PASSWORD;
} }
await fsPromises.writeFile(destFolder + "/docker.env", cleaned_content); await fsPromises.writeFile(destFolder + "/docker.env", cleaned_content);
console.log("Exporting docker environment file done."); console.log("Exporting docker environment file done.");
} }
@ -222,6 +244,7 @@ async function createFinalArchive(destFolder, timestamp) {
console.log("Creating final archive"); console.log("Creating final archive");
const archive = `${Constants.BACKUP_PATH}/appsmith-backup-${timestamp}.tar.gz`; const archive = `${Constants.BACKUP_PATH}/appsmith-backup-${timestamp}.tar.gz`;
await utils.execCommand([ await utils.execCommand([
"tar", "tar",
"-cah", "-cah",
@ -243,10 +266,13 @@ async function postBackupCleanup() {
process.env.APPSMITH_BACKUP_ARCHIVE_LIMIT, process.env.APPSMITH_BACKUP_ARCHIVE_LIMIT,
); );
const backupFiles = await utils.listLocalBackupFiles(); const backupFiles = await utils.listLocalBackupFiles();
while (backupFiles.length > backupArchivesLimit) { while (backupFiles.length > backupArchivesLimit) {
const fileName = backupFiles.shift(); const fileName = backupFiles.shift();
await fsPromises.rm(Constants.BACKUP_PATH + "/" + fileName); await fsPromises.rm(Constants.BACKUP_PATH + "/" + fileName);
} }
console.log("Cleanup task completed."); console.log("Cleanup task completed.");
} }
@ -263,10 +289,11 @@ export function getGitRoot(gitRoot?) {
if (gitRoot == null || gitRoot === "") { if (gitRoot == null || gitRoot === "") {
gitRoot = "/appsmith-stacks/git-storage"; gitRoot = "/appsmith-stacks/git-storage";
} }
return gitRoot; return gitRoot;
} }
export function generateBackupRootPath() { export async function generateBackupRootPath() {
return fsPromises.mkdtemp(path.join(os.tmpdir(), "appsmithctl-backup-")); return fsPromises.mkdtemp(path.join(os.tmpdir(), "appsmithctl-backup-"));
} }
@ -277,6 +304,7 @@ export function getBackupContentsPath(backupRootPath, timestamp) {
export function removeSensitiveEnvData(content) { export function removeSensitiveEnvData(content) {
// Remove encryption and Mongodb data from docker.env // Remove encryption and Mongodb data from docker.env
const output_lines = []; const output_lines = [];
content.split(/\r?\n/).forEach((line) => { content.split(/\r?\n/).forEach((line) => {
if ( if (
!line.startsWith("APPSMITH_ENCRYPTION") && !line.startsWith("APPSMITH_ENCRYPTION") &&
@ -286,20 +314,24 @@ export function removeSensitiveEnvData(content) {
output_lines.push(line); output_lines.push(line);
} }
}); });
return output_lines.join("\n"); return output_lines.join("\n");
} }
export function getBackupArchiveLimit(backupArchivesLimit?) { export function getBackupArchiveLimit(backupArchivesLimit?) {
if (!backupArchivesLimit) if (!backupArchivesLimit)
backupArchivesLimit = Constants.APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT; backupArchivesLimit = Constants.APPSMITH_DEFAULT_BACKUP_ARCHIVE_LIMIT;
return backupArchivesLimit; return backupArchivesLimit;
} }
export async function removeOldBackups(backupFiles, backupArchivesLimit) { export async function removeOldBackups(backupFiles, backupArchivesLimit) {
while (backupFiles.length > backupArchivesLimit) { while (backupFiles.length > backupArchivesLimit) {
const fileName = backupFiles.shift(); const fileName = backupFiles.shift();
await fsPromises.rm(Constants.BACKUP_PATH + "/" + fileName); await fsPromises.rm(Constants.BACKUP_PATH + "/" + fileName);
} }
return backupFiles; return backupFiles;
} }
@ -309,6 +341,7 @@ export function getTimeStampInISO() {
export async function getAvailableBackupSpaceInBytes(path) { export async function getAvailableBackupSpaceInBytes(path) {
const stat = await fsPromises.statfs(path); const stat = await fsPromises.statfs(path);
return stat.bsize * stat.bfree; return stat.bsize * stat.bfree;
} }

View File

@ -26,6 +26,7 @@ export async function exec() {
async function checkReplicaSet(client: MongoClient) { async function checkReplicaSet(client: MongoClient) {
await client.connect(); await client.connect();
return await new Promise<boolean>((resolve) => { return await new Promise<boolean>((resolve) => {
try { try {
const changeStream = client const changeStream = client
@ -43,6 +44,7 @@ async function checkReplicaSet(client: MongoClient) {
} else { } else {
console.error("Error even from changeStream", err); console.error("Error even from changeStream", err);
} }
resolve(false); resolve(false);
}); });

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import * as Constants from "./constants"; import * as Constants from "./constants";
import * as utils from "./utils"; import * as utils from "./utils";
@ -6,6 +5,7 @@ import * as utils from "./utils";
export async function exportDatabase() { export async function exportDatabase() {
console.log("export_database ...."); console.log("export_database ....");
const dbUrl = utils.getDburl(); const dbUrl = utils.getDburl();
await fsPromises.mkdir(Constants.BACKUP_PATH, { recursive: true }); await fsPromises.mkdir(Constants.BACKUP_PATH, { recursive: true });
await utils.execCommand([ await utils.execCommand([
"mongodump", "mongodump",

View File

@ -30,6 +30,7 @@ export async function run(forceOption) {
console.log("stop backend & rts application before import database"); console.log("stop backend & rts application before import database");
await utils.stop(["backend", "rts"]); await utils.stop(["backend", "rts"]);
let shellCmdResult: string; let shellCmdResult: string;
try { try {
shellCmdResult = await utils.execCommandReturningOutput([ shellCmdResult = await utils.execCommandReturningOutput([
"mongo", "mongo",
@ -43,11 +44,14 @@ export async function run(forceOption) {
throw error; throw error;
} }
const collectionsLen = parseInt(shellCmdResult.trimEnd()); const collectionsLen = parseInt(shellCmdResult.trimEnd());
if (collectionsLen > 0) { if (collectionsLen > 0) {
if (forceOption) { if (forceOption) {
await importDatabase(); await importDatabase();
return; return;
} }
console.log(); console.log();
console.log( console.log(
"**************************** WARNING ****************************", "**************************** WARNING ****************************",
@ -59,12 +63,15 @@ export async function run(forceOption) {
"Importing this DB will erase this data. Are you sure you want to proceed?[Yes/No] ", "Importing this DB will erase this data. Are you sure you want to proceed?[Yes/No] ",
); );
const answer = input && input.toLocaleUpperCase(); const answer = input && input.toLocaleUpperCase();
if (answer === "Y" || answer === "YES") { if (answer === "Y" || answer === "YES") {
await importDatabase(); await importDatabase();
return; return;
} else if (answer === "N" || answer === "NO") { } else if (answer === "N" || answer === "NO") {
return; return;
} }
console.log( console.log(
`Your input is invalid. Please try to run import command again.`, `Your input is invalid. Please try to run import command again.`,
); );

View File

@ -38,6 +38,7 @@ if (["export-db", "export_db", "ex"].includes(command)) {
console.log("Importing database"); console.log("Importing database");
// Get Force option flag to run import DB immediately // Get Force option flag to run import DB immediately
const forceOption = process.argv[3] === "-f"; const forceOption = process.argv[3] === "-f";
try { try {
import_db.run(forceOption); import_db.run(forceOption);
console.log("Importing database done"); console.log("Importing database done");

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import * as Constants from "./constants"; import * as Constants from "./constants";

View File

@ -59,6 +59,7 @@ export async function sendBackupErrorToAdmins(err, backupTimestamp) {
if (instanceName) { if (instanceName) {
text = text + "Appsmith instance name: " + instanceName + "\n"; text = text + "Appsmith instance name: " + instanceName + "\n";
} }
if (domainName) { if (domainName) {
text = text =
text + text +
@ -68,6 +69,7 @@ export async function sendBackupErrorToAdmins(err, backupTimestamp) {
"/settings/general" + "/settings/general" +
"\n"; "\n";
} }
text = text + "\n" + err.stack; text = text + "\n" + err.stack;
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({

View File

@ -4,6 +4,7 @@ const command_args = process.argv.slice(3);
export async function exec() { export async function exec() {
let errorCode = 0; let errorCode = 0;
try { try {
await execMongoEval(command_args[0], process.env.APPSMITH_DB_URL); await execMongoEval(command_args[0], process.env.APPSMITH_DB_URL);
} catch (err) { } catch (err) {
@ -16,9 +17,11 @@ export async function exec() {
async function execMongoEval(queryExpression, appsmithMongoURI) { async function execMongoEval(queryExpression, appsmithMongoURI) {
queryExpression = queryExpression.trim(); queryExpression = queryExpression.trim();
if (command_args.includes("--pretty")) { if (command_args.includes("--pretty")) {
queryExpression += ".pretty()"; queryExpression += ".pretty()";
} }
return await utils.execCommand([ return await utils.execCommand([
"mongosh", "mongosh",
appsmithMongoURI, appsmithMongoURI,

View File

@ -1,4 +1,3 @@
// @ts-ignore
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import path from "path"; import path from "path";
import os from "os"; import os from "os";
@ -10,14 +9,17 @@ const command_args = process.argv.slice(3);
async function getBackupFileName() { async function getBackupFileName() {
const backupFiles = await utils.listLocalBackupFiles(); const backupFiles = await utils.listLocalBackupFiles();
console.log( console.log(
"\n" + "\n" +
backupFiles.length + backupFiles.length +
" Appsmith backup file(s) found: [Sorted in ascending/chronological order]", " Appsmith backup file(s) found: [Sorted in ascending/chronological order]",
); );
if (backupFiles.length == 0) { if (backupFiles.length == 0) {
return; return;
} }
console.log( console.log(
"----------------------------------------------------------------", "----------------------------------------------------------------",
); );
@ -25,11 +27,13 @@ async function getBackupFileName() {
console.log( console.log(
"----------------------------------------------------------------", "----------------------------------------------------------------",
); );
for (let i = 0; i < backupFiles.length; i++) { for (let i = 0; i < backupFiles.length; i++) {
if (i === backupFiles.length - 1) if (i === backupFiles.length - 1)
console.log(i + "\t|\t" + backupFiles[i] + " <--Most recent backup"); console.log(i + "\t|\t" + backupFiles[i] + " <--Most recent backup");
else console.log(i + "\t|\t" + backupFiles[i]); else console.log(i + "\t|\t" + backupFiles[i]);
} }
console.log( console.log(
"----------------------------------------------------------------", "----------------------------------------------------------------",
); );
@ -38,6 +42,7 @@ async function getBackupFileName() {
readlineSync.question("Please enter the backup file index: "), readlineSync.question("Please enter the backup file index: "),
10, 10,
); );
if ( if (
!isNaN(backupFileIndex) && !isNaN(backupFileIndex) &&
Number.isInteger(backupFileIndex) && Number.isInteger(backupFileIndex) &&
@ -54,8 +59,14 @@ async function getBackupFileName() {
async function decryptArchive(encryptedFilePath, backupFilePath) { async function decryptArchive(encryptedFilePath, backupFilePath) {
console.log("Enter the password to decrypt the backup archive:"); console.log("Enter the password to decrypt the backup archive:");
for (const _ of [1, 2, 3]) {
for (const attempt of [1, 2, 3]) {
if (attempt > 1) {
console.log("Retry attempt", attempt);
}
const decryptionPwd = readlineSync.question("", { hideEchoBack: true }); const decryptionPwd = readlineSync.question("", { hideEchoBack: true });
try { try {
await utils.execCommandSilent([ await utils.execCommandSilent([
"openssl", "openssl",
@ -72,11 +83,13 @@ async function decryptArchive(encryptedFilePath, backupFilePath) {
"-k", "-k",
decryptionPwd, decryptionPwd,
]); ]);
return true; return true;
} catch (error) { } catch (error) {
console.log("Invalid password. Please try again:"); console.log("Invalid password. Please try again:");
} }
} }
return false; return false;
} }
@ -101,9 +114,11 @@ async function restoreDatabase(restoreContentsPath, dbUrl) {
`--archive=${restoreContentsPath}/mongodb-data.gz`, `--archive=${restoreContentsPath}/mongodb-data.gz`,
"--gzip", "--gzip",
]; ];
try { try {
const fromDbName = await getBackupDatabaseName(restoreContentsPath); const fromDbName = await getBackupDatabaseName(restoreContentsPath);
const toDbName = utils.getDatabaseNameFromMongoURI(dbUrl); const toDbName = utils.getDatabaseNameFromMongoURI(dbUrl);
console.log("Restoring database from " + fromDbName + " to " + toDbName); console.log("Restoring database from " + fromDbName + " to " + toDbName);
cmd.push( cmd.push(
"--nsInclude=*", "--nsInclude=*",
@ -130,6 +145,7 @@ async function restoreDockerEnvFile(
const updatedbUrl = utils.getDburl(); const updatedbUrl = utils.getDburl();
let encryptionPwd = process.env.APPSMITH_ENCRYPTION_PASSWORD; let encryptionPwd = process.env.APPSMITH_ENCRYPTION_PASSWORD;
let encryptionSalt = process.env.APPSMITH_ENCRYPTION_SALT; let encryptionSalt = process.env.APPSMITH_ENCRYPTION_SALT;
await utils.execCommand([ await utils.execCommand([
"mv", "mv",
dockerEnvFile, dockerEnvFile,
@ -140,6 +156,7 @@ async function restoreDockerEnvFile(
restoreContentsPath + "/docker.env", restoreContentsPath + "/docker.env",
dockerEnvFile, dockerEnvFile,
]); ]);
if (overwriteEncryptionKeys) { if (overwriteEncryptionKeys) {
if (encryptionPwd && encryptionSalt) { if (encryptionPwd && encryptionSalt) {
const input = readlineSync.question( const input = readlineSync.question(
@ -148,6 +165,7 @@ async function restoreDockerEnvFile(
Or Type "n"/"No" to provide encryption key & password corresponding to the original Appsmith instance that is being restored.\n', Or Type "n"/"No" to provide encryption key & password corresponding to the original Appsmith instance that is being restored.\n',
); );
const answer = input && input.toLocaleUpperCase(); const answer = input && input.toLocaleUpperCase();
if (answer === "N" || answer === "NO") { if (answer === "N" || answer === "NO") {
encryptionPwd = readlineSync.question( encryptionPwd = readlineSync.question(
"Enter the APPSMITH_ENCRYPTION_PASSWORD: ", "Enter the APPSMITH_ENCRYPTION_PASSWORD: ",
@ -180,6 +198,7 @@ async function restoreDockerEnvFile(
}, },
); );
} }
await fsPromises.appendFile( await fsPromises.appendFile(
dockerEnvFile, dockerEnvFile,
"\nAPPSMITH_ENCRYPTION_PASSWORD=" + "\nAPPSMITH_ENCRYPTION_PASSWORD=" +
@ -204,6 +223,7 @@ async function restoreDockerEnvFile(
process.env.APPSMITH_MONGODB_PASSWORD, process.env.APPSMITH_MONGODB_PASSWORD,
); );
} }
console.log("Restoring docker environment file completed"); console.log("Restoring docker environment file completed");
} }
@ -211,6 +231,7 @@ async function restoreGitStorageArchive(restoreContentsPath, backupName) {
console.log("Restoring git-storage archive"); console.log("Restoring git-storage archive");
// TODO: Consider APPSMITH_GIT_ROOT env for later iterations // TODO: Consider APPSMITH_GIT_ROOT env for later iterations
const gitRoot = "/appsmith-stacks/git-storage"; const gitRoot = "/appsmith-stacks/git-storage";
await utils.execCommand(["mv", gitRoot, gitRoot + "-" + backupName]); await utils.execCommand(["mv", gitRoot, gitRoot + "-" + backupName]);
await utils.execCommand([ await utils.execCommand([
"mv", "mv",
@ -228,6 +249,7 @@ async function checkRestoreVersionCompatability(restoreContentsPath) {
); );
const manifest_json = JSON.parse(manifest_data); const manifest_json = JSON.parse(manifest_data);
const restoreVersion = manifest_json["appsmithVersion"]; const restoreVersion = manifest_json["appsmithVersion"];
console.log("Current Appsmith Version: " + currentVersion); console.log("Current Appsmith Version: " + currentVersion);
console.log("Restore Appsmith Version: " + restoreVersion); console.log("Restore Appsmith Version: " + restoreVersion);
@ -251,6 +273,7 @@ async function checkRestoreVersionCompatability(restoreContentsPath) {
const confirm = readlineSync.question( const confirm = readlineSync.question(
'Press Enter to continue \nOr Type "c" to cancel the restore process.\n', 'Press Enter to continue \nOr Type "c" to cancel the restore process.\n',
); );
if (confirm.toLowerCase() === "c") { if (confirm.toLowerCase() === "c") {
process.exit(0); process.exit(0);
} }
@ -259,6 +282,7 @@ async function checkRestoreVersionCompatability(restoreContentsPath) {
async function getBackupDatabaseName(restoreContentsPath) { async function getBackupDatabaseName(restoreContentsPath) {
let db_name = "appsmith"; let db_name = "appsmith";
if (command_args.includes("--backup-db-name")) { if (command_args.includes("--backup-db-name")) {
for (let i = 0; i < command_args.length; i++) { for (let i = 0; i < command_args.length; i++) {
if (command_args[i].startsWith("--backup-db-name")) { if (command_args[i].startsWith("--backup-db-name")) {
@ -271,12 +295,14 @@ async function getBackupDatabaseName(restoreContentsPath) {
{ encoding: "utf8" }, { encoding: "utf8" },
); );
const manifest_json = JSON.parse(manifest_data); const manifest_json = JSON.parse(manifest_data);
if ("dbName" in manifest_json) { if ("dbName" in manifest_json) {
db_name = manifest_json["dbName"]; db_name = manifest_json["dbName"];
} }
} }
console.log("Backup Database Name: " + db_name); console.log("Backup Database Name: " + db_name);
return db_name; return db_name;
} }
@ -290,15 +316,18 @@ export async function run() {
try { try {
let backupFileName = await getBackupFileName(); let backupFileName = await getBackupFileName();
if (backupFileName == null) { if (backupFileName == null) {
process.exit(errorCode); process.exit(errorCode);
} else { } else {
backupFilePath = path.join(Constants.BACKUP_PATH, backupFileName); backupFilePath = path.join(Constants.BACKUP_PATH, backupFileName);
if (isArchiveEncrypted(backupFileName)) { if (isArchiveEncrypted(backupFileName)) {
const encryptedBackupFilePath = path.join( const encryptedBackupFilePath = path.join(
Constants.BACKUP_PATH, Constants.BACKUP_PATH,
backupFileName, backupFileName,
); );
backupFileName = backupFileName.replace(".enc", ""); backupFileName = backupFileName.replace(".enc", "");
backupFilePath = path.join(Constants.BACKUP_PATH, backupFileName); backupFilePath = path.join(Constants.BACKUP_PATH, backupFileName);
cleanupArchive = true; cleanupArchive = true;
@ -307,6 +336,7 @@ export async function run() {
encryptedBackupFilePath, encryptedBackupFilePath,
backupFilePath, backupFilePath,
); );
if (!decryptSuccess) { if (!decryptSuccess) {
console.log( console.log(
"You have entered the incorrect password multiple times. Aborting the restore process.", "You have entered the incorrect password multiple times. Aborting the restore process.",
@ -315,6 +345,7 @@ export async function run() {
process.exit(errorCode); process.exit(errorCode);
} }
} }
const backupName = backupFileName.replace(/\.tar\.gz$/, ""); const backupName = backupFileName.replace(/\.tar\.gz$/, "");
const restoreRootPath = await fsPromises.mkdtemp(os.tmpdir()); const restoreRootPath = await fsPromises.mkdtemp(os.tmpdir());
const restoreContentsPath = path.join(restoreRootPath, backupName); const restoreContentsPath = path.join(restoreRootPath, backupName);
@ -346,6 +377,7 @@ export async function run() {
if (cleanupArchive) { if (cleanupArchive) {
await fsPromises.rm(backupFilePath, { force: true }); await fsPromises.rm(backupFilePath, { force: true });
} }
await utils.start(["backend", "rts"]); await utils.start(["backend", "rts"]);
process.exit(errorCode); process.exit(errorCode);
} }

View File

@ -9,6 +9,7 @@ describe("execCommandReturningOutput", () => {
"hello", "hello",
"world", "world",
]); ]);
expect(result).toBe("hello world"); expect(result).toBe("hello world");
}); });
@ -18,6 +19,7 @@ describe("execCommandReturningOutput", () => {
"--eval", "--eval",
"console.log('to out')", "console.log('to out')",
]); ]);
expect(result).toBe("to out"); expect(result).toBe("to out");
}); });
@ -27,6 +29,7 @@ describe("execCommandReturningOutput", () => {
"--eval", "--eval",
"console.error('to err')", "console.error('to err')",
]); ]);
expect(result).toBe("to err"); expect(result).toBe("to err");
}); });
@ -36,6 +39,7 @@ describe("execCommandReturningOutput", () => {
"--eval", "--eval",
"console.log('to out'); console.error('to err')", "console.log('to out'); console.error('to err')",
]); ]);
expect(result).toBe("to out\nto err"); expect(result).toBe("to out\nto err");
}); });
@ -45,6 +49,7 @@ describe("execCommandReturningOutput", () => {
"--eval", "--eval",
"console.error('to err'); console.log('to out')", "console.error('to err'); console.log('to out')",
]); ]);
expect(result).toBe("to out\nto err"); expect(result).toBe("to out\nto err");
}); });
}); });
@ -56,6 +61,7 @@ describe("execCommandSilent", () => {
test("silences stdout and stderr", async () => { test("silences stdout and stderr", async () => {
const consoleSpy = jest.spyOn(console, "log"); const consoleSpy = jest.spyOn(console, "log");
await utils.execCommandSilent(["node", "--eval", "console.log('test')"]); await utils.execCommandSilent(["node", "--eval", "console.log('test')"]);
expect(consoleSpy).not.toHaveBeenCalled(); expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore(); consoleSpy.mockRestore();

View File

@ -1,8 +1,6 @@
// @ts-ignore
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import * as Constants from "./constants"; import * as Constants from "./constants";
import childProcess from "child_process"; import childProcess from "child_process";
// @ts-ignore
import fs from "node:fs"; import fs from "node:fs";
import { ConnectionString } from "mongodb-connection-string-url"; import { ConnectionString } from "mongodb-connection-string-url";
@ -42,11 +40,13 @@ export async function start(apps) {
export function getDburl() { export function getDburl() {
let dbUrl = ""; let dbUrl = "";
try { try {
const env_array = fs const env_array = fs
.readFileSync(Constants.ENV_PATH, "utf8") .readFileSync(Constants.ENV_PATH, "utf8")
.toString() .toString()
.split("\n"); .split("\n");
for (const i in env_array) { for (const i in env_array) {
if ( if (
env_array[i].startsWith("APPSMITH_MONGODB_URI") || env_array[i].startsWith("APPSMITH_MONGODB_URI") ||
@ -61,14 +61,16 @@ export function getDburl() {
} }
const dbEnvUrl = const dbEnvUrl =
process.env.APPSMITH_DB_URL || process.env.APPSMITH_MONGO_DB_URI; process.env.APPSMITH_DB_URL || process.env.APPSMITH_MONGO_DB_URI;
// Make sure dbEnvUrl takes precedence over dbUrl // Make sure dbEnvUrl takes precedence over dbUrl
if (dbEnvUrl && dbEnvUrl !== "undefined") { if (dbEnvUrl && dbEnvUrl !== "undefined") {
dbUrl = dbEnvUrl.trim(); dbUrl = dbEnvUrl.trim();
} }
return dbUrl; return dbUrl;
} }
export function execCommand(cmd: string[], options?) { export async function execCommand(cmd: string[], options?) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
let isPromiseDone = false; let isPromiseDone = false;
@ -81,7 +83,9 @@ export function execCommand(cmd: string[], options?) {
if (isPromiseDone) { if (isPromiseDone) {
return; return;
} }
isPromiseDone = true; isPromiseDone = true;
if (code === 0) { if (code === 0) {
resolve(); resolve();
} else { } else {
@ -93,6 +97,7 @@ export function execCommand(cmd: string[], options?) {
if (isPromiseDone) { if (isPromiseDone) {
return; return;
} }
isPromiseDone = true; isPromiseDone = true;
console.error("Error running command", err); console.error("Error running command", err);
reject(); reject();
@ -100,7 +105,7 @@ export function execCommand(cmd: string[], options?) {
}); });
} }
export function execCommandReturningOutput(cmd, options?) { export async function execCommandReturningOutput(cmd, options?) {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
const p = childProcess.spawn(cmd[0], cmd.slice(1), options); const p = childProcess.spawn(cmd[0], cmd.slice(1), options);
@ -125,6 +130,7 @@ export function execCommandReturningOutput(cmd, options?) {
"\n" + "\n" +
errChunks.join("").trim() errChunks.join("").trim()
).trim(); ).trim();
if (code === 0) { if (code === 0) {
resolve(output); resolve(output);
} else { } else {
@ -137,6 +143,7 @@ export function execCommandReturningOutput(cmd, options?) {
export async function listLocalBackupFiles() { export async function listLocalBackupFiles() {
// Ascending order // Ascending order
const backupFiles = []; const backupFiles = [];
await fsPromises await fsPromises
.readdir(Constants.BACKUP_PATH) .readdir(Constants.BACKUP_PATH)
.then((filenames) => { .then((filenames) => {
@ -149,6 +156,7 @@ export async function listLocalBackupFiles() {
.catch((err) => { .catch((err) => {
console.log(err); console.log(err);
}); });
return backupFiles; return backupFiles;
} }
@ -160,6 +168,7 @@ export async function updateLastBackupErrorMailSentInMilliSec(ts) {
export async function getLastBackupErrorMailSentInMilliSec() { export async function getLastBackupErrorMailSentInMilliSec() {
try { try {
const ts = await fsPromises.readFile(Constants.LAST_ERROR_MAIL_TS, "utf8"); const ts = await fsPromises.readFile(Constants.LAST_ERROR_MAIL_TS, "utf8");
return parseInt(ts, 10); return parseInt(ts, 10);
} catch (error) { } catch (error) {
return 0; return 0;
@ -179,6 +188,7 @@ export function preprocessMongoDBURI(uri /* string */) {
const cs = new ConnectionString(uri); const cs = new ConnectionString(uri);
const params = cs.searchParams; const params = cs.searchParams;
params.set("appName", "appsmithctl"); params.set("appName", "appsmithctl");
if ( if (
@ -205,7 +215,7 @@ export function preprocessMongoDBURI(uri /* string */) {
return cs.toString(); return cs.toString();
} }
export function execCommandSilent(cmd, options?) { export async function execCommandSilent(cmd, options?) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
let isPromiseDone = false; let isPromiseDone = false;
@ -218,7 +228,9 @@ export function execCommandSilent(cmd, options?) {
if (isPromiseDone) { if (isPromiseDone) {
return; return;
} }
isPromiseDone = true; isPromiseDone = true;
if (code === 0) { if (code === 0) {
resolve(); resolve();
} else { } else {
@ -230,6 +242,7 @@ export function execCommandSilent(cmd, options?) {
if (isPromiseDone) { if (isPromiseDone) {
return; return;
} }
isPromiseDone = true; isPromiseDone = true;
reject(err); reject(err);
}); });
@ -238,5 +251,6 @@ export function execCommandSilent(cmd, options?) {
export function getDatabaseNameFromMongoURI(uri) { export function getDatabaseNameFromMongoURI(uri) {
const uriParts = uri.split("/"); const uriParts = uri.split("/");
return uriParts[uriParts.length - 1].split("?")[0]; return uriParts[uriParts.length - 1].split("?")[0];
} }

View File

@ -2,12 +2,14 @@ import * as utils from "./utils";
export async function exec() { export async function exec() {
let version = null; let version = null;
try { try {
version = await utils.getCurrentAppsmithVersion(); version = await utils.getCurrentAppsmithVersion();
} catch (err) { } catch (err) {
console.error("Error fetching current Appsmith version", err); console.error("Error fetching current Appsmith version", err);
process.exit(1); process.exit(1);
} }
if (version) { if (version) {
console.log(version); console.log(version);
} else { } else {

View File

@ -12820,6 +12820,7 @@ __metadata:
"@shared/ast": "workspace:^" "@shared/ast": "workspace:^"
"@types/express": ^4.17.14 "@types/express": ^4.17.14
"@types/jest": ^29.2.3 "@types/jest": ^29.2.3
"@types/node": "*"
"@types/nodemailer": ^6.4.17 "@types/nodemailer": ^6.4.17
"@types/readline-sync": ^1.4.8 "@types/readline-sync": ^1.4.8
axios: ^1.7.4 axios: ^1.7.4