ag-toolkit 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/LICENSE +21 -0
- package/README.ja.md +33 -0
- package/README.md +33 -0
- package/dist/components/ConfigEditor.d.ts +14 -0
- package/dist/components/ConfigEditor.js +196 -0
- package/dist/components/ConfigEditorItem.d.ts +3 -0
- package/dist/components/ConfigEditorItem.js +4 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/git.d.ts +61 -0
- package/dist/git.js +418 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useSearchFilter.d.ts +1 -0
- package/dist/hooks/useSearchFilter.js +22 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/lib/arg-parser.d.ts +30 -0
- package/dist/lib/arg-parser.js +84 -0
- package/dist/lib/cli.d.ts +11 -0
- package/dist/lib/cli.js +60 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +103 -0
- package/dist/lib/format.d.ts +2 -0
- package/dist/lib/format.js +15 -0
- package/dist/lib/index.d.ts +10 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/isDeepEqual.d.ts +28 -0
- package/dist/lib/isDeepEqual.js +147 -0
- package/dist/lib/isValidBranchName.d.ts +1 -0
- package/dist/lib/isValidBranchName.js +28 -0
- package/dist/lib/match.d.ts +1 -0
- package/dist/lib/match.js +12 -0
- package/dist/lib/package.d.ts +7 -0
- package/dist/lib/package.js +10 -0
- package/dist/lib/sanitizeString.d.ts +8 -0
- package/dist/lib/sanitizeString.js +10 -0
- package/package.json +51 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
export const findUp = async (name, startDir) => {
|
|
5
|
+
let dir = resolve(startDir);
|
|
6
|
+
const stopDir = resolve(homedir(), "..");
|
|
7
|
+
while (dir !== stopDir) {
|
|
8
|
+
const filePath = join(dir, name);
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(filePath);
|
|
11
|
+
return filePath;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
dir = dirname(dir);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
export const readConfigFile = async (filePath, validate) => {
|
|
20
|
+
try {
|
|
21
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
22
|
+
return validate(JSON.parse(content));
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Error reading or parsing config file at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export const writeConfig = async (filePath, config) => {
|
|
32
|
+
try {
|
|
33
|
+
await fs.mkdir(dirname(filePath), { recursive: true });
|
|
34
|
+
await fs.writeFile(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Failed to write config file: ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export const loadConfig = async (options, cwd = process.cwd()) => {
|
|
41
|
+
const { toolName, configFile, localConfigFile, defaultConfig, validate } = options;
|
|
42
|
+
const globalConfigPath = join(homedir(), ".config", toolName, configFile);
|
|
43
|
+
const globalConfig = await readConfigFile(globalConfigPath, validate);
|
|
44
|
+
const localConfigPath = await findUp(localConfigFile, cwd);
|
|
45
|
+
const localConfig = localConfigPath
|
|
46
|
+
? await readConfigFile(localConfigPath, validate)
|
|
47
|
+
: null;
|
|
48
|
+
const config = {
|
|
49
|
+
...defaultConfig,
|
|
50
|
+
...(globalConfig || {}),
|
|
51
|
+
...(localConfig || {}),
|
|
52
|
+
};
|
|
53
|
+
const sources = {};
|
|
54
|
+
for (const key in config) {
|
|
55
|
+
if (Object.hasOwn(config, key)) {
|
|
56
|
+
const k = key;
|
|
57
|
+
if (localConfig && Object.hasOwn(localConfig, k)) {
|
|
58
|
+
sources[k] = "local";
|
|
59
|
+
}
|
|
60
|
+
else if (globalConfig && Object.hasOwn(globalConfig, k)) {
|
|
61
|
+
sources[k] = "global";
|
|
62
|
+
}
|
|
63
|
+
else if (Object.hasOwn(defaultConfig, k)) {
|
|
64
|
+
sources[k] = "default";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
config,
|
|
70
|
+
sources,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
export const getGlobalConfigPath = (toolName, configFile) => {
|
|
74
|
+
return join(homedir(), ".config", toolName, configFile);
|
|
75
|
+
};
|
|
76
|
+
export const findLocalConfigPath = async (localConfigFile, cwd = process.cwd()) => {
|
|
77
|
+
return findUp(localConfigFile, cwd);
|
|
78
|
+
};
|
|
79
|
+
export const writeLocalConfig = async (localConfigPath, config) => {
|
|
80
|
+
await writeConfig(localConfigPath, config);
|
|
81
|
+
};
|
|
82
|
+
export const loadLocalConfig = async (localConfigFile, validate, cwd = process.cwd()) => {
|
|
83
|
+
const existingPath = await findLocalConfigPath(localConfigFile, cwd);
|
|
84
|
+
if (existingPath) {
|
|
85
|
+
return {
|
|
86
|
+
path: existingPath,
|
|
87
|
+
config: await readConfigFile(existingPath, validate),
|
|
88
|
+
exists: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
path: join(cwd, localConfigFile),
|
|
93
|
+
config: null,
|
|
94
|
+
exists: false,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export const writeGlobalConfig = async (config, toolName, configFile) => {
|
|
98
|
+
const globalConfigPath = getGlobalConfigPath(toolName, configFile);
|
|
99
|
+
await writeConfig(globalConfigPath, config);
|
|
100
|
+
};
|
|
101
|
+
export const resetGlobalConfig = async (defaultConfig, toolName, configFile) => {
|
|
102
|
+
await writeGlobalConfig(defaultConfig, toolName, configFile);
|
|
103
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const formatDate = (date) => {
|
|
2
|
+
if (!date) {
|
|
3
|
+
return "--";
|
|
4
|
+
}
|
|
5
|
+
return date.toISOString().split("T")[0] ?? "--";
|
|
6
|
+
};
|
|
7
|
+
export const formatConfigValue = (value) => {
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return value.length > 0 ? value.join(", ") : "(not set)";
|
|
10
|
+
}
|
|
11
|
+
if (value === undefined || value === null || value === "") {
|
|
12
|
+
return "(not set)";
|
|
13
|
+
}
|
|
14
|
+
return String(value);
|
|
15
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./arg-parser.js";
|
|
2
|
+
export * from "./cli.js";
|
|
3
|
+
export * from "./config.js";
|
|
4
|
+
export * from "./format.js";
|
|
5
|
+
export * from "./index.js";
|
|
6
|
+
export * from "./isDeepEqual.js";
|
|
7
|
+
export * from "./isValidBranchName.js";
|
|
8
|
+
export * from "./match.js";
|
|
9
|
+
export * from "./package.js";
|
|
10
|
+
export * from "./sanitizeString.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./arg-parser.js";
|
|
2
|
+
export * from "./cli.js";
|
|
3
|
+
export * from "./config.js";
|
|
4
|
+
export * from "./format.js";
|
|
5
|
+
export * from "./index.js";
|
|
6
|
+
export * from "./isDeepEqual.js";
|
|
7
|
+
export * from "./isValidBranchName.js";
|
|
8
|
+
export * from "./match.js";
|
|
9
|
+
export * from "./package.js";
|
|
10
|
+
export * from "./sanitizeString.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for isDeepEqual comparison
|
|
3
|
+
*/
|
|
4
|
+
export interface IsDeepEqualOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to ignore array order when comparing arrays
|
|
7
|
+
* @default true
|
|
8
|
+
*/
|
|
9
|
+
strictOrder?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Performs a deep equality comparison between two values
|
|
13
|
+
*
|
|
14
|
+
* @param a - First value to compare
|
|
15
|
+
* @param b - Second value to compare
|
|
16
|
+
* @param options - Comparison options
|
|
17
|
+
* @returns true if values are deeply equal, false otherwise
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* isDeepEqual({ a: 1, b: [2, 3] }, { b: [2, 3], a: 1 }); // true
|
|
22
|
+
* isDeepEqual([1, 2, 3], [3, 2, 1]); // false
|
|
23
|
+
* isDeepEqual([1, 2, 3], [3, 2, 1], { strictOrder: false }); // true
|
|
24
|
+
* isDeepEqual(new Set([1, 2]), new Set([2, 1])); // true
|
|
25
|
+
* isDeepEqual(new Map([['a', 1]]), new Map([['a', 1]])); // true
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function isDeepEqual(a: unknown, b: unknown, options?: IsDeepEqualOptions): boolean;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs a deep equality comparison between two values
|
|
3
|
+
*
|
|
4
|
+
* @param a - First value to compare
|
|
5
|
+
* @param b - Second value to compare
|
|
6
|
+
* @param options - Comparison options
|
|
7
|
+
* @returns true if values are deeply equal, false otherwise
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* isDeepEqual({ a: 1, b: [2, 3] }, { b: [2, 3], a: 1 }); // true
|
|
12
|
+
* isDeepEqual([1, 2, 3], [3, 2, 1]); // false
|
|
13
|
+
* isDeepEqual([1, 2, 3], [3, 2, 1], { strictOrder: false }); // true
|
|
14
|
+
* isDeepEqual(new Set([1, 2]), new Set([2, 1])); // true
|
|
15
|
+
* isDeepEqual(new Map([['a', 1]]), new Map([['a', 1]])); // true
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function isDeepEqual(a, b, options = {}) {
|
|
19
|
+
const { strictOrder = true } = options;
|
|
20
|
+
const visited = new WeakSet();
|
|
21
|
+
function compare(x, y) {
|
|
22
|
+
if (Object.is(x, y)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (x == null || y == null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (typeof x !== typeof y) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (typeof x !== "object" || typeof y !== "object") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (visited.has(x) || visited.has(y)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
visited.add(x);
|
|
38
|
+
visited.add(y);
|
|
39
|
+
const ctorX = x.constructor;
|
|
40
|
+
const ctorY = y.constructor;
|
|
41
|
+
if (ctorX !== ctorY) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (x instanceof Date && y instanceof Date) {
|
|
45
|
+
return x.getTime() === y.getTime();
|
|
46
|
+
}
|
|
47
|
+
if (x instanceof RegExp && y instanceof RegExp) {
|
|
48
|
+
return x.toString() === y.toString();
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(x) && Array.isArray(y)) {
|
|
51
|
+
if (x.length !== y.length) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (strictOrder) {
|
|
55
|
+
for (const [index, element] of x.entries()) {
|
|
56
|
+
if (!compare(element, y[index])) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const yCopy = [...y];
|
|
63
|
+
for (const itemX of x) {
|
|
64
|
+
let found = false;
|
|
65
|
+
for (let index = 0; index < yCopy.length; index++) {
|
|
66
|
+
if (compare(itemX, yCopy[index])) {
|
|
67
|
+
yCopy.splice(index, 1);
|
|
68
|
+
found = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!found) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return yCopy.length === 0;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (x instanceof Set && y instanceof Set) {
|
|
81
|
+
if (x.size !== y.size) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
for (const item of x) {
|
|
85
|
+
let found = false;
|
|
86
|
+
for (const otherItem of y) {
|
|
87
|
+
if (compare(item, otherItem)) {
|
|
88
|
+
found = true;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!found) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (x instanceof Map && y instanceof Map) {
|
|
99
|
+
if (x.size !== y.size) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
for (const [key, value] of x) {
|
|
103
|
+
let found = false;
|
|
104
|
+
for (const [otherKey, otherValue] of y) {
|
|
105
|
+
if (compare(key, otherKey) && compare(value, otherValue)) {
|
|
106
|
+
found = true;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!found) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (ArrayBuffer.isView(x) && ArrayBuffer.isView(y)) {
|
|
117
|
+
const xArray = x;
|
|
118
|
+
const yArray = y;
|
|
119
|
+
if (xArray.byteLength !== yArray.byteLength) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
for (let index = 0; index < xArray.byteLength; index++) {
|
|
123
|
+
if (xArray[index] !== yArray[index]) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const keysX = Object.keys(x);
|
|
130
|
+
const keysY = Object.keys(y);
|
|
131
|
+
if (keysX.length !== keysY.length) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
for (const key of keysX) {
|
|
135
|
+
if (!(key in y)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const key of keysX) {
|
|
140
|
+
if (!compare(x[key], y[key])) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return compare(a, b);
|
|
147
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isValidBranchName(branchName: string): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function isValidBranchName(branchName) {
|
|
2
|
+
if (!branchName || branchName.length === 0) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
if (!/^[a-zA-Z0-9-._/]+$/.test(branchName)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (branchName.includes("..")) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (branchName.startsWith("/") || branchName.endsWith("/")) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (branchName.includes("//")) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (branchName.endsWith(".")) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const components = branchName.split("/");
|
|
21
|
+
for (const component of components) {
|
|
22
|
+
if (component.length > 0 &&
|
|
23
|
+
(component.startsWith(".") || component.endsWith(".lock"))) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const matchPattern: (text: string, pattern: string) => boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const matchPattern = (text, pattern) => {
|
|
2
|
+
if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
|
|
3
|
+
try {
|
|
4
|
+
const regex = new RegExp(pattern.slice(1, pattern.lastIndexOf("/")), pattern.slice(pattern.lastIndexOf("/") + 1));
|
|
5
|
+
return regex.test(text);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return text === pattern;
|
|
12
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export const loadPackageJson = (importMetaUrl) => {
|
|
5
|
+
const filename = fileURLToPath(importMetaUrl);
|
|
6
|
+
const scriptDir = dirname(filename);
|
|
7
|
+
const packageJsonPath = join(scriptDir, "..", "package.json");
|
|
8
|
+
const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
|
|
9
|
+
return JSON.parse(packageJsonContent);
|
|
10
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes a string by removing non-printable characters.
|
|
3
|
+
* This is useful for cleaning up output from external commands before displaying it in the terminal.
|
|
4
|
+
* It allows printable ASCII characters (space to tilde), newlines, carriage returns, and tabs.
|
|
5
|
+
* @param str The string to sanitize.
|
|
6
|
+
* @returns The sanitized string.
|
|
7
|
+
*/
|
|
8
|
+
export declare const sanitizeString: (str: string) => string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes a string by removing non-printable characters.
|
|
3
|
+
* This is useful for cleaning up output from external commands before displaying it in the terminal.
|
|
4
|
+
* It allows printable ASCII characters (space to tilde), newlines, carriage returns, and tabs.
|
|
5
|
+
* @param str The string to sanitize.
|
|
6
|
+
* @returns The sanitized string.
|
|
7
|
+
*/
|
|
8
|
+
export const sanitizeString = (str) => {
|
|
9
|
+
return str.replace(/[^ -~\n\r\t]/g, "");
|
|
10
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ag-toolkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared library for agbd and agrb",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/riya-amemiya/ag-toolkit.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/riya-amemiya/ag-toolkit#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/riya-amemiya/ag-toolkit/issues"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"dev": "tsc --watch",
|
|
23
|
+
"test": "biome check .",
|
|
24
|
+
"lint": "biome check . --write",
|
|
25
|
+
"ci": "biome ci ."
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"ink": "6.3.1",
|
|
35
|
+
"ink-select-input": "6.2.0",
|
|
36
|
+
"ink-spinner": "5.0.0",
|
|
37
|
+
"react": "19.1.1",
|
|
38
|
+
"simple-git": "3.28.0",
|
|
39
|
+
"valibot": "1.1.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@biomejs/biome": "2.2.4",
|
|
43
|
+
"@sindresorhus/tsconfig": "8.0.1",
|
|
44
|
+
"@types/bun": "1.2.22",
|
|
45
|
+
"@types/react": "19.1.13",
|
|
46
|
+
"chalk": "5.6.2",
|
|
47
|
+
"ink-testing-library": "4.0.0",
|
|
48
|
+
"ts-node": "10.9.2",
|
|
49
|
+
"typescript": "5.9.2"
|
|
50
|
+
}
|
|
51
|
+
}
|