@microsoft/inshellisense 0.0.1-rc.5 → 0.0.1-rc.6
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 +21 -1
- package/build/commands/complete.js +15 -0
- package/build/commands/root.js +13 -2
- package/build/index.js +4 -0
- package/build/isterm/commandManager.js +7 -2
- package/build/isterm/pty.js +34 -3
- package/build/runtime/generator.js +21 -10
- package/build/runtime/parser.js +2 -2
- package/build/runtime/runtime.js +37 -23
- package/build/runtime/suggestion.js +34 -14
- package/build/runtime/template.js +23 -17
- package/build/runtime/utils.js +42 -12
- package/build/ui/suggestionManager.js +35 -15
- package/build/ui/ui-root.js +31 -20
- package/build/utils/ansi.js +1 -0
- package/build/utils/log.js +11 -3
- package/build/utils/shell.js +17 -1
- package/package.json +4 -2
- package/shell/bash-preexec.sh +380 -0
- package/shell/shellIntegration-rc.zsh +34 -1
- package/shell/shellIntegration.bash +34 -0
- package/shell/shellIntegration.fish +8 -0
- package/shell/shellIntegration.ps1 +9 -0
package/build/runtime/utils.js
CHANGED
|
@@ -1,22 +1,52 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import fsAsync from "node:fs/promises";
|
|
6
|
+
import { Shell } from "../utils/shell.js";
|
|
7
|
+
import log from "../utils/log.js";
|
|
8
|
+
export const buildExecuteShellCommand = (timeout) => async ({ command, env, args, cwd }) => {
|
|
9
|
+
const child = spawn(command, args, { cwd, env });
|
|
10
|
+
setTimeout(() => child.kill("SIGKILL"), timeout);
|
|
11
|
+
let stdout = "";
|
|
12
|
+
let stderr = "";
|
|
13
|
+
child.stdout.on("data", (data) => (stdout += data));
|
|
14
|
+
child.stderr.on("data", (data) => (stderr += data));
|
|
15
|
+
child.on("error", (err) => {
|
|
16
|
+
log.debug({ msg: "shell command failed", e: err.message });
|
|
11
17
|
});
|
|
12
|
-
};
|
|
13
|
-
export const executeShellCommandTTY = async (shell, command) => {
|
|
14
|
-
const child = spawn(shell, ["-c", command.trim()], { stdio: "inherit" });
|
|
15
18
|
return new Promise((resolve) => {
|
|
16
19
|
child.on("close", (code) => {
|
|
17
20
|
resolve({
|
|
18
|
-
code,
|
|
21
|
+
status: code ?? 0,
|
|
22
|
+
stderr,
|
|
23
|
+
stdout,
|
|
19
24
|
});
|
|
20
25
|
});
|
|
21
26
|
});
|
|
22
27
|
};
|
|
28
|
+
export const resolveCwd = async (cmdToken, cwd, shell) => {
|
|
29
|
+
if (cmdToken == null)
|
|
30
|
+
return { cwd, pathy: false, complete: false };
|
|
31
|
+
const { token } = cmdToken;
|
|
32
|
+
const sep = shell == Shell.Bash ? "/" : path.sep;
|
|
33
|
+
if (!token.includes(sep))
|
|
34
|
+
return { cwd, pathy: false, complete: false };
|
|
35
|
+
const resolvedCwd = path.isAbsolute(token) ? token : path.join(cwd, token);
|
|
36
|
+
try {
|
|
37
|
+
await fsAsync.access(resolvedCwd, fsAsync.constants.R_OK);
|
|
38
|
+
return { cwd: resolvedCwd, pathy: true, complete: token.endsWith(sep) };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// fallback to the parent folder if possible
|
|
42
|
+
const baselessCwd = resolvedCwd.substring(0, resolvedCwd.length - path.basename(resolvedCwd).length);
|
|
43
|
+
try {
|
|
44
|
+
await fsAsync.access(baselessCwd, fsAsync.constants.R_OK);
|
|
45
|
+
return { cwd: baselessCwd, pathy: true, complete: token.endsWith(sep) };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/*empty*/
|
|
49
|
+
}
|
|
50
|
+
return { cwd, pathy: false, complete: false };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -17,11 +17,13 @@ export class SuggestionManager {
|
|
|
17
17
|
#command;
|
|
18
18
|
#activeSuggestionIdx;
|
|
19
19
|
#suggestBlob;
|
|
20
|
-
|
|
20
|
+
#shell;
|
|
21
|
+
constructor(terminal, shell) {
|
|
21
22
|
this.#term = terminal;
|
|
22
23
|
this.#suggestBlob = { suggestions: [] };
|
|
23
24
|
this.#command = "";
|
|
24
25
|
this.#activeSuggestionIdx = 0;
|
|
26
|
+
this.#shell = shell;
|
|
25
27
|
}
|
|
26
28
|
async _loadSuggestions() {
|
|
27
29
|
const commandText = this.#term.getCommandState().commandText;
|
|
@@ -33,7 +35,7 @@ export class SuggestionManager {
|
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
37
|
this.#command = commandText;
|
|
36
|
-
const suggestionBlob = await getSuggestions(commandText);
|
|
38
|
+
const suggestionBlob = await getSuggestions(commandText, this.#term.cwd, this.#shell);
|
|
37
39
|
this.#suggestBlob = suggestionBlob;
|
|
38
40
|
}
|
|
39
41
|
_renderArgumentDescription(description, x) {
|
|
@@ -46,6 +48,11 @@ export class SuggestionManager {
|
|
|
46
48
|
return "";
|
|
47
49
|
return renderBox(truncateMultilineText(description, descriptionWidth - borderWidth, descriptionHeight), descriptionWidth, x);
|
|
48
50
|
}
|
|
51
|
+
_descriptionRows(description) {
|
|
52
|
+
if (!description)
|
|
53
|
+
return 0;
|
|
54
|
+
return truncateMultilineText(description, descriptionWidth - borderWidth, descriptionHeight).length;
|
|
55
|
+
}
|
|
49
56
|
_renderSuggestions(suggestions, activeSuggestionIdx, x) {
|
|
50
57
|
return renderBox(suggestions.map((suggestion, idx) => {
|
|
51
58
|
const suggestionText = `${suggestion.icon} ${suggestion.name}`.padEnd(suggestionWidth - borderWidth, " ");
|
|
@@ -53,10 +60,10 @@ export class SuggestionManager {
|
|
|
53
60
|
return idx == activeSuggestionIdx ? chalk.bgHex(activeSuggestionBackgroundColor)(truncatedSuggestion) : truncatedSuggestion;
|
|
54
61
|
}), suggestionWidth, x);
|
|
55
62
|
}
|
|
56
|
-
async render() {
|
|
63
|
+
async render(remainingLines) {
|
|
57
64
|
await this._loadSuggestions();
|
|
58
65
|
if (!this.#suggestBlob)
|
|
59
|
-
return { data: "",
|
|
66
|
+
return { data: "", rows: 0 };
|
|
60
67
|
const { suggestions, argumentDescription } = this.#suggestBlob;
|
|
61
68
|
const page = Math.min(Math.floor(this.#activeSuggestionIdx / maxSuggestions) + 1, Math.floor(suggestions.length / maxSuggestions) + 1);
|
|
62
69
|
const pagedSuggestions = suggestions.filter((_, idx) => idx < page * maxSuggestions && idx >= (page - 1) * maxSuggestions);
|
|
@@ -77,20 +84,32 @@ export class SuggestionManager {
|
|
|
77
84
|
ansi.cursorUp(2) +
|
|
78
85
|
ansi.cursorForward(clampedLeftPadding) +
|
|
79
86
|
this._renderArgumentDescription(argumentDescription, clampedLeftPadding),
|
|
80
|
-
|
|
87
|
+
rows: 3,
|
|
81
88
|
};
|
|
82
89
|
}
|
|
83
|
-
return { data: "",
|
|
90
|
+
return { data: "", rows: 0 };
|
|
91
|
+
}
|
|
92
|
+
const suggestionRowsUsed = pagedSuggestions.length + borderWidth;
|
|
93
|
+
let descriptionRowsUsed = this._descriptionRows(activeDescription) + borderWidth;
|
|
94
|
+
let rows = Math.max(descriptionRowsUsed, suggestionRowsUsed);
|
|
95
|
+
if (rows <= remainingLines) {
|
|
96
|
+
descriptionRowsUsed = suggestionRowsUsed;
|
|
97
|
+
rows = suggestionRowsUsed;
|
|
84
98
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
const descriptionUI = ansi.cursorUp(descriptionRowsUsed - 1) +
|
|
100
|
+
(swapDescription
|
|
101
|
+
? this._renderDescription(activeDescription, clampedLeftPadding)
|
|
102
|
+
: this._renderDescription(activeDescription, clampedLeftPadding + suggestionWidth)) +
|
|
103
|
+
ansi.cursorDown(descriptionRowsUsed - 1);
|
|
104
|
+
const suggestionUI = ansi.cursorUp(suggestionRowsUsed - 1) +
|
|
105
|
+
(swapDescription
|
|
106
|
+
? this._renderSuggestions(pagedSuggestions, activePagedSuggestionIndex, clampedLeftPadding + descriptionWidth)
|
|
107
|
+
: this._renderSuggestions(pagedSuggestions, activePagedSuggestionIndex, clampedLeftPadding)) +
|
|
108
|
+
ansi.cursorDown(suggestionRowsUsed - 1);
|
|
109
|
+
const ui = swapDescription ? descriptionUI + suggestionUI : suggestionUI + descriptionUI;
|
|
91
110
|
return {
|
|
92
|
-
data: ansi.cursorHide + ansi.
|
|
93
|
-
|
|
111
|
+
data: ansi.cursorHide + ansi.cursorForward(clampedLeftPadding) + ui + ansi.cursorShow,
|
|
112
|
+
rows,
|
|
94
113
|
};
|
|
95
114
|
}
|
|
96
115
|
update(input) {
|
|
@@ -108,7 +127,8 @@ export class SuggestionManager {
|
|
|
108
127
|
}
|
|
109
128
|
else if (keyStroke == "tab") {
|
|
110
129
|
const removals = "\u007F".repeat(this.#suggestBlob?.charactersToDrop ?? 0);
|
|
111
|
-
const
|
|
130
|
+
const suggestion = this.#suggestBlob?.suggestions.at(this.#activeSuggestionIdx);
|
|
131
|
+
const chars = suggestion?.insertValue ?? suggestion?.name + " ";
|
|
112
132
|
if (this.#suggestBlob == null || !chars.trim() || this.#suggestBlob?.suggestions.length == 0) {
|
|
113
133
|
return false;
|
|
114
134
|
}
|
package/build/ui/ui-root.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
+
import ansi from "ansi-escapes";
|
|
4
|
+
import chalk from "chalk";
|
|
3
5
|
import { inputModifier } from "./input.js";
|
|
4
6
|
import log from "../utils/log.js";
|
|
5
7
|
import isterm from "../isterm/index.js";
|
|
6
8
|
import { eraseLinesBelow } from "../utils/ansi.js";
|
|
7
|
-
import ansi from "ansi-escapes";
|
|
8
9
|
import { SuggestionManager, MAX_LINES } from "./suggestionManager.js";
|
|
10
|
+
export const renderConfirmation = (live) => {
|
|
11
|
+
const statusMessage = live ? chalk.green("live") : chalk.red("not found");
|
|
12
|
+
return `inshellisense session [${statusMessage}]\n`;
|
|
13
|
+
};
|
|
9
14
|
export const render = async (shell) => {
|
|
10
15
|
const term = await isterm.spawn({ shell, rows: process.stdout.rows, cols: process.stdout.columns });
|
|
11
|
-
const suggestionManager = new SuggestionManager(term);
|
|
16
|
+
const suggestionManager = new SuggestionManager(term, shell);
|
|
12
17
|
let hasActiveSuggestions = false;
|
|
13
|
-
let
|
|
18
|
+
let previousSuggestionsRows = 0;
|
|
14
19
|
process.stdin.setRawMode(true);
|
|
15
20
|
const writeOutput = (data) => {
|
|
16
21
|
log.debug({ msg: "writing data", data });
|
|
@@ -18,30 +23,36 @@ export const render = async (shell) => {
|
|
|
18
23
|
};
|
|
19
24
|
writeOutput(ansi.clearTerminal);
|
|
20
25
|
term.onData((data) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
if (hasActiveSuggestions) {
|
|
27
|
+
// Considers when data includes newlines which have shifted the cursor position downwards
|
|
28
|
+
const newlines = Math.max((data.match(/\r/g) || []).length, (data.match(/\n/g) || []).length);
|
|
29
|
+
const linesOfInterest = MAX_LINES + newlines;
|
|
30
|
+
if (term.getCursorState().remainingLines <= previousSuggestionsRows) {
|
|
31
|
+
// handles when suggestions get loaded before shell output so you need to always clear below output as a precaution
|
|
32
|
+
if (term.getCursorState().remainingLines != 0) {
|
|
33
|
+
writeOutput(ansi.cursorHide + ansi.cursorSavePosition + eraseLinesBelow(linesOfInterest + 1) + ansi.cursorRestorePosition);
|
|
34
|
+
}
|
|
35
|
+
writeOutput(data +
|
|
36
|
+
ansi.cursorHide +
|
|
25
37
|
ansi.cursorSavePosition +
|
|
26
|
-
ansi.cursorPrevLine.repeat(
|
|
27
|
-
term.getCells(
|
|
38
|
+
ansi.cursorPrevLine.repeat(linesOfInterest) +
|
|
39
|
+
term.getCells(linesOfInterest, "above") +
|
|
28
40
|
ansi.cursorRestorePosition +
|
|
29
|
-
ansi.cursorShow
|
|
30
|
-
data);
|
|
41
|
+
ansi.cursorShow);
|
|
31
42
|
}
|
|
32
43
|
else {
|
|
33
|
-
writeOutput(ansi.cursorHide + ansi.cursorSavePosition + eraseLinesBelow(
|
|
44
|
+
writeOutput(ansi.cursorHide + ansi.cursorSavePosition + eraseLinesBelow(linesOfInterest + 1) + ansi.cursorRestorePosition + ansi.cursorShow + data);
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
47
|
else {
|
|
37
48
|
writeOutput(data);
|
|
38
49
|
}
|
|
39
50
|
setImmediate(async () => {
|
|
40
|
-
const suggestion = await suggestionManager.render();
|
|
51
|
+
const suggestion = await suggestionManager.render(term.getCursorState().remainingLines);
|
|
41
52
|
const commandState = term.getCommandState();
|
|
42
53
|
if (suggestion.data != "" && commandState.cursorTerminated && !commandState.hasOutput) {
|
|
43
54
|
if (hasActiveSuggestions) {
|
|
44
|
-
if (term.getCursorState().remainingLines < suggestion.
|
|
55
|
+
if (term.getCursorState().remainingLines < suggestion.rows) {
|
|
45
56
|
writeOutput(ansi.cursorHide +
|
|
46
57
|
ansi.cursorSavePosition +
|
|
47
58
|
ansi.cursorPrevLine.repeat(MAX_LINES) +
|
|
@@ -54,7 +65,7 @@ export const render = async (shell) => {
|
|
|
54
65
|
ansi.cursorShow);
|
|
55
66
|
}
|
|
56
67
|
else {
|
|
57
|
-
const offset = MAX_LINES - suggestion.
|
|
68
|
+
const offset = MAX_LINES - suggestion.rows;
|
|
58
69
|
writeOutput(ansi.cursorHide +
|
|
59
70
|
ansi.cursorSavePosition +
|
|
60
71
|
eraseLinesBelow(MAX_LINES) +
|
|
@@ -65,13 +76,13 @@ export const render = async (shell) => {
|
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
else {
|
|
68
|
-
if (term.getCursorState().remainingLines < suggestion.
|
|
79
|
+
if (term.getCursorState().remainingLines < suggestion.rows) {
|
|
69
80
|
writeOutput(ansi.cursorHide + ansi.cursorSavePosition + ansi.cursorUp() + suggestion.data + ansi.cursorRestorePosition + ansi.cursorShow);
|
|
70
81
|
}
|
|
71
82
|
else {
|
|
72
83
|
writeOutput(ansi.cursorHide +
|
|
73
84
|
ansi.cursorSavePosition +
|
|
74
|
-
ansi.cursorNextLine.repeat(suggestion.
|
|
85
|
+
ansi.cursorNextLine.repeat(suggestion.rows) +
|
|
75
86
|
suggestion.data +
|
|
76
87
|
ansi.cursorRestorePosition +
|
|
77
88
|
ansi.cursorShow);
|
|
@@ -81,7 +92,7 @@ export const render = async (shell) => {
|
|
|
81
92
|
}
|
|
82
93
|
else {
|
|
83
94
|
if (hasActiveSuggestions) {
|
|
84
|
-
if (term.getCursorState().remainingLines
|
|
95
|
+
if (term.getCursorState().remainingLines <= previousSuggestionsRows) {
|
|
85
96
|
writeOutput(ansi.cursorHide +
|
|
86
97
|
ansi.cursorSavePosition +
|
|
87
98
|
ansi.cursorPrevLine.repeat(MAX_LINES) +
|
|
@@ -95,12 +106,12 @@ export const render = async (shell) => {
|
|
|
95
106
|
}
|
|
96
107
|
hasActiveSuggestions = false;
|
|
97
108
|
}
|
|
98
|
-
|
|
109
|
+
previousSuggestionsRows = suggestion.rows;
|
|
99
110
|
});
|
|
100
111
|
});
|
|
101
112
|
process.stdin.on("data", (d) => {
|
|
102
113
|
const suggestionResult = suggestionManager.update(d);
|
|
103
|
-
if (
|
|
114
|
+
if (previousSuggestionsRows > 0 && suggestionResult == "handled") {
|
|
104
115
|
term.noop();
|
|
105
116
|
}
|
|
106
117
|
else if (suggestionResult != "fully-handled") {
|
package/build/utils/ansi.js
CHANGED
|
@@ -11,6 +11,7 @@ export var IstermOscPt;
|
|
|
11
11
|
(function (IstermOscPt) {
|
|
12
12
|
IstermOscPt["PromptStarted"] = "PS";
|
|
13
13
|
IstermOscPt["PromptEnded"] = "PE";
|
|
14
|
+
IstermOscPt["CurrentWorkingDirectory"] = "CWD";
|
|
14
15
|
})(IstermOscPt || (IstermOscPt = {}));
|
|
15
16
|
export const IstermPromptStart = IS_OSC + IstermOscPt.PromptStarted + BEL;
|
|
16
17
|
export const IstermPromptEnd = IS_OSC + IstermOscPt.PromptEnded + BEL;
|
package/build/utils/log.js
CHANGED
|
@@ -4,9 +4,13 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import fsAsync from "node:fs/promises";
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const logFolder = path.join(os.homedir(), ".inshellisense");
|
|
8
|
+
const logTarget = path.join(logFolder, "inshellisense.log");
|
|
9
|
+
let logEnabled = false;
|
|
9
10
|
const reset = async () => {
|
|
11
|
+
if (!fs.existsSync(logTarget)) {
|
|
12
|
+
await fsAsync.mkdir(logFolder, { recursive: true });
|
|
13
|
+
}
|
|
10
14
|
await fsAsync.writeFile(logTarget, "");
|
|
11
15
|
};
|
|
12
16
|
const debug = (content) => {
|
|
@@ -19,4 +23,8 @@ const debug = (content) => {
|
|
|
19
23
|
}
|
|
20
24
|
});
|
|
21
25
|
};
|
|
22
|
-
export
|
|
26
|
+
export const enable = async () => {
|
|
27
|
+
await reset();
|
|
28
|
+
logEnabled = true;
|
|
29
|
+
};
|
|
30
|
+
export default { reset, debug, enable };
|
package/build/utils/shell.js
CHANGED
|
@@ -17,9 +17,25 @@ export var Shell;
|
|
|
17
17
|
Shell["Fish"] = "fish";
|
|
18
18
|
Shell["Cmd"] = "cmd";
|
|
19
19
|
})(Shell || (Shell = {}));
|
|
20
|
-
export const supportedShells = [
|
|
20
|
+
export const supportedShells = [
|
|
21
|
+
Shell.Bash,
|
|
22
|
+
process.platform == "win32" ? Shell.Powershell : null,
|
|
23
|
+
Shell.Pwsh,
|
|
24
|
+
Shell.Zsh,
|
|
25
|
+
Shell.Fish,
|
|
26
|
+
process.platform == "win32" ? Shell.Cmd : null,
|
|
27
|
+
].filter((shell) => shell != null);
|
|
21
28
|
export const userZdotdir = process.env?.ZDOTDIR ?? os.homedir() ?? `~`;
|
|
22
29
|
export const zdotdir = path.join(os.tmpdir(), `is-zsh`);
|
|
30
|
+
const configFolder = ".inshellisense";
|
|
31
|
+
export const setupBashPreExec = async () => {
|
|
32
|
+
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
33
|
+
const globalConfigPath = path.join(os.homedir(), configFolder);
|
|
34
|
+
if (!fs.existsSync(globalConfigPath)) {
|
|
35
|
+
await fsAsync.mkdir(globalConfigPath, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
await fsAsync.cp(path.join(shellFolderPath, "bash-preexec.sh"), path.join(globalConfigPath, "bash-preexec.sh"));
|
|
38
|
+
};
|
|
23
39
|
export const setupZshDotfiles = async () => {
|
|
24
40
|
const shellFolderPath = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "..", "shell");
|
|
25
41
|
await fsAsync.cp(path.join(shellFolderPath, "shellIntegration-rc.zsh"), path.join(zdotdir, ".zshrc"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/inshellisense",
|
|
3
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.6",
|
|
4
4
|
"description": "IDE style command line auto complete",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"start": "node ./build/index.js",
|
|
19
19
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
20
20
|
"lint": "eslint src/ --ext .ts,.tsx && prettier src/ --check",
|
|
21
|
-
"lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write"
|
|
21
|
+
"lint:fix": "eslint src/ --ext .ts,.tsx --fix && prettier src/ --write",
|
|
22
|
+
"debug": "node --inspect --loader ts-node/esm src/index.ts -V"
|
|
22
23
|
},
|
|
23
24
|
"repository": {
|
|
24
25
|
"type": "git",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"jest": "^29.7.0",
|
|
61
62
|
"prettier": "3.0.3",
|
|
62
63
|
"ts-jest": "^29.1.1",
|
|
64
|
+
"ts-node": "^10.9.2",
|
|
63
65
|
"typescript": "^5.2.2"
|
|
64
66
|
}
|
|
65
67
|
}
|