explainthisrepo 0.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/README.md +87 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +117 -0
- package/dist/generate.d.ts +1 -0
- package/dist/generate.js +36 -0
- package/dist/github.d.ts +2 -0
- package/dist/github.js +97 -0
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.js +43 -0
- package/dist/writer.d.ts +1 -0
- package/dist/writer.js +6 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# ExplainThisRepo
|
|
2
|
+
|
|
3
|
+
This folder contains a high-performance, lightweight port of the ExplainThisRepo CLI tool.
|
|
4
|
+
While the original Python implementation is the primary version of this tool, this Node.js port was created to provide a "zero-compilation" experience for users where Python C-extensions (like Pillow) can be difficult to build.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
- **Automated Repository Analysis**: Seamlessly fetches repository data and documentation via the GitHub REST API.
|
|
8
|
+
- **AI-Driven Contextualization**: Uses Google Gemini 2.5 Flash Lite to extract technical value and purpose from codebases.
|
|
9
|
+
- **Senior Engineer Perspective**: Generates explanations tailored for developers, focusing on architecture, target audience, and execution.
|
|
10
|
+
- **Markdown Generation**: Automatically outputs a clean, structured `EXPLAIN.md` file for immediate reading.
|
|
11
|
+
- **TypeScript Core**: Built with type safety and modern asynchronous patterns for reliable performance.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Follow these steps to set up the project locally on your machine:
|
|
16
|
+
|
|
17
|
+
1. **Clone the Repository**
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/Spectra010s/ExplainThisRepo.git
|
|
20
|
+
cd ExplainThisRepo/node_version
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. **Install Dependencies**
|
|
24
|
+
```bash
|
|
25
|
+
npm install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
3. **Set Up Environment Variables**
|
|
29
|
+
Create a `.env` file in the root directory or export the variable directly in your terminal:
|
|
30
|
+
```bash
|
|
31
|
+
export GEMINI_API_KEY=your_actual_api_key_here
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
4. **Build the Project**
|
|
35
|
+
Compile the TypeScript source code into executable JavaScript:
|
|
36
|
+
```bash
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
5 **Link the command globally:**
|
|
40
|
+
```bash
|
|
41
|
+
npm link
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
Once the project is built, you can use the CLI tool to analyze any public GitHub repository.
|
|
48
|
+
|
|
49
|
+
### Running the CLI
|
|
50
|
+
Execute the tool by passing the `owner/repo` string as an argument:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node dist/cli.js facebook/react
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Expected Workflow
|
|
57
|
+
1. The tool fetches the repository description and README from GitHub.
|
|
58
|
+
2. It processes the information and sends a structured prompt to the Gemini AI.
|
|
59
|
+
3. An `EXPLAIN.md` file is generated in your current working directory containing:
|
|
60
|
+
- Project Overview
|
|
61
|
+
- Functional Breakdown
|
|
62
|
+
- Target User Identification
|
|
63
|
+
- Setup/Execution Instructions
|
|
64
|
+
|
|
65
|
+
### Scripts
|
|
66
|
+
- `npm run build`: Compiles TypeScript to the `dist` folder.
|
|
67
|
+
- `npm run format`: Formats the codebase using Prettier.
|
|
68
|
+
- `npm start`: Runs the tool (requires the repository argument).
|
|
69
|
+
|
|
70
|
+
## Contributing
|
|
71
|
+
|
|
72
|
+
Contributions are what make the open-source community an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
|
|
73
|
+
|
|
74
|
+
-️ **Report Bugs**: Open an issue if you find any unexpected behavior.
|
|
75
|
+
- **Feature Requests**: Proposals for new features are always welcome.
|
|
76
|
+
- **Pull Requests**: Ensure your code follows the existing style and all linting passes.
|
|
77
|
+
|
|
78
|
+
> Note: Please run npm run format before submitting a Pull Request to ensure code consistency.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
This project is licensed under the MIT License as specified in the package configuration.
|
|
82
|
+
|
|
83
|
+
## Author Info
|
|
84
|
+
|
|
85
|
+
**Spectra010s**
|
|
86
|
+
- [Portfolio](https://spectra010s.vercel.app)
|
|
87
|
+
- [Twitter/X](https://x.com/Spectra010s)
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { fetchRepo, fetchReadme } from "./github.js";
|
|
5
|
+
import { buildPrompt } from "./prompt.js";
|
|
6
|
+
import { generateExplanation } from "./generate.js";
|
|
7
|
+
import { writeOutput } from "./writer.js";
|
|
8
|
+
function usage() {
|
|
9
|
+
console.log("usage:");
|
|
10
|
+
console.log(" explainthisrepo owner/repo");
|
|
11
|
+
console.log(" explainthisrepo owner/repo --detailed");
|
|
12
|
+
console.log(" explainthisrepo --doctor");
|
|
13
|
+
console.log(" explainthisrepo --version");
|
|
14
|
+
}
|
|
15
|
+
function getPkgVersion() {
|
|
16
|
+
return process.env.npm_package_version || "unknown";
|
|
17
|
+
}
|
|
18
|
+
function printVersion() {
|
|
19
|
+
console.log(getPkgVersion());
|
|
20
|
+
}
|
|
21
|
+
function hasEnv(key) {
|
|
22
|
+
const v = process.env[key];
|
|
23
|
+
return Boolean(v && v.trim());
|
|
24
|
+
}
|
|
25
|
+
async function checkUrl(url, timeoutMs = 6000) {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(url, {
|
|
30
|
+
method: "GET",
|
|
31
|
+
headers: { "User-Agent": "explainthisrepo" },
|
|
32
|
+
signal: controller.signal,
|
|
33
|
+
});
|
|
34
|
+
clearTimeout(t);
|
|
35
|
+
return { ok: res.ok, msg: `ok (${res.status})` };
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
clearTimeout(t);
|
|
39
|
+
return { ok: false, msg: `failed (${e?.name || "Error"}: ${e?.message || e})` };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function runDoctor() {
|
|
43
|
+
console.log("explainthisrepo doctor report\n");
|
|
44
|
+
console.log(`node: ${process.version}`);
|
|
45
|
+
console.log(`os: ${os.type()} ${os.release()}`);
|
|
46
|
+
console.log(`platform: ${process.platform} ${process.arch}`);
|
|
47
|
+
console.log(`version: ${getPkgVersion()}`);
|
|
48
|
+
console.log("\nenvironment:");
|
|
49
|
+
console.log(`- GEMINI_API_KEY set: ${hasEnv("GEMINI_API_KEY")}`);
|
|
50
|
+
console.log(`- GITHUB_TOKEN set: ${hasEnv("GITHUB_TOKEN")}`);
|
|
51
|
+
console.log("\nnetwork checks:");
|
|
52
|
+
const gh = await checkUrl("https://api.github.com");
|
|
53
|
+
console.log(`- github api: ${gh.msg}`);
|
|
54
|
+
const gem = await checkUrl("https://generativelanguage.googleapis.com");
|
|
55
|
+
console.log(`- gemini endpoint: ${gem.msg}`);
|
|
56
|
+
return gh.ok ? 0 : 1;
|
|
57
|
+
}
|
|
58
|
+
async function main() {
|
|
59
|
+
const args = process.argv.slice(2);
|
|
60
|
+
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
|
|
61
|
+
usage();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
if (args[0] === "--version") {
|
|
65
|
+
printVersion();
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
if (args[0] === "--doctor") {
|
|
69
|
+
const code = await runDoctor();
|
|
70
|
+
process.exit(code);
|
|
71
|
+
}
|
|
72
|
+
let detailed = false;
|
|
73
|
+
if (args.length === 2) {
|
|
74
|
+
if (args[1] !== "--detailed") {
|
|
75
|
+
usage();
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
detailed = true;
|
|
79
|
+
}
|
|
80
|
+
else if (args.length !== 1) {
|
|
81
|
+
usage();
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const target = args[0];
|
|
85
|
+
if (!target.includes("/") || target.split("/").length !== 2) {
|
|
86
|
+
console.log("Invalid format. Use owner/repo");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const [owner, repo] = target.split("/");
|
|
90
|
+
if (!owner || !repo) {
|
|
91
|
+
console.log("Invalid format. Use owner/repo");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
console.log(`Fetching ${owner}/${repo}...`);
|
|
95
|
+
try {
|
|
96
|
+
const repoData = await fetchRepo(owner, repo);
|
|
97
|
+
const readme = await fetchReadme(owner, repo);
|
|
98
|
+
const prompt = buildPrompt(repoData.full_name, repoData.description, readme, detailed);
|
|
99
|
+
console.log("Generating explanation...");
|
|
100
|
+
const output = await generateExplanation(prompt);
|
|
101
|
+
console.log("Writing EXPLAIN.md...");
|
|
102
|
+
writeOutput(output);
|
|
103
|
+
const wordCount = output.split(/\s+/).filter(Boolean).length;
|
|
104
|
+
console.log("EXPLAIN.md generated successfully 🎉");
|
|
105
|
+
console.log(`Words: ${wordCount}`);
|
|
106
|
+
console.log("Open EXPLAIN.md to read it.");
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
console.log("Failed to generate explanation.");
|
|
110
|
+
console.log(`error: ${e?.message || e}`);
|
|
111
|
+
console.log("\nfix:");
|
|
112
|
+
console.log("- Ensure GEMINI_API_KEY is set");
|
|
113
|
+
console.log("- Or run: explainthisrepo --doctor");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateExplanation(prompt: string): Promise<string>;
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
+
const DEFAULT_MODEL = "gemini-2.5-flash-lite";
|
|
3
|
+
function getApiKey() {
|
|
4
|
+
const key = process.env.GEMINI_API_KEY;
|
|
5
|
+
if (!key || !key.trim()) {
|
|
6
|
+
throw new Error([
|
|
7
|
+
"GEMINI_API_KEY is not set.",
|
|
8
|
+
"",
|
|
9
|
+
"Fix:",
|
|
10
|
+
' export GEMINI_API_KEY="your_key_here"',
|
|
11
|
+
].join("\n"));
|
|
12
|
+
}
|
|
13
|
+
return key.trim();
|
|
14
|
+
}
|
|
15
|
+
export async function generateExplanation(prompt) {
|
|
16
|
+
const apiKey = getApiKey();
|
|
17
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
|
18
|
+
const modelName = (process.env.GEMINI_MODEL || DEFAULT_MODEL).trim();
|
|
19
|
+
const model = genAI.getGenerativeModel({ model: modelName });
|
|
20
|
+
try {
|
|
21
|
+
const result = await model.generateContent(prompt);
|
|
22
|
+
const text = result?.response?.text?.() ?? "";
|
|
23
|
+
if (!text.trim()) {
|
|
24
|
+
throw new Error("Gemini returned no text");
|
|
25
|
+
}
|
|
26
|
+
return text.trim();
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const msg = err?.message ? String(err.message) : String(err);
|
|
30
|
+
throw new Error([
|
|
31
|
+
"Failed to generate explanation (Gemini).",
|
|
32
|
+
`Model: ${modelName}`,
|
|
33
|
+
`Error: ${msg}`,
|
|
34
|
+
].join("\n"));
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/github.d.ts
ADDED
package/dist/github.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
const GITHUB_API_BASE = "https://api.github.com";
|
|
3
|
+
function sleep(ms) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
function getGithubHeaders() {
|
|
7
|
+
const headers = {
|
|
8
|
+
Accept: "application/vnd.github.v3+json",
|
|
9
|
+
"User-Agent": "ExplainThisRepo",
|
|
10
|
+
};
|
|
11
|
+
const token = process.env.GITHUB_TOKEN || process.env.GITHUB_API_KEY;
|
|
12
|
+
if (token && token.trim()) {
|
|
13
|
+
headers.Authorization = `Bearer ${token.trim()}`;
|
|
14
|
+
}
|
|
15
|
+
return headers;
|
|
16
|
+
}
|
|
17
|
+
const github = axios.create({
|
|
18
|
+
baseURL: GITHUB_API_BASE,
|
|
19
|
+
headers: getGithubHeaders(),
|
|
20
|
+
timeout: 12000,
|
|
21
|
+
});
|
|
22
|
+
function formatAxiosError(err) {
|
|
23
|
+
if (!axios.isAxiosError(err))
|
|
24
|
+
return "Unknown error";
|
|
25
|
+
const status = err.response?.status;
|
|
26
|
+
const data = err.response?.data;
|
|
27
|
+
const msg = data?.message || err.message;
|
|
28
|
+
if (status === 404)
|
|
29
|
+
return "Repository not found (404). Check owner/repo.";
|
|
30
|
+
if (status === 401)
|
|
31
|
+
return "GitHub auth failed (401). Invalid GITHUB_TOKEN.";
|
|
32
|
+
if (status === 403 && typeof msg === "string" && msg.toLowerCase().includes("rate limit")) {
|
|
33
|
+
return "GitHub rate limit hit (403). Set GITHUB_TOKEN to increase limits.";
|
|
34
|
+
}
|
|
35
|
+
if (status === 429)
|
|
36
|
+
return "GitHub rate limit hit (429). Try again in a minute.";
|
|
37
|
+
if (status && status >= 500)
|
|
38
|
+
return `GitHub server error (${status}). Try again later.`;
|
|
39
|
+
return `GitHub request failed (${status ?? "no status"}): ${msg}`;
|
|
40
|
+
}
|
|
41
|
+
async function requestWithRetry(fn, opts) {
|
|
42
|
+
const maxRetries = opts?.maxRetries ?? 3;
|
|
43
|
+
const baseDelayMs = opts?.baseDelayMs ?? 700;
|
|
44
|
+
let attempt = 0;
|
|
45
|
+
while (true) {
|
|
46
|
+
try {
|
|
47
|
+
return await fn();
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
attempt += 1;
|
|
51
|
+
const isAxios = axios.isAxiosError(err);
|
|
52
|
+
const status = isAxios ? err.response?.status : undefined;
|
|
53
|
+
const retryable = status === 403 || status === 429 || (status !== undefined && status >= 500);
|
|
54
|
+
if (!retryable || attempt > maxRetries) {
|
|
55
|
+
throw new Error(formatAxiosError(err));
|
|
56
|
+
}
|
|
57
|
+
const resetHeader = isAxios ? err.response?.headers?.["x-ratelimit-reset"] : undefined;
|
|
58
|
+
if ((status === 403 || status === 429) && resetHeader) {
|
|
59
|
+
const resetSeconds = Number(resetHeader);
|
|
60
|
+
if (!Number.isNaN(resetSeconds)) {
|
|
61
|
+
const waitMs = Math.max(resetSeconds * 1000 - Date.now(), 1000);
|
|
62
|
+
await sleep(Math.min(waitMs, 30_000));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
67
|
+
await sleep(Math.min(delay, 8000));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function fetchRepo(owner, repo) {
|
|
72
|
+
return requestWithRetry(async () => {
|
|
73
|
+
const res = await github.get(`/repos/${owner}/${repo}`);
|
|
74
|
+
return res.data;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function fetchReadme(owner, repo) {
|
|
78
|
+
try {
|
|
79
|
+
return await requestWithRetry(async () => {
|
|
80
|
+
const res = await github.get(`/repos/${owner}/${repo}/readme`, {
|
|
81
|
+
headers: {
|
|
82
|
+
...getGithubHeaders(),
|
|
83
|
+
Accept: "application/vnd.github.v3.raw",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
return res.data;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
if (axios.isAxiosError(err)) {
|
|
91
|
+
const status = err.response?.status;
|
|
92
|
+
if (status === 404)
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildPrompt(repoName: string, description: string | null, readme: string | null, detailed?: boolean): string;
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function buildPrompt(repoName, description, readme, detailed = false) {
|
|
2
|
+
let prompt = `
|
|
3
|
+
You are a senior software engineer.
|
|
4
|
+
|
|
5
|
+
Your task is to explain a GitHub repository clearly and concisely for a human reader.
|
|
6
|
+
|
|
7
|
+
Repository:
|
|
8
|
+
- Name: ${repoName}
|
|
9
|
+
- Description: ${description || "No description provided"}
|
|
10
|
+
|
|
11
|
+
README content:
|
|
12
|
+
${readme || "No README provided"}
|
|
13
|
+
|
|
14
|
+
Instructions:
|
|
15
|
+
- Explain what this project does.
|
|
16
|
+
- Say who it is for.
|
|
17
|
+
- Explain how to run or use it.
|
|
18
|
+
- Do not assume missing details.
|
|
19
|
+
- If something is unclear, say so.
|
|
20
|
+
- Avoid hype or marketing language.
|
|
21
|
+
- Be concise and practical.
|
|
22
|
+
- Use clear markdown headings.
|
|
23
|
+
`.trim();
|
|
24
|
+
if (detailed) {
|
|
25
|
+
prompt += `
|
|
26
|
+
|
|
27
|
+
Additional instructions:
|
|
28
|
+
- Explain the high-level architecture.
|
|
29
|
+
- Describe the folder structure.
|
|
30
|
+
- Mention important files and their roles.
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
prompt += `
|
|
34
|
+
|
|
35
|
+
Output format:
|
|
36
|
+
# Overview
|
|
37
|
+
# What this project does
|
|
38
|
+
# Who it is for
|
|
39
|
+
# How to run or use it
|
|
40
|
+
# Notes or limitations
|
|
41
|
+
`;
|
|
42
|
+
return prompt.trim();
|
|
43
|
+
}
|
package/dist/writer.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeOutput(content: string): void;
|
package/dist/writer.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "explainthisrepo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool to explain a GitHub repository in plain English",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Caleb Wodi <calebwodi33@gmail.com>",
|
|
8
|
+
"homepage": "https://explainthisrepo.com",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/calchiwo/ExplainThisRepo.git",
|
|
12
|
+
"directory": "node_version"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/calchiwo/ExplainThisRepo/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"github",
|
|
19
|
+
"cli",
|
|
20
|
+
"explain",
|
|
21
|
+
"repository",
|
|
22
|
+
"ai",
|
|
23
|
+
"developer-tools"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"explainthisrepo": "./dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"start": "node dist/cli.js",
|
|
40
|
+
"prepublishOnly": "npm run build"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@google/generative-ai": "^0.24.1",
|
|
47
|
+
"axios": "^1.13.2",
|
|
48
|
+
"dotenv": "^17.2.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
|
+
"typescript": "^5.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|