Skip to content

Commit bd9d2dd

Browse files
committed
feat: add deletion command to remove 1 or all resources
1 parent 3666586 commit bd9d2dd

File tree

4 files changed

+303
-20
lines changed

4 files changed

+303
-20
lines changed

.suite-cli/cli/cli.js

+46-17
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ const { cwd } = require('node:process');
88
const { readFileSync } = require('node:fs');
99
const path = require('node:path');
1010
const actionHandlers = require('./scripts')
11-
const { logInfo, getExistingComponent, getExistingApps, getNextAvailablePort, scaffoldApp, scaffoldGateways,readFileContent } = require('./scripts/scripts.module');
11+
const { logInfo, getExistingComponent, getExistingApps, getNextAvailablePort, scaffoldApp, scaffoldGateways, readFileContent } = require('./scripts/scripts.module');
1212

1313
const program = new Command();
1414
const packageJsonPath = path.join(__dirname, 'package.json');
15-
const packageJSON = JSON.parse(readFileSync(packageJsonPath,{'encoding':'utf8'}));
15+
const packageJSON = JSON.parse(readFileSync(packageJsonPath, { 'encoding': 'utf8' }));
1616
program.version(packageJSON.version); //get library version set by release script
1717
const prompt = createPromptModule();
1818
program
@@ -131,9 +131,9 @@ program
131131
.then(answers => {
132132
let existing_services = []
133133
try {
134-
existing_services = getExistingComponent({ key: 'services', currentDir: cwd() })
134+
existing_services = getExistingComponent({ key: 'services', currentDir: cwd() })
135135
} catch (error) {
136-
136+
137137
}
138138
switch (answers.resource) {
139139
case 'monorepo':
@@ -242,7 +242,7 @@ program
242242
]).then((answers) => actionHandlers.scaffoldNewLibrary({ answers: { ...answers, private: false } }))
243243
break
244244
case 'app':
245-
const existing_apps = getExistingComponent({ key: 'apps', currentDir: cwd() })||[]
245+
const existing_apps = getExistingComponent({ key: 'apps', currentDir: cwd() }) || []
246246
const formatServiceName = (service) => `${service.name}: ${service.port}`;
247247
prompt([
248248
{
@@ -306,7 +306,7 @@ program
306306
break;
307307
case 'gateway':
308308
const apps = getExistingApps({ currentDir: cwd() });
309-
if(!apps) {
309+
if (!apps) {
310310
logInfo({ message: `No apps found in this project. You need to have atleast one app to generate a gateway` })
311311
ora().info(`Run 'suite generate' to create one...`)
312312
process.exit(0);
@@ -362,16 +362,45 @@ program
362362
});
363363
});
364364
program
365-
.command('remove')
366-
.description('Clean remove a monorepo resource plus all the associated files. No residual files remain behind')
367-
.option('service', 'remove service and associated files')
368-
.option('app', 'remove app and associated files')
369-
.option('library', 'remove library and associated files')
370-
.option('microservice', 'remove microservice and associated files')
371-
.option('gateway', 'remove gateway and associated files')
372-
.action(async (options) => {
373-
console.log({options})
374-
await actionHandlers.dockerPrune({ options })
375-
});
365+
.command('remove <resource> [resource_name]')
366+
.description('Clean remove a monorepo resource or all resources of a specific type with the --all flag.')
367+
.option('-f, --force', 'Force removal without confirmation')
368+
.option('--all', 'Remove all resources of the specified type') // Add --all flag
369+
.action(async (resource, resource_name, options) => {
370+
const spinner = ora();
371+
372+
// Validate --all flag usage
373+
if (!resource_name && !options.all) {
374+
spinner.fail('You must provide either a resource name or the --all flag to remove all resources.');
375+
return;
376+
}
377+
378+
try {
379+
// Confirm the deletion if --force is not provided
380+
if (!options.force) {
381+
const { confirmRemoval } = await prompt([
382+
{
383+
type: 'confirm',
384+
name: 'confirmRemoval',
385+
message: `Are you sure you want to remove ${options.all ? `all ${resource}s` : `${resource} "${resource_name}"`} ?`,
386+
default: false
387+
}
388+
]);
389+
390+
if (!confirmRemoval) {
391+
spinner.info('Operation cancelled.');
392+
return;
393+
}
394+
}
395+
396+
// Call the resource removal handler
397+
await actionHandlers.removeResource({ answers: { resource, resource_name, options } });
398+
399+
} catch (error) {
400+
spinner.fail(`Failed to remove ${resource} ${resource_name || 'resources'}: ${error.message}`);
401+
}
402+
});
403+
404+
376405
program.parse(process.argv);
377406
module.exports = program
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
const { removeResource, logError } = require('../scripts.module')
3+
4+
module.exports = async ({ answers }) => {
5+
try {
6+
await removeResource({ answers });
7+
} catch (error) {
8+
logError({ error })
9+
}
10+
}

.suite-cli/cli/scripts/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ module.exports.releasePackage = require('./commands/releasePackage.cmd');
2323
module.exports.scaffoldNewService = require('./commands/scaffoldNewService.cmd');
2424
module.exports.scaffoldNewLibrary = require('./commands/scaffoldNewLibrary.cmd');
2525
module.exports.test = require('./commands/test.cmd');
26+
module.exports.removeResource = require('./commands/removeResource.cmd');

.suite-cli/cli/scripts/scripts.module.js

+246-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
const chalk = require('chalk')
22
const { join, sep, resolve } = require('node:path')
3+
const fs = require('fs');
4+
const path = require('node:path');
35
const os = require('os')
46
const { mkdirSync, readFile } = require('fs')
57
const { cwd, chdir, exit, platform } = require('node:process')
6-
const { existsSync, statSync, readdirSync, writeFileSync, readFileSync, rmSync } = require('node:fs');
8+
const { existsSync, statSync, readdirSync, writeFileSync, readFileSync, rmSync, rm } = require('node:fs');
79
let { exec, spawn } = require('node:child_process');
810
const { writeFile } = require('node:fs/promises');
911
const assets = require('./assets')
@@ -1643,7 +1645,7 @@ const scaffoldGateways = async ({ answers }) => {
16431645
const { projectName } = readFileContent({ currentDir: cwd() });
16441646
let { apps } = answers;
16451647
const project_root = generatRootPath({ currentDir: cwd() });
1646-
const service_objects = getExistingComponent({ key:'services', currentDir: cwd() });
1648+
const service_objects = getExistingComponent({ key: 'services', currentDir: cwd() });
16471649
// add port to services in each app eg ['auth']=>[{name:'auth',port:9001}]
16481650
apps = apps.map((app) => {
16491651
app.services.map((name, i) => {
@@ -1705,6 +1707,244 @@ const scaffoldGateway = ({ project_root, app, answers, webserver, projectName })
17051707

17061708
}
17071709

1710+
/**
1711+
* Removes a microservice and all associated files.
1712+
* @param {Object} options - Options for removing the microservice.
1713+
* @param {string} options.project_root - The root directory of the project.
1714+
* @param {string} options.service_name - The name of the microservice to be removed.
1715+
* @param {boolean} [options.sync] - If true, use synchronous file removal.
1716+
*/
1717+
const removeResource = async ({ answers }) => {
1718+
const { resource, resource_name, options } = answers;
1719+
let project_root;
1720+
1721+
try {
1722+
project_root = generatRootPath({ currentDir: cwd() });
1723+
} catch (error) {
1724+
// Not within a suite repo
1725+
if (error.message && error.message === 'suite.json and(or) .git not found') {
1726+
ora('This does not look like a suite repo').warn()
1727+
ora().info('If it is run <suite init> from project root to reinitialize suite project and try again')
1728+
exit(1)
1729+
}
1730+
else {
1731+
// rethrow the error
1732+
throw new Error('Error code 10005.Kindly raise an issue at https://github.com/microservices-suite/node-microservices-suite/issues')
1733+
}
1734+
}
1735+
const spinner = ora();
1736+
if (options.all) {
1737+
// Logic to remove all resources of the given type
1738+
1739+
const resourceEnum = {
1740+
library: ['shared'],
1741+
app: ['gateways', 'apps'],
1742+
service: ['microservices'],
1743+
gateway: ['gateways', 'apps']
1744+
}
1745+
if(!resourceEnum[resource]) return ora().warn(`Unkown resource name ${resource}`)
1746+
const allResourceChildrenPath = path.join(project_root, ...(resourceEnum[resource]));
1747+
if (!fs.existsSync(allResourceChildrenPath)) {
1748+
spinner.warn(`No ${resource}(s) found.`);
1749+
return;
1750+
}
1751+
1752+
// Remove all resources
1753+
const services = fs.readdirSync(allResourceChildrenPath);
1754+
if (services.length === 0) {
1755+
spinner.warn(`No ${resource}(s) found.`);
1756+
return;
1757+
}
1758+
1759+
// Synchronous removal of all services
1760+
services.forEach(service => {
1761+
const fullPath = path.join(allResourceChildrenPath, service);
1762+
fs.rmSync(fullPath, { recursive: true, force: true });
1763+
});
1764+
spinner.succeed(`All ${resource}(s) have been removed.`);
1765+
1766+
return;
1767+
}
1768+
1769+
// Call the appropriate action handler based on the resource type
1770+
switch (resource) {
1771+
case 'service':
1772+
await removeService({ service_name: resource_name, project_root, sync: true });
1773+
break;
1774+
case 'app':
1775+
await removeApp({ app_name: resource_name, project_root, sync: true });
1776+
break;
1777+
case 'library':
1778+
await removeLibrary({ library_name: resource_name, project_root, sync: true });
1779+
break;
1780+
case 'gateway':
1781+
await removeGateway({ gateway_name: resource_name, project_root, sync: true });
1782+
break;
1783+
default:
1784+
throw new Error(`Unknown resource type: ${resource}`);
1785+
}
1786+
1787+
};
1788+
/**
1789+
* Removes a microservice and all associated files.
1790+
* @param {Object} options - Options for removing the microservice.
1791+
* @param {string} options.project_root - The root directory of the project.
1792+
* @param {string} options.service_name - The name of the microservice to be removed.
1793+
* @param {boolean} [options.sync] - If true, use synchronous file removal.
1794+
* @param {boolean} [options.all] - If true, remove all services of the specified type.
1795+
*/
1796+
const removeService = async ({ service_name, project_root, sync }) => {
1797+
const servicePath = path.join(project_root, 'microservices', service_name);
1798+
const spinner = ora(`Removing microservice "${service_name}"...`).start();
1799+
1800+
try {
1801+
// Check if the specific service exists
1802+
if (!fs.existsSync(servicePath)) {
1803+
spinner.warn(`Microservice "${service_name}" not found.`);
1804+
return;
1805+
}
1806+
1807+
if (sync) {
1808+
// Synchronous removal of the specific service
1809+
fs.rmSync(servicePath, { recursive: true, force: true });
1810+
spinner.succeed(`Microservice "${service_name}" and all associated files have been removed.`);
1811+
} else {
1812+
// Asynchronous removal of the specific service
1813+
fs.rm(servicePath, { recursive: true, force: true }, (err) => {
1814+
if (err) throw err;
1815+
spinner.succeed(`Microservice "${service_name}" and all associated files have been removed.`);
1816+
});
1817+
}
1818+
} catch (error) {
1819+
spinner.fail(`Error while removing microservice "${service_name}": ${error.message}`);
1820+
}
1821+
};
1822+
1823+
1824+
/**
1825+
* Removes a microservice and all associated files.
1826+
* @param {Object} options - Options for removing the microservice.
1827+
* @param {string} options.project_root - The root directory of the project.
1828+
* @param {string} options.app_name - The name of the microapp to be removed.
1829+
* @param {boolean} [options.sync] - If true, use synchronous file removal.
1830+
*/
1831+
/**
1832+
* Removes an app from both Docker and Kubernetes directories.
1833+
* @param {Object} options - Options for removing the app.
1834+
* @param {string} options.app_name - The name of the app to be removed.
1835+
* @param {string} options.project_root - The root directory of the project.
1836+
* @param {boolean} [options.sync] - If true, use synchronous file removal.
1837+
*/
1838+
const removeApp = async ({ app_name, project_root, sync = true }) => {
1839+
const appDockerPath = path.join(project_root, 'gateways', 'apps', app_name);
1840+
const appKubePath = path.join(project_root, 'k8s', app_name);
1841+
1842+
// Check if the app exists in either Docker or Kubernetes directories
1843+
const dockerExists = existsSync(appDockerPath);
1844+
const kubeExists = existsSync(appKubePath);
1845+
1846+
if (!dockerExists && !kubeExists) {
1847+
ora().warn(`App "${app_name}" not found in Docker or Kubernetes.`);
1848+
return;
1849+
}
1850+
1851+
try {
1852+
if (sync) {
1853+
// Use synchronous removal
1854+
if (dockerExists) {
1855+
rmSync(appDockerPath, { recursive: true, force: true });
1856+
ora().info(`Docker files for app "${app_name}" have been removed.`);
1857+
}
1858+
1859+
if (kubeExists) {
1860+
rmSync(appKubePath, { recursive: true, force: true });
1861+
ora().info(`Kubernetes files for app "${app_name}" have been removed.`);
1862+
}
1863+
} else {
1864+
// Use asynchronous removal
1865+
if (dockerExists) {
1866+
rm(appDockerPath, { recursive: true, force: true }, (err) => {
1867+
if (err) throw err;
1868+
ora().info(`Docker files for app "${app_name}" have been removed.`);
1869+
});
1870+
}
1871+
1872+
if (kubeExists) {
1873+
rm(appKubePath, { recursive: true, force: true }, (err) => {
1874+
if (err) throw err;
1875+
ora().info(`Kubernetes files for app "${app_name}" have been removed.`);
1876+
});
1877+
}
1878+
}
1879+
1880+
ora().succeed(`App "${app_name}" removed successfully.`);
1881+
} catch (error) {
1882+
ora().fail(`Error while removing app "${app_name}": ${error.message}`);
1883+
}
1884+
};
1885+
1886+
/**
1887+
* Removes a microservice and all associated files.
1888+
* @param {Object} options - Options for removing the microservice.
1889+
* @param {string} options.project_root - The root directory of the project.
1890+
* @param {string} options.library_name - The name of the microlibrary to be removed.
1891+
* @param {boolean} [options.sync] - If true, use synchronous file removal.
1892+
*/
1893+
const removeLibrary = async ({ library_name, project_root, sync }) => {
1894+
const libraryPath = path.join(project_root, 'shared', library_name);
1895+
// Check if the microservice directory exists
1896+
if (!existsSync(libraryPath)) {
1897+
ora().warn(`Library "${library_name}" not found.`);
1898+
return;
1899+
}
1900+
try {
1901+
if (sync) {
1902+
// Use synchronous removal
1903+
rmSync(libraryPath, { recursive: true, force: true });
1904+
ora().info(`Library "${library_name}" and all associated files have been removed.`);
1905+
} else {
1906+
// Use asynchronous removal
1907+
rm(libraryPath, { recursive: true, force: true }, (err) => {
1908+
if (err) throw err;
1909+
ora().info(`Library "${library_name}" and all associated files have been removed.`);
1910+
});
1911+
}
1912+
ora().succeed(`Library "${library_name}" removed successfully.`);
1913+
} catch (error) {
1914+
ora().fail(`Error while removing Library "${library_name}":`, error.message);
1915+
}
1916+
};
1917+
1918+
/**
1919+
* Removes a microservice and all associated files.
1920+
* @param {Object} options - Options for removing the microservice.
1921+
* @param {string} options.project_root - The root directory of the project.
1922+
* @param {string} options.gateway - The name of the microgateway to be removed.
1923+
* @param {boolean} [options.sync] - If true, use synchronous file removal.
1924+
*/
1925+
const removeGateway = async ({ gateway, project_root, sync }) => {
1926+
const gatewayPath = path.join(project_root, 'apps', gateway);
1927+
if (!existsSync(gatewayPath)) {
1928+
ora().warn(`Gateway "${gateway}" not found.`);
1929+
return;
1930+
}
1931+
try {
1932+
if (sync) {
1933+
// Use synchronous removal
1934+
rmSync(gatewayPath, { recursive: true, force: true });
1935+
ora().info(`Gateway "${gateway}" and all associated files have been removed.`);
1936+
} else {
1937+
// Use asynchronous removal
1938+
rm(gatewayPath, { recursive: true, force: true }, (err) => {
1939+
if (err) throw err;
1940+
ora().info(`Gateway "${gateway}" and all associated files have been removed.`);
1941+
});
1942+
}
1943+
ora().succeed(`Gateway "${gateway}" removed successfully.`);
1944+
} catch (error) {
1945+
ora().fail(`Error while removing Gateway "${gateway}":`, error.message);
1946+
}
1947+
};
17081948
module.exports = {
17091949
generateDirectoryPath,
17101950
changeDirectory,
@@ -1737,5 +1977,8 @@ module.exports = {
17371977
scaffoldApp,
17381978
scaffoldGateways,
17391979
getExistingApps,
1740-
readFileContent
1980+
readFileContent,
1981+
removeResource,
1982+
removeService,
1983+
removeApp
17411984
}

0 commit comments

Comments
 (0)