create-backlist 6.0.6 → 6.0.7

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/backlist.js CHANGED
@@ -1,130 +1,132 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
3
 
3
- const inquirer = require('inquirer');
4
- const chalk = require('chalk');
5
- const fs = require('fs-extra');
6
- const path = require('path');
7
- const { Command } = require('commander');
8
- const chokidar = require('chokidar');
4
+ const inquirer = require("inquirer");
5
+ const chalk = require("chalk");
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+ const { Command } = require("commander");
9
+ const chokidar = require("chokidar");
9
10
 
10
- const { isCommandAvailable } = require('../src/utils');
11
+ // FIX: repo has utils.js at root
12
+ const { isCommandAvailable } = require("../utils");
11
13
 
12
- const { generateNodeProject } = require('../src/generators/node');
13
- const { generateDotnetProject } = require('../src/generators/dotnet');
14
- const { generateJavaProject } = require('../src/generators/java');
15
- const { generatePythonProject } = require('../src/generators/python');
14
+ const { generateNodeProject } = require("../src/generators/node");
15
+ const { generateDotnetProject } = require("../src/generators/dotnet");
16
+ const { generateJavaProject } = require("../src/generators/java");
17
+ const { generatePythonProject } = require("../src/generators/python");
16
18
 
17
- const { scanFrontend, writeContracts } = require('../src/scanner');
19
+ const { scanFrontend, writeContracts } = require("../src/scanner");
18
20
 
19
21
  function resolveOptionsFromFlags(flags) {
20
22
  return {
21
- projectName: flags.projectName || 'backend',
22
- srcPath: flags.srcPath || 'src',
23
- stack: flags.stack || 'node-ts-express',
23
+ projectName: flags.projectName || "backend",
24
+ srcPath: flags.srcPath || "src",
25
+ stack: flags.stack || "node-ts-express",
24
26
  dbType: flags.dbType,
25
27
  addAuth: flags.addAuth,
26
28
  addSeeder: flags.addSeeder,
27
29
  extraFeatures: flags.extraFeatures || [],
28
- projectDir: path.resolve(process.cwd(), flags.projectName || 'backend'),
29
- frontendSrcDir: path.resolve(process.cwd(), flags.srcPath || 'src'),
30
+ projectDir: path.resolve(process.cwd(), flags.projectName || "backend"),
31
+ frontendSrcDir: path.resolve(process.cwd(), flags.srcPath || "src"),
30
32
  };
31
33
  }
32
34
 
33
35
  async function runGeneration(options, contracts) {
34
36
  switch (options.stack) {
35
- case 'node-ts-express':
37
+ case "node-ts-express":
36
38
  await generateNodeProject({ ...options, contracts });
37
39
  break;
38
40
 
39
- case 'dotnet-webapi':
40
- if (!await isCommandAvailable('dotnet')) {
41
- throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
41
+ case "dotnet-webapi":
42
+ if (!(await isCommandAvailable("dotnet"))) {
43
+ throw new Error(".NET SDK is not installed. Install: https://dotnet.microsoft.com/download");
42
44
  }
43
45
  await generateDotnetProject({ ...options, contracts });
44
46
  break;
45
47
 
46
- case 'java-spring':
47
- if (!await isCommandAvailable('java')) {
48
- throw new Error('Java (JDK 17 or newer) is not installed. Please install a JDK to continue.');
48
+ case "java-spring":
49
+ if (!(await isCommandAvailable("java"))) {
50
+ throw new Error("Java (JDK 17+) is not installed. Install a JDK to continue.");
49
51
  }
50
52
  await generateJavaProject({ ...options, contracts });
51
53
  break;
52
54
 
53
- case 'python-fastapi':
54
- if (!await isCommandAvailable('python')) {
55
- throw new Error('Python is not installed. Please install Python (3.8+) and pip to continue.');
55
+ case "python-fastapi":
56
+ if (!(await isCommandAvailable("python"))) {
57
+ throw new Error("Python is not installed. Please install Python (3.8+) and pip.");
56
58
  }
57
59
  await generatePythonProject({ ...options, contracts });
58
60
  break;
59
61
 
60
62
  default:
61
- throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
63
+ throw new Error(`Unsupported stack '${options.stack}'.`);
62
64
  }
63
65
  }
64
66
 
65
67
  async function interactiveMain() {
66
- console.log(chalk.cyan.bold('Welcome to Backlist! The Polyglot Backend Generator.'));
68
+ console.log(chalk.cyan.bold("Welcome to Backlist! The Polyglot Backend Generator."));
67
69
 
68
70
  const answers = await inquirer.prompt([
69
71
  {
70
- type: 'input',
71
- name: 'projectName',
72
- message: 'Enter a name for your backend directory:',
73
- default: 'backend',
74
- validate: input => input ? true : 'Project name cannot be empty.'
72
+ type: "input",
73
+ name: "projectName",
74
+ message: "Enter a name for your backend directory:",
75
+ default: "backend",
76
+ validate: (input) => (input ? true : "Project name cannot be empty."),
75
77
  },
76
78
  {
77
- type: 'list',
78
- name: 'stack',
79
- message: 'Select the backend stack:',
79
+ type: "list",
80
+ name: "stack",
81
+ message: "Select the backend stack:",
80
82
  choices: [
81
- { name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
82
- { name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
83
- { name: 'Java (Spring Boot)', value: 'java-spring' },
84
- { name: 'Python (FastAPI)', value: 'python-fastapi' },
83
+ { name: "Node.js (TypeScript, Express)", value: "node-ts-express" },
84
+ { name: "C# (ASP.NET Core Web API)", value: "dotnet-webapi" },
85
+ { name: "Java (Spring Boot)", value: "java-spring" },
86
+ { name: "Python (FastAPI)", value: "python-fastapi" },
85
87
  ],
86
88
  },
87
89
  {
88
- type: 'input',
89
- name: 'srcPath',
90
- message: 'Enter the path to your frontend `src` directory:',
91
- default: 'src',
90
+ type: "input",
91
+ name: "srcPath",
92
+ message: "Enter the path to your frontend `src` directory:",
93
+ default: "src",
92
94
  },
93
95
  {
94
- type: 'list',
95
- name: 'dbType',
96
- message: 'Select your database type for Node.js:',
96
+ type: "list",
97
+ name: "dbType",
98
+ message: "Select your database type for Node.js:",
97
99
  choices: [
98
- { name: 'NoSQL (MongoDB with Mongoose)', value: 'mongoose' },
99
- { name: 'SQL (PostgreSQL/MySQL with Prisma)', value: 'prisma' },
100
+ { name: "NoSQL (MongoDB with Mongoose)", value: "mongoose" },
101
+ { name: "SQL (PostgreSQL/MySQL with Prisma)", value: "prisma" },
100
102
  ],
101
- when: (answers) => answers.stack === 'node-ts-express'
103
+ when: (a) => a.stack === "node-ts-express",
102
104
  },
103
105
  {
104
- type: 'confirm',
105
- name: 'addAuth',
106
- message: 'Add JWT authentication boilerplate?',
106
+ type: "confirm",
107
+ name: "addAuth",
108
+ message: "Add JWT authentication boilerplate?",
107
109
  default: true,
108
- when: (answers) => answers.stack === 'node-ts-express'
110
+ when: (a) => a.stack === "node-ts-express",
109
111
  },
110
112
  {
111
- type: 'confirm',
112
- name: 'addSeeder',
113
- message: 'Add a database seeder with sample data?',
113
+ type: "confirm",
114
+ name: "addSeeder",
115
+ message: "Add a database seeder with sample data?",
114
116
  default: true,
115
- when: (answers) => answers.stack === 'node-ts-express' && answers.addAuth
117
+ when: (a) => a.stack === "node-ts-express" && a.addAuth,
116
118
  },
117
119
  {
118
- type: 'checkbox',
119
- name: 'extraFeatures',
120
- message: 'Select additional features for Node.js:',
120
+ type: "checkbox",
121
+ name: "extraFeatures",
122
+ message: "Select additional features for Node.js:",
121
123
  choices: [
122
- { name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
123
- { name: 'API Testing Boilerplate (Jest & Supertest)', value: 'testing', checked: true },
124
- { name: 'API Documentation (Swagger UI)', value: 'swagger', checked: true },
124
+ { name: "Docker Support", value: "docker", checked: true },
125
+ { name: "API Testing Boilerplate (Jest & Supertest)", value: "testing", checked: true },
126
+ { name: "API Documentation (Swagger UI)", value: "swagger", checked: true },
125
127
  ],
126
- when: (answers) => answers.stack === 'node-ts-express'
127
- }
128
+ when: (a) => a.stack === "node-ts-express",
129
+ },
128
130
  ]);
129
131
 
130
132
  const options = {
@@ -139,16 +141,15 @@ async function interactiveMain() {
139
141
  console.log(chalk.blue(`\nStarting backend generation for: ${chalk.bold(options.stack)}`));
140
142
  await runGeneration(options, contracts);
141
143
 
142
- console.log(chalk.green.bold('\nBackend generation complete!'));
143
- console.log('\nNext Steps:');
144
+ console.log(chalk.green.bold("\nBackend generation complete!"));
145
+ console.log("\nNext Steps:");
144
146
  console.log(chalk.cyan(` cd ${options.projectName}`));
145
- console.log(chalk.cyan(' (Check the generated README.md for instructions)'));
146
147
  } catch (error) {
147
- console.error(chalk.red.bold('\nAn error occurred during generation:'));
148
+ console.error(chalk.red.bold("\nAn error occurred during generation:"));
148
149
  console.error(error);
149
150
 
150
151
  if (fs.existsSync(options.projectDir)) {
151
- console.log(chalk.yellow(' -> Cleaning up failed installation...'));
152
+ console.log(chalk.yellow(" -> Cleaning up failed installation..."));
152
153
  fs.removeSync(options.projectDir);
153
154
  }
154
155
  process.exit(1);
@@ -158,15 +159,13 @@ async function interactiveMain() {
158
159
  async function main() {
159
160
  const program = new Command();
160
161
 
162
+ program.name("backlist").description("Backlist CLI").version("5.1.0");
163
+
161
164
  program
162
- .name('backlist')
163
- .description('Backlist CLI - generate backend from frontend via AST scan')
164
- .version('1.0.0');
165
-
166
- program.command('scan')
167
- .description('Scan frontend and write contracts JSON')
168
- .option('-s, --srcPath <path>', 'frontend src path', 'src')
169
- .option('-o, --out <file>', 'output contracts file', '.backlist/contracts.json')
165
+ .command("scan")
166
+ .description("Scan frontend and write contracts JSON")
167
+ .option("-s, --srcPath <path>", "frontend src path", "src")
168
+ .option("-o, --out <file>", "output contracts file", ".backlist/contracts.json")
170
169
  .action(async (flags) => {
171
170
  const frontendSrcDir = path.resolve(process.cwd(), flags.srcPath);
172
171
  const outFile = path.resolve(process.cwd(), flags.out);
@@ -175,28 +174,31 @@ async function main() {
175
174
  console.log(chalk.green(`Wrote contracts to ${outFile}`));
176
175
  });
177
176
 
178
- program.command('generate')
179
- .description('Generate backend using contracts')
180
- .requiredOption('-k, --stack <stack>', 'stack: node-ts-express | dotnet-webapi | java-spring | python-fastapi')
181
- .option('-p, --projectName <name>', 'backend directory', 'backend')
182
- .option('-s, --srcPath <path>', 'frontend src path', 'src')
183
- .option('-c, --contracts <file>', 'contracts file', '.backlist/contracts.json')
177
+ program
178
+ .command("generate")
179
+ .description("Generate backend using contracts")
180
+ .requiredOption("-k, --stack <stack>", "stack: node-ts-express | dotnet-webapi | java-spring | python-fastapi")
181
+ .option("-p, --projectName <name>", "backend directory", "backend")
182
+ .option("-s, --srcPath <path>", "frontend src path", "src")
183
+ .option("-c, --contracts <file>", "contracts file", ".backlist/contracts.json")
184
184
  .action(async (flags) => {
185
185
  const options = resolveOptionsFromFlags(flags);
186
+
186
187
  const contractsPath = path.resolve(process.cwd(), flags.contracts);
187
188
  const contracts = fs.existsSync(contractsPath)
188
189
  ? await fs.readJson(contractsPath)
189
190
  : await scanFrontend({ frontendSrcDir: options.frontendSrcDir });
190
191
 
191
192
  await runGeneration(options, contracts);
192
- console.log(chalk.green('Generation complete.'));
193
+ console.log(chalk.green("Generation complete."));
193
194
  });
194
195
 
195
- program.command('watch')
196
- .description('Watch frontend and regenerate backend on changes')
197
- .requiredOption('-k, --stack <stack>', 'stack')
198
- .option('-p, --projectName <name>', 'backend directory', 'backend')
199
- .option('-s, --srcPath <path>', 'frontend src path', 'src')
196
+ program
197
+ .command("watch")
198
+ .description("Watch frontend and regenerate backend on changes")
199
+ .requiredOption("-k, --stack <stack>", "stack")
200
+ .option("-p, --projectName <name>", "backend directory", "backend")
201
+ .option("-s, --srcPath <path>", "frontend src path", "src")
200
202
  .action(async (flags) => {
201
203
  const options = resolveOptionsFromFlags(flags);
202
204
  const watcher = chokidar.watch(options.frontendSrcDir, { ignoreInitial: true });
@@ -208,11 +210,10 @@ async function main() {
208
210
  };
209
211
 
210
212
  await run();
211
- watcher.on('add', run).on('change', run).on('unlink', run);
213
+ watcher.on("add", run).on("change", run).on("unlink", run);
212
214
  console.log(chalk.cyan(`[watch] watching ${options.frontendSrcDir}`));
213
215
  });
214
216
 
215
- // If no args => old interactive mode
216
217
  if (process.argv.length <= 2) {
217
218
  await interactiveMain();
218
219
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "6.0.6",
3
+ "version": "6.0.7",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/src/analyzer.js CHANGED
@@ -8,9 +8,6 @@ const traverse = require("@babel/traverse").default;
8
8
 
9
9
  const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete"]);
10
10
 
11
- // -------------------------
12
- // Small utils
13
- // -------------------------
14
11
  function normalizeSlashes(p) {
15
12
  return String(p || "").replace(/\\/g, "/");
16
13
  }
@@ -23,9 +20,6 @@ function readJSONSafe(p) {
23
20
  }
24
21
  }
25
22
 
26
- // -------------------------
27
- // AUTH detection (for addAuth)
28
- // -------------------------
29
23
  function findAuthUsageInRepo(rootDir) {
30
24
  const pkgPath = path.join(rootDir, "package.json");
31
25
  const pkg = readJSONSafe(pkgPath) || {};
@@ -57,35 +51,26 @@ function findAuthUsageInRepo(rootDir) {
57
51
  "auth()",
58
52
  "currentUser",
59
53
  "createServerClient",
54
+ "jwt",
55
+ "bearer",
60
56
  ];
61
57
 
62
58
  for (const dir of scanDirs) {
63
59
  const files = glob.sync(`${normalizeSlashes(dir)}/**/*.{js,ts,jsx,tsx}`, {
64
- ignore: [
65
- "**/node_modules/**",
66
- "**/dist/**",
67
- "**/build/**",
68
- "**/.next/**",
69
- "**/coverage/**",
70
- ],
60
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**", "**/coverage/**"],
71
61
  });
72
62
 
73
63
  for (const f of files) {
74
64
  try {
75
65
  const content = fs.readFileSync(f, "utf8");
76
66
  if (patterns.some((p) => content.includes(p))) return true;
77
- } catch {
78
- // ignore read errors
79
- }
67
+ } catch {}
80
68
  }
81
69
  }
82
70
 
83
71
  return false;
84
72
  }
85
73
 
86
- // -------------------------
87
- // Helper functions for frontend API scan
88
- // -------------------------
89
74
  function toTitleCase(str) {
90
75
  if (!str) return "Default";
91
76
  return String(str)
@@ -126,9 +111,7 @@ function deriveControllerNameFromUrl(urlValue) {
126
111
  let seg = null;
127
112
  if (apiIndex >= 0) {
128
113
  seg = parts[apiIndex + 1] || null;
129
- if (seg && /^v\d+$/i.test(seg)) {
130
- seg = parts[apiIndex + 2] || seg;
131
- }
114
+ if (seg && /^v\d+$/i.test(seg)) seg = parts[apiIndex + 2] || seg;
132
115
  } else {
133
116
  seg = parts[0] || null;
134
117
  }
@@ -141,9 +124,6 @@ function deriveActionName(method, route) {
141
124
  return `${String(method).toLowerCase()}${toTitleCase(last)}`;
142
125
  }
143
126
 
144
- // -------------------------
145
- // Axios/Fetch detection
146
- // -------------------------
147
127
  function detectAxiosLikeMethod(node) {
148
128
  if (!node.callee || node.callee.type !== "MemberExpression") return null;
149
129
  const prop = node.callee.property;
@@ -172,16 +152,19 @@ function getUrlValue(urlNode) {
172
152
  return null;
173
153
  }
174
154
 
175
- // -------------------------
176
- // Extract request schema (simple)
177
155
  function inferTypeFromNode(node) {
178
156
  if (!node) return "String";
179
157
  switch (node.type) {
180
- case "StringLiteral": return "String";
181
- case "NumericLiteral": return "Number";
182
- case "BooleanLiteral": return "Boolean";
183
- case "NullLiteral": return "String";
184
- default: return "String";
158
+ case "StringLiteral":
159
+ return "String";
160
+ case "NumericLiteral":
161
+ return "Number";
162
+ case "BooleanLiteral":
163
+ return "Boolean";
164
+ case "NullLiteral":
165
+ return "String";
166
+ default:
167
+ return "String";
185
168
  }
186
169
  }
187
170
 
@@ -190,7 +173,13 @@ function extractObjectSchema(objExpr) {
190
173
  if (!objExpr || objExpr.type !== "ObjectExpression") return null;
191
174
  for (const prop of objExpr.properties) {
192
175
  if (prop.type !== "ObjectProperty") continue;
193
- const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.value;
176
+ const key =
177
+ prop.key.type === "Identifier"
178
+ ? prop.key.name
179
+ : prop.key.type === "StringLiteral"
180
+ ? prop.key.value
181
+ : null;
182
+ if (!key) continue;
194
183
  schema[key] = inferTypeFromNode(prop.value);
195
184
  }
196
185
  return schema;
@@ -209,9 +198,6 @@ function resolveIdentifierToInit(pathObj, identifierName) {
209
198
  }
210
199
  }
211
200
 
212
- // -------------------------
213
- // Main frontend scanner
214
- // -------------------------
215
201
  async function analyzeFrontend(srcPath) {
216
202
  if (!srcPath) throw new Error("analyzeFrontend: srcPath is required");
217
203
  if (!fs.existsSync(srcPath)) throw new Error(`Source dir '${srcPath}' does not exist`);
@@ -224,13 +210,23 @@ async function analyzeFrontend(srcPath) {
224
210
 
225
211
  for (const file of files) {
226
212
  let code;
227
- try { code = await fs.readFile(file, "utf-8"); } catch { continue; }
213
+ try {
214
+ code = await fs.readFile(file, "utf-8");
215
+ } catch {
216
+ continue;
217
+ }
218
+
228
219
  let ast;
229
- try { ast = parser.parse(code, { sourceType: "module", plugins: ["jsx","typescript"] }); } catch { continue; }
220
+ try {
221
+ ast = parser.parse(code, { sourceType: "module", plugins: ["jsx", "typescript"] });
222
+ } catch {
223
+ continue;
224
+ }
230
225
 
231
226
  traverse(ast, {
232
227
  CallExpression(callPath) {
233
228
  const node = callPath.node;
229
+
234
230
  const isFetch = node.callee.type === "Identifier" && node.callee.name === "fetch";
235
231
  const axiosMethod = detectAxiosLikeMethod(node);
236
232
  if (!isFetch && !axiosMethod) return;
@@ -242,19 +238,33 @@ async function analyzeFrontend(srcPath) {
242
238
  if (isFetch) {
243
239
  urlValue = getUrlValue(node.arguments[0]);
244
240
  const optionsNode = node.arguments[1];
241
+
245
242
  if (optionsNode?.type === "ObjectExpression") {
246
- const mProp = optionsNode.properties.find(p => p.key?.name==="method");
247
- if (mProp?.value?.type==="StringLiteral") method = mProp.value.value.toUpperCase();
248
- if (["POST","PUT","PATCH"].includes(method)) {
249
- const bProp = optionsNode.properties.find(p => p.key?.name==="body");
243
+ const mProp = optionsNode.properties.find(
244
+ (p) => p.type === "ObjectProperty" && p.key?.type === "Identifier" && p.key.name === "method"
245
+ );
246
+ if (mProp?.value?.type === "StringLiteral") method = mProp.value.value.toUpperCase();
247
+
248
+ if (["POST", "PUT", "PATCH"].includes(method)) {
249
+ const bProp = optionsNode.properties.find(
250
+ (p) => p.type === "ObjectProperty" && p.key?.type === "Identifier" && p.key.name === "body"
251
+ );
252
+
250
253
  if (bProp) {
251
254
  const v = bProp.value;
252
- if (v.type==="CallExpression" && v.callee.object?.name==="JSON" && v.callee.property?.name==="stringify") {
255
+ if (
256
+ v.type === "CallExpression" &&
257
+ v.callee.type === "MemberExpression" &&
258
+ v.callee.object?.type === "Identifier" &&
259
+ v.callee.object.name === "JSON" &&
260
+ v.callee.property?.type === "Identifier" &&
261
+ v.callee.property.name === "stringify"
262
+ ) {
253
263
  const arg0 = v.arguments[0];
254
- if (arg0?.type==="ObjectExpression") schemaFields = extractObjectSchema(arg0);
255
- else if (arg0?.type==="Identifier") {
264
+ if (arg0?.type === "ObjectExpression") schemaFields = extractObjectSchema(arg0);
265
+ else if (arg0?.type === "Identifier") {
256
266
  const init = resolveIdentifierToInit(callPath, arg0.name);
257
- if (init?.type==="ObjectExpression") schemaFields = extractObjectSchema(init);
267
+ if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
258
268
  }
259
269
  }
260
270
  }
@@ -265,12 +275,13 @@ async function analyzeFrontend(srcPath) {
265
275
  if (axiosMethod) {
266
276
  method = axiosMethod;
267
277
  urlValue = getUrlValue(node.arguments[0]);
268
- if (["POST","PUT","PATCH"].includes(method)) {
278
+
279
+ if (["POST", "PUT", "PATCH"].includes(method)) {
269
280
  const dataArg = node.arguments[1];
270
- if (dataArg?.type==="ObjectExpression") schemaFields = extractObjectSchema(dataArg);
271
- else if (dataArg?.type==="Identifier") {
281
+ if (dataArg?.type === "ObjectExpression") schemaFields = extractObjectSchema(dataArg);
282
+ else if (dataArg?.type === "Identifier") {
272
283
  const init = resolveIdentifierToInit(callPath, dataArg.name);
273
- if (init?.type==="ObjectExpression") schemaFields = extractObjectSchema(init);
284
+ if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
274
285
  }
275
286
  }
276
287
  }
@@ -292,37 +303,34 @@ async function analyzeFrontend(srcPath) {
292
303
  actionName,
293
304
  pathParams: extractPathParams(route),
294
305
  queryParams: extractQueryParamsFromUrl(apiPath),
306
+ schemaFields,
295
307
  requestBody: schemaFields ? { fields: schemaFields } : null,
296
308
  sourceFile: normalizeSlashes(file),
297
309
  });
298
310
  }
299
- }
311
+ },
300
312
  });
301
313
  }
302
314
 
303
315
  return Array.from(endpoints.values());
304
316
  }
305
317
 
306
- // -------------------------
307
- // Main analyze() for CLI
308
- // -------------------------
309
318
  async function analyze(projectRoot = process.cwd()) {
310
319
  const rootDir = path.resolve(projectRoot);
320
+
311
321
  const frontendSrc = ["src", "app", "pages"]
312
- .map(d => path.join(rootDir,d))
313
- .find(d => fs.existsSync(d));
322
+ .map((d) => path.join(rootDir, d))
323
+ .find((d) => fs.existsSync(d));
314
324
 
315
325
  const endpoints = frontendSrc ? await analyzeFrontend(frontendSrc) : [];
326
+ const hasAuth = findAuthUsageInRepo(rootDir);
316
327
 
317
328
  return {
318
329
  rootDir: normalizeSlashes(rootDir),
319
- hasAuth: findAuthUsageInRepo(rootDir),
320
- addAuth: findAuthUsageInRepo(rootDir),
321
- endpoints
330
+ hasAuth,
331
+ addAuth: hasAuth,
332
+ endpoints,
322
333
  };
323
334
  }
324
335
 
325
- module.exports = {
326
- analyze,
327
- analyzeFrontend
328
- };
336
+ module.exports = { analyze, analyzeFrontend };
package/src/utils.js CHANGED
@@ -1,27 +1,24 @@
1
- const { execa } = require('execa');
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
+ const { execa } = require("execa");
2
3
 
3
4
  const VERSION_ARGS = {
4
- java: ['-version'],
5
- python: ['--version'],
6
- python3: ['--version'],
7
- node: ['--version'],
8
- npm: ['--version'],
9
- dotnet: ['--version'],
10
- mvn: ['-v'],
11
- git: ['--version'],
5
+ java: ["-version"],
6
+ python: ["--version"],
7
+ python3: ["--version"],
8
+ node: ["--version"],
9
+ npm: ["--version"],
10
+ dotnet: ["--version"],
11
+ mvn: ["-v"],
12
+ git: ["--version"],
12
13
  };
13
14
 
14
15
  async function isCommandAvailable(command) {
15
- const args = VERSION_ARGS[command] || ['--version'];
16
-
16
+ const args = VERSION_ARGS[command] || ["--version"];
17
17
  try {
18
- // Reject false only if spawn fails (ENOENT). Non-zero exit still counts as "available".
19
18
  await execa(command, args, { reject: false });
20
19
  return true;
21
20
  } catch (err) {
22
- // If command not found, execa throws with code 'ENOENT'
23
- if (err && err.code === 'ENOENT') return false;
24
- // Other unexpected errors: treat as not available
21
+ if (err && err.code === "ENOENT") return false;
25
22
  return false;
26
23
  }
27
24
  }