create-backlist 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/bin/index.js +87 -0
- package/package.json +24 -0
- package/src/analyzer.js +60 -0
- package/src/generators/dotnet.js +52 -0
- package/src/generators/node.js +51 -0
- package/src/generators/template.js +15 -0
- package/src/templates/dotnet/partials/Controller.cs.ejs +24 -0
- package/src/templates/dotnet/partials/README.md.ejs +16 -0
- package/src/templates/node-ts-express/base/server.ts +15 -0
- package/src/templates/node-ts-express/base/tsconfig.json +10 -0
- package/src/templates/node-ts-express/partials/README.md.ejs +16 -0
- package/src/templates/node-ts-express/partials/package.json.ejs +23 -0
- package/src/templates/node-ts-express/partials/routes.ts.ejs +23 -0
- package/src/utils.js +14 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { isCommandAvailable } = require('../src/utils');
|
|
8
|
+
|
|
9
|
+
// Import generators
|
|
10
|
+
const { generateNodeProject } = require('../src/generators/node');
|
|
11
|
+
const { generateDotnetProject } = require('../src/generators/dotnet');
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
console.log(chalk.cyan.bold('š Welcome to Backlist! The Intelligent Backend Generator.'));
|
|
15
|
+
|
|
16
|
+
const answers = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'input',
|
|
19
|
+
name: 'projectName',
|
|
20
|
+
message: 'Enter a name for your backend directory:',
|
|
21
|
+
default: 'backend',
|
|
22
|
+
validate: input => input ? true : 'Project name cannot be empty.'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: 'list',
|
|
26
|
+
name: 'stack',
|
|
27
|
+
message: 'Select the backend stack:',
|
|
28
|
+
choices: [
|
|
29
|
+
{ name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
|
|
30
|
+
{ name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
|
|
31
|
+
new inquirer.Separator(),
|
|
32
|
+
{ name: 'Python (FastAPI) - Coming Soon', disabled: true },
|
|
33
|
+
{ name: 'Java (Spring Boot) - Coming Soon', disabled: true },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'input',
|
|
38
|
+
name: 'srcPath',
|
|
39
|
+
message: 'Enter the path to your frontend `src` directory:',
|
|
40
|
+
default: 'src',
|
|
41
|
+
}
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const options = {
|
|
45
|
+
...answers,
|
|
46
|
+
projectDir: path.resolve(process.cwd(), answers.projectName),
|
|
47
|
+
frontendSrcDir: path.resolve(process.cwd(), answers.srcPath),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
console.log(chalk.blue(`\n⨠Starting backend generation for: ${chalk.bold(options.stack)}`));
|
|
52
|
+
|
|
53
|
+
// --- Dispatcher Logic ---
|
|
54
|
+
switch (options.stack) {
|
|
55
|
+
case 'node-ts-express':
|
|
56
|
+
await generateNodeProject(options);
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case 'dotnet-webapi':
|
|
60
|
+
if (!await isCommandAvailable('dotnet')) {
|
|
61
|
+
throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
|
|
62
|
+
}
|
|
63
|
+
await generateDotnetProject(options);
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`The selected stack '${options.stack}' is not supported.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(chalk.green.bold('\nā
Backend generation complete!'));
|
|
71
|
+
console.log('\nNext Steps:');
|
|
72
|
+
console.log(chalk.cyan(` cd ${options.projectName}`));
|
|
73
|
+
console.log(chalk.cyan(' (Check the generated README.md for instructions)'));
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(chalk.red.bold('\nā An error occurred:'));
|
|
77
|
+
console.error(chalk.red(` ${error.message}`));
|
|
78
|
+
|
|
79
|
+
if (fs.existsSync(options.projectDir)) {
|
|
80
|
+
console.log(chalk.yellow(' -> Cleaning up failed installation...'));
|
|
81
|
+
fs.removeSync(options.projectDir);
|
|
82
|
+
}
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-backlist",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "An advanced, multi-language backend generator based on frontend analysis.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-backlist": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/index.js"
|
|
11
|
+
},
|
|
12
|
+
"author": "Your Name",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@babel/parser": "^7.22.7",
|
|
16
|
+
"@babel/traverse": "^7.22.8",
|
|
17
|
+
"chalk": "^4.1.2",
|
|
18
|
+
"ejs": "^3.1.9",
|
|
19
|
+
"execa": "^6.1.0",
|
|
20
|
+
"fs-extra": "^11.1.1",
|
|
21
|
+
"glob": "^10.3.3",
|
|
22
|
+
"inquirer": "^8.2.4"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/analyzer.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const { glob } = require('glob');
|
|
3
|
+
const parser = require('@babel/parser');
|
|
4
|
+
const traverse = require('@babel/traverse').default;
|
|
5
|
+
|
|
6
|
+
function toTitleCase(str) {
|
|
7
|
+
if (!str) return 'Default';
|
|
8
|
+
return str.replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())
|
|
9
|
+
.replace(/[^a-zA-Z0-9]/g, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function analyzeFrontend(srcPath) {
|
|
13
|
+
if (!fs.existsSync(srcPath)) {
|
|
14
|
+
throw new Error(`The source directory '${srcPath}' does not exist.`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const files = await glob(`${srcPath}/**/*.{js,ts,jsx,tsx}`, { ignore: 'node_modules/**' });
|
|
18
|
+
const endpoints = new Map();
|
|
19
|
+
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
const code = await fs.readFile(file, 'utf-8');
|
|
22
|
+
try {
|
|
23
|
+
const ast = parser.parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
|
|
24
|
+
traverse(ast, {
|
|
25
|
+
CallExpression(path) {
|
|
26
|
+
if (path.node.callee.name !== 'fetch') return;
|
|
27
|
+
const urlNode = path.node.arguments[0];
|
|
28
|
+
|
|
29
|
+
let urlValue;
|
|
30
|
+
if (urlNode.type === 'StringLiteral') {
|
|
31
|
+
urlValue = urlNode.value;
|
|
32
|
+
} else if (urlNode.type === 'TemplateLiteral' && urlNode.quasis.length > 0) {
|
|
33
|
+
urlValue = urlNode.quasis.map(q => q.value.raw).join('{id}');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!urlValue || !urlValue.startsWith('/api/')) return;
|
|
37
|
+
|
|
38
|
+
let method = 'GET';
|
|
39
|
+
|
|
40
|
+
const optionsNode = path.node.arguments[1];
|
|
41
|
+
if (optionsNode && optionsNode.type === 'ObjectExpression') {
|
|
42
|
+
const methodProp = optionsNode.properties.find(p => p.key.name === 'method');
|
|
43
|
+
if (methodProp && methodProp.value.type === 'StringLiteral') {
|
|
44
|
+
method = methodProp.value.value.toUpperCase();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const controllerName = toTitleCase(urlValue.split('/')[2]);
|
|
49
|
+
const key = `${method}:${urlValue}`;
|
|
50
|
+
if (!endpoints.has(key)) {
|
|
51
|
+
endpoints.set(key, { path: urlValue, method, controllerName });
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
} catch (e) { /* Ignore parsing errors */ }
|
|
56
|
+
}
|
|
57
|
+
return Array.from(endpoints.values());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { analyzeFrontend };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { execa } = require('execa');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { analyzeFrontend } = require('../analyzer');
|
|
6
|
+
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
|
+
|
|
8
|
+
async function generateDotnetProject(options) {
|
|
9
|
+
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
|
+
|
|
11
|
+
console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
|
|
12
|
+
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
13
|
+
|
|
14
|
+
const controllers = endpoints.reduce((acc, ep) => {
|
|
15
|
+
(acc[ep.controllerName] = acc[ep.controllerName] || []).push(ep);
|
|
16
|
+
return acc;
|
|
17
|
+
}, {});
|
|
18
|
+
|
|
19
|
+
if (Object.keys(controllers).length > 0) {
|
|
20
|
+
console.log(chalk.green(` -> Found endpoints for ${Object.keys(controllers).length} controllers.`));
|
|
21
|
+
} else {
|
|
22
|
+
console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(chalk.blue(' -> Scaffolding .NET Core Web API project...'));
|
|
26
|
+
await execa('dotnet', ['new', 'webapi', '-n', projectName, '-o', projectDir, '--no-https']);
|
|
27
|
+
|
|
28
|
+
await fs.remove(path.join(projectDir, 'Controllers', 'WeatherForecastController.cs'));
|
|
29
|
+
await fs.remove(path.join(projectDir, 'WeatherForecast.cs'));
|
|
30
|
+
|
|
31
|
+
console.log(chalk.blue(' -> Generating custom controllers...'));
|
|
32
|
+
for (const controllerName of Object.keys(controllers)) {
|
|
33
|
+
if (controllerName === 'Default') continue; // Skip if no proper controller name was found
|
|
34
|
+
await renderAndWrite(
|
|
35
|
+
getTemplatePath('dotnet/partials/Controller.cs.ejs'),
|
|
36
|
+
path.join(projectDir, 'Controllers', `${controllerName}Controller.cs`),
|
|
37
|
+
{
|
|
38
|
+
projectName,
|
|
39
|
+
controllerName,
|
|
40
|
+
endpoints: controllers[controllerName]
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await renderAndWrite(
|
|
46
|
+
getTemplatePath('dotnet/partials/README.md.ejs'),
|
|
47
|
+
path.join(projectDir, 'README.md'),
|
|
48
|
+
{ projectName }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { generateDotnetProject };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { execa } = require('execa');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { analyzeFrontend } = require('../analyzer');
|
|
6
|
+
const { renderAndWrite, getTemplatePath } = require('./template');
|
|
7
|
+
|
|
8
|
+
async function generateNodeProject(options) {
|
|
9
|
+
const { projectDir, projectName, frontendSrcDir } = options;
|
|
10
|
+
|
|
11
|
+
console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'));
|
|
12
|
+
const endpoints = await analyzeFrontend(frontendSrcDir);
|
|
13
|
+
if (endpoints.length > 0) {
|
|
14
|
+
console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
|
|
15
|
+
} else {
|
|
16
|
+
console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(chalk.blue(' -> Scaffolding Node.js (Express + TS) project...'));
|
|
20
|
+
|
|
21
|
+
const baseDir = getTemplatePath('node-ts-express/base');
|
|
22
|
+
await fs.copy(baseDir, projectDir);
|
|
23
|
+
|
|
24
|
+
await renderAndWrite(
|
|
25
|
+
getTemplatePath('node-ts-express/partials/package.json.ejs'),
|
|
26
|
+
path.join(projectDir, 'package.json'),
|
|
27
|
+
{ projectName }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
await renderAndWrite(
|
|
31
|
+
getTemplatePath('node-ts-express/partials/routes.ts.ejs'),
|
|
32
|
+
path.join(projectDir, 'src', 'routes.ts'),
|
|
33
|
+
{ endpoints }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const serverFilePath = path.join(projectDir, 'src', 'server.ts');
|
|
37
|
+
let serverFile = await fs.readFile(serverFilePath, 'utf-8');
|
|
38
|
+
serverFile = serverFile.replace('// INJECT:ROUTES', `import apiRoutes from './routes';\napp.use(apiRoutes);`);
|
|
39
|
+
await fs.writeFile(serverFilePath, serverFile);
|
|
40
|
+
|
|
41
|
+
console.log(chalk.magenta(' -> Installing dependencies (npm install)...'));
|
|
42
|
+
await execa('npm', ['install'], { cwd: projectDir });
|
|
43
|
+
|
|
44
|
+
await renderAndWrite(
|
|
45
|
+
getTemplatePath('node-ts-express/partials/README.md.ejs'),
|
|
46
|
+
path.join(projectDir, 'README.md'),
|
|
47
|
+
{ projectName }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { generateNodeProject };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const ejs = require('ejs');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
async function renderAndWrite(templatePath, destinationPath, data) {
|
|
6
|
+
const template = await fs.readFile(templatePath, 'utf-8');
|
|
7
|
+
const content = ejs.render(template, data);
|
|
8
|
+
await fs.outputFile(destinationPath, content);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getTemplatePath(subpath) {
|
|
12
|
+
return path.join(__dirname, '..', 'templates', subpath);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { renderAndWrite, getTemplatePath };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
using Microsoft.AspNetCore.Mvc;
|
|
2
|
+
|
|
3
|
+
namespace <%= projectName %>.Controllers;
|
|
4
|
+
|
|
5
|
+
[ApiController]
|
|
6
|
+
[Route("api/[controller]")]
|
|
7
|
+
public class <%= controllerName %>Controller : ControllerBase
|
|
8
|
+
{
|
|
9
|
+
// Endpoints for <%= controllerName %> auto-generated by Backlist
|
|
10
|
+
|
|
11
|
+
<% endpoints.forEach(endpoint => { %>
|
|
12
|
+
<%# Convert /api/users/{id} to just {id} for the route attribute %>
|
|
13
|
+
<% const routePath = endpoint.path.replace(`/api/${controllerName.toLowerCase()}`, '').substring(1); %>
|
|
14
|
+
/**
|
|
15
|
+
* <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>
|
|
16
|
+
*/
|
|
17
|
+
[Http<%= endpoint.method.charAt(0) + endpoint.method.slice(1).toLowerCase() %>("<%- routePath %>")]
|
|
18
|
+
public IActionResult AutoGenerated_<%= endpoint.method %>_<%= routePath.replace(/{|}/g, 'By_').replace(/[^a-zA-Z0-9_]/g, '') || 'Index' %>()
|
|
19
|
+
{
|
|
20
|
+
// TODO: Implement logic here. You can access route parameters like: public IActionResult Get(int id)
|
|
21
|
+
return Ok(new { message = "Auto-generated response for <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>" });
|
|
22
|
+
}
|
|
23
|
+
<% }); %>
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# <%= projectName %> (.NET Core Web API)
|
|
2
|
+
|
|
3
|
+
This backend was auto-generated by **Backlist**.
|
|
4
|
+
|
|
5
|
+
## š Getting Started
|
|
6
|
+
|
|
7
|
+
1. **Navigate to the directory:**
|
|
8
|
+
```bash
|
|
9
|
+
cd <%= projectName %>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. **Run the development server:**
|
|
13
|
+
```bash
|
|
14
|
+
dotnet watch run
|
|
15
|
+
```
|
|
16
|
+
The server will start on `http://localhost:5XXX`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
|
|
5
|
+
dotenv.config();
|
|
6
|
+
const app = express();
|
|
7
|
+
app.use(cors());
|
|
8
|
+
app.use(express.json());
|
|
9
|
+
|
|
10
|
+
app.get('/', (req, res) => res.send('Node.js Backend says Hello! Generated by Backlist.'));
|
|
11
|
+
|
|
12
|
+
// INJECT:ROUTES
|
|
13
|
+
|
|
14
|
+
const PORT = process.env.PORT || 8000;
|
|
15
|
+
app.listen(PORT, () => console.log(`ā”ļø Server running on http://localhost:${PORT}`));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# <%= projectName %> (Node.js + Express + TypeScript)
|
|
2
|
+
|
|
3
|
+
This backend was auto-generated by **Backlist**.
|
|
4
|
+
|
|
5
|
+
## š Getting Started
|
|
6
|
+
|
|
7
|
+
1. **Navigate to the directory:**
|
|
8
|
+
```bash
|
|
9
|
+
cd <%= projectName %>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. **Run the development server:**
|
|
13
|
+
```bash
|
|
14
|
+
npm run dev
|
|
15
|
+
```
|
|
16
|
+
The server will start on `http://localhost:8000`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= projectName %>",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "dist/server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"start": "node dist/server.js",
|
|
9
|
+
"dev": "ts-node-dev --respawn --transpile-only src/server.ts"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"cors": "^2.8.5",
|
|
13
|
+
"dotenv": "^16.3.1",
|
|
14
|
+
"express": "^4.18.2"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/cors": "^2.8.13",
|
|
18
|
+
"@types/express": "^4.17.17",
|
|
19
|
+
"@types/node": "^20.4.2",
|
|
20
|
+
"ts-node-dev": "^2.0.0",
|
|
21
|
+
"typescript": "^5.1.6"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Auto-generated by Backlist on <%= new Date().toISOString() %>
|
|
2
|
+
import { Router, Request, Response } from 'express';
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
<% if (endpoints.length === 0) { %>
|
|
7
|
+
// No API endpoints were detected in the frontend code. Add your routes here.
|
|
8
|
+
<% } else { %>
|
|
9
|
+
<% endpoints.forEach(endpoint => { %>
|
|
10
|
+
<%# Convert /api/users/{id} to /users/:id %>
|
|
11
|
+
<% const expressPath = endpoint.path.replace('/api', '').replace(/{(\w+)}/g, ':$1'); %>
|
|
12
|
+
/**
|
|
13
|
+
* Handler for <%= endpoint.method.toUpperCase() %> <%= endpoint.path %>
|
|
14
|
+
*/
|
|
15
|
+
router.<%= endpoint.method.toLowerCase() %>('<%- expressPath %>', (req: Request, res: Response) => {
|
|
16
|
+
console.log(`Request received for: ${req.method} ${req.path}`);
|
|
17
|
+
// TODO: Implement your logic here.
|
|
18
|
+
res.status(200).json({ message: 'Auto-generated response for <%= endpoint.path %>' });
|
|
19
|
+
});
|
|
20
|
+
<% }); %>
|
|
21
|
+
<% } %>
|
|
22
|
+
|
|
23
|
+
export default router;
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { execa } = require('execa');
|
|
2
|
+
|
|
3
|
+
async function isCommandAvailable(command) {
|
|
4
|
+
try {
|
|
5
|
+
// Using a harmless version command to check for presence
|
|
6
|
+
const checkCommand = command === 'java' ? '-version' : '--version';
|
|
7
|
+
await execa(command, [checkCommand]);
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = { isCommandAvailable };
|