node-modules-tools 0.0.0 → 0.0.2

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.
@@ -1,5 +1,14 @@
1
+ import { relative, dirname } from 'pathe';
1
2
  import { x } from 'tinyexec';
2
3
 
4
+ async function resolveRoot(options) {
5
+ const raw = await x("pnpm", ["root", "-w"], { throwOnError: true, nodeOptions: { cwd: options.cwd } });
6
+ return dirname(raw.stdout.trim());
7
+ }
8
+ async function getPnpmVersion(options) {
9
+ const raw = await x("pnpm", ["--version"], { throwOnError: true, nodeOptions: { cwd: options.cwd } });
10
+ return raw.stdout.trim();
11
+ }
3
12
  async function getDependenciesTree(options) {
4
13
  const args = ["ls", "--json", "--no-optional", "--depth", String(options.depth)];
5
14
  if (options.monorepo)
@@ -9,71 +18,92 @@ async function getDependenciesTree(options) {
9
18
  return tree;
10
19
  }
11
20
  async function listPackageDependencies(options) {
21
+ const root = await resolveRoot(options) || options.cwd;
12
22
  const tree = await getDependenciesTree(options);
13
- const specs = /* @__PURE__ */ new Map();
14
- const map = /* @__PURE__ */ new WeakMap();
23
+ const packages = /* @__PURE__ */ new Map();
24
+ const workspacePackages = tree.map((pkg) => {
25
+ let name = pkg.name;
26
+ if (!name) {
27
+ let path = relative(root, pkg.path);
28
+ if (path === ".")
29
+ path = "";
30
+ const suffix = path.toLowerCase().replace(/[^a-z0-9-]+/g, "_").slice(0, 20);
31
+ name = suffix ? `#workspace-${suffix}` : "#workspace-root";
32
+ }
33
+ const version = pkg.version || "0.0.0";
34
+ const node = {
35
+ spec: `${name}@${version}`,
36
+ name,
37
+ version,
38
+ filepath: pkg.path,
39
+ dependencies: /* @__PURE__ */ new Set(),
40
+ workspace: true
41
+ };
42
+ if (pkg.private)
43
+ node.private = true;
44
+ packages.set(node.spec, node);
45
+ return {
46
+ pkg,
47
+ node
48
+ };
49
+ });
50
+ const mapNormalize = /* @__PURE__ */ new WeakMap();
15
51
  function normalize(raw) {
16
- let node = map.get(raw);
52
+ let node = mapNormalize.get(raw);
17
53
  if (node)
18
54
  return node;
19
- node = {
20
- spec: `${raw.from}@${raw.version}`,
55
+ let version = raw.version;
56
+ if (version.includes(":")) {
57
+ const workspaceMapping = workspacePackages.find((i) => i.pkg.path === raw.path);
58
+ if (workspaceMapping)
59
+ version = workspaceMapping.node.version;
60
+ }
61
+ const spec = `${raw.from}@${version}`;
62
+ node = packages.get(spec) || {
63
+ spec,
21
64
  name: raw.from,
22
- version: raw.version,
23
- path: raw.path,
24
- dependencies: /* @__PURE__ */ new Set(),
25
- dependents: /* @__PURE__ */ new Set(),
26
- flatDependents: /* @__PURE__ */ new Set(),
27
- flatDependencies: /* @__PURE__ */ new Set(),
28
- nestedLevels: /* @__PURE__ */ new Set(),
29
- dev: false,
30
- prod: false,
31
- optional: false
65
+ version,
66
+ filepath: raw.path,
67
+ dependencies: /* @__PURE__ */ new Set()
32
68
  };
33
- map.set(raw, node);
69
+ mapNormalize.set(raw, node);
34
70
  return node;
35
71
  }
36
- function traverse(_node, level, mode, directImporter, nestedImporter) {
37
- if (_node.from.startsWith("@types"))
38
- return;
39
- const node = normalize(_node);
40
- if (directImporter)
41
- node.dependents.add(directImporter);
42
- for (const im of nestedImporter)
43
- node.flatDependents.add(im);
44
- node.nestedLevels.add(level);
45
- if (mode === "dev")
46
- node.dev = true;
47
- if (mode === "prod")
48
- node.prod = true;
49
- if (mode === "optional")
50
- node.optional = true;
72
+ function traverse(raw, level, mode) {
73
+ const node = normalize(raw);
74
+ if (!node.workspace) {
75
+ if (mode === "dev")
76
+ node.dev = true;
77
+ if (mode === "prod")
78
+ node.prod = true;
79
+ if (mode === "optional")
80
+ node.optional = true;
81
+ }
51
82
  if (options.traverseFilter?.(node) === false)
52
- return;
53
- if (specs.has(node.spec))
54
- return;
55
- specs.set(node.spec, node);
56
- for (const dep of Object.values(_node.dependencies || {})) {
57
- traverse(dep, level + 1, mode, node.spec, [...nestedImporter, node.spec]);
83
+ return node;
84
+ if (packages.has(node.spec))
85
+ return node;
86
+ packages.set(node.spec, node);
87
+ for (const dep of Object.values(raw.dependencies || {})) {
88
+ const resolvedDep = traverse(dep, level + 1, mode);
89
+ node.dependencies.add(resolvedDep.spec);
58
90
  }
91
+ return node;
59
92
  }
60
- for (const pkg of tree) {
93
+ for (const { pkg, node } of workspacePackages) {
61
94
  for (const dep of Object.values(pkg.dependencies || {})) {
62
- traverse(dep, 1, "prod", undefined, []);
95
+ const result = traverse(dep, 1, "prod");
96
+ node.dependencies.add(result.spec);
63
97
  }
64
98
  for (const dep of Object.values(pkg.devDependencies || {})) {
65
- traverse(dep, 1, "dev", undefined, []);
66
- }
67
- }
68
- const packages = [...specs.values()].sort((a, b) => a.spec.localeCompare(b.spec));
69
- for (const pkg of packages) {
70
- for (const dep of pkg.flatDependents) {
71
- const node = specs.get(dep);
72
- if (node)
73
- node.flatDependencies.add(pkg.spec);
99
+ const result = traverse(dep, 1, "dev");
100
+ node.dependencies.add(result.spec);
74
101
  }
75
102
  }
76
103
  return {
104
+ root,
105
+ packageManager: "pnpm",
106
+ packageManagerVersion: await getPnpmVersion(options),
77
107
  packages
78
108
  };
79
109
  }
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  type PackageModuleTypeSimple = 'cjs' | 'esm';
2
- type PackageModuleType = 'cjs' | 'esm' | 'dual' | 'faux';
2
+ type PackageModuleType = 'cjs' | 'esm' | 'dual' | 'faux' | 'dts';
3
3
  interface ListPackageDependenciesOptions {
4
4
  /**
5
5
  * Current working directory
@@ -16,12 +16,21 @@ interface ListPackageDependenciesOptions {
16
16
  /**
17
17
  * Filter if a package should be included and continue traversing
18
18
  */
19
- traverseFilter?: (node: PackageNode) => boolean;
19
+ traverseFilter?: (node: PackageNodeRaw) => boolean;
20
20
  }
21
- interface ListPackageDependenciesResult {
22
- packages: PackageNode[];
21
+ interface ListPackageDependenciesRawResult {
22
+ root: string;
23
+ packageManager: string;
24
+ packageManagerVersion?: string;
25
+ packages: Map<string, PackageNodeRaw>;
23
26
  }
24
- interface PackageNode {
27
+ interface ListPackageDependenciesBaseResult extends ListPackageDependenciesRawResult {
28
+ packages: Map<string, PackageNodeBase>;
29
+ }
30
+ interface ListPackageDependenciesResult extends ListPackageDependenciesBaseResult {
31
+ packages: Map<string, PackageNode>;
32
+ }
33
+ interface PackageNodeRaw {
25
34
  /** Package Name */
26
35
  name: string;
27
36
  /** Version */
@@ -29,41 +38,59 @@ interface PackageNode {
29
38
  /** Combined name and version using `@` */
30
39
  spec: string;
31
40
  /** Absolute file path of the package */
32
- path: string;
41
+ filepath: string;
33
42
  /** Direct dependencies of this package */
34
43
  dependencies: Set<string>;
44
+ /** Is this package from local workspace */
45
+ workspace?: boolean;
46
+ /** Is this package private */
47
+ private?: boolean;
48
+ /** Is this package part of devDependencies */
49
+ dev?: boolean;
50
+ /** Is this package part of dependencies */
51
+ prod?: boolean;
52
+ /** Is this package part of optionalDependencies */
53
+ optional?: boolean;
54
+ }
55
+ interface PackageNodeBase extends PackageNodeRaw {
35
56
  /** Direct dependents of this package */
36
57
  dependents: Set<string>;
58
+ /** The lowest depth of this package */
59
+ depth: number;
37
60
  /** All nested dependencies of this package */
38
61
  flatDependencies: Set<string>;
39
62
  /** All nested dependents of this package */
40
63
  flatDependents: Set<string>;
41
- /** Nested levels of this package */
42
- nestedLevels: Set<number>;
43
- /** Is this package part of devDependencies */
44
- dev: boolean;
45
- /** Is this package part of dependencies */
46
- prod: boolean;
47
- /** Is this package part of optionalDependencies */
48
- optional: boolean;
49
64
  }
50
- interface ResolvedPackageNode extends PackageNode {
51
- module: PackageModuleType;
65
+ interface PackageNode extends PackageNodeBase {
66
+ resolved: {
67
+ module: PackageModuleType;
68
+ license?: string;
69
+ author?: string;
70
+ repository?: string;
71
+ homepage?: string;
72
+ engines?: Record<string, string>;
73
+ };
52
74
  }
53
75
 
54
- /**
55
- * Analyze a package node, and return a resolved package node.
56
- * This function mutates the input package node.
57
- *
58
- * - Set `module` to the resolved module type (cjs, esm, dual, faux).
59
- */
60
- declare function analyzePackage(pkg: PackageNode): Promise<ResolvedPackageNode>;
76
+ declare const PackageModuleTypes: readonly PackageModuleType[];
61
77
 
62
78
  /**
63
79
  * List dependencies of packages in the current project.
64
80
  *
65
81
  * This function will automatically detect the package manager in the current project, and list the dependencies of the packages.
66
82
  */
83
+ declare function listPackageDependenciesRaw(options: ListPackageDependenciesOptions): Promise<ListPackageDependenciesRawResult>;
67
84
  declare function listPackageDependencies(options: ListPackageDependenciesOptions): Promise<ListPackageDependenciesResult>;
68
85
 
69
- export { type ListPackageDependenciesOptions, type ListPackageDependenciesResult, type PackageModuleType, type PackageModuleTypeSimple, type PackageNode, type ResolvedPackageNode, analyzePackage, listPackageDependencies };
86
+ /**
87
+ * Analyze a package node, and return a resolved package node.
88
+ * This function mutates the input package node.
89
+ *
90
+ * - Set `module` to the resolved module type (cjs, esm, dual, faux, none).
91
+ */
92
+ declare function resolvePackage(pkg: PackageNodeBase): Promise<PackageNode>;
93
+
94
+ declare function stripBomTag(content: string): string;
95
+
96
+ export { type ListPackageDependenciesBaseResult, type ListPackageDependenciesOptions, type ListPackageDependenciesRawResult, type ListPackageDependenciesResult, type PackageModuleType, type PackageModuleTypeSimple, PackageModuleTypes, type PackageNode, type PackageNodeBase, type PackageNodeRaw, listPackageDependencies, listPackageDependenciesRaw, resolvePackage, stripBomTag };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  type PackageModuleTypeSimple = 'cjs' | 'esm';
2
- type PackageModuleType = 'cjs' | 'esm' | 'dual' | 'faux';
2
+ type PackageModuleType = 'cjs' | 'esm' | 'dual' | 'faux' | 'dts';
3
3
  interface ListPackageDependenciesOptions {
4
4
  /**
5
5
  * Current working directory
@@ -16,12 +16,21 @@ interface ListPackageDependenciesOptions {
16
16
  /**
17
17
  * Filter if a package should be included and continue traversing
18
18
  */
19
- traverseFilter?: (node: PackageNode) => boolean;
19
+ traverseFilter?: (node: PackageNodeRaw) => boolean;
20
20
  }
21
- interface ListPackageDependenciesResult {
22
- packages: PackageNode[];
21
+ interface ListPackageDependenciesRawResult {
22
+ root: string;
23
+ packageManager: string;
24
+ packageManagerVersion?: string;
25
+ packages: Map<string, PackageNodeRaw>;
23
26
  }
24
- interface PackageNode {
27
+ interface ListPackageDependenciesBaseResult extends ListPackageDependenciesRawResult {
28
+ packages: Map<string, PackageNodeBase>;
29
+ }
30
+ interface ListPackageDependenciesResult extends ListPackageDependenciesBaseResult {
31
+ packages: Map<string, PackageNode>;
32
+ }
33
+ interface PackageNodeRaw {
25
34
  /** Package Name */
26
35
  name: string;
27
36
  /** Version */
@@ -29,41 +38,59 @@ interface PackageNode {
29
38
  /** Combined name and version using `@` */
30
39
  spec: string;
31
40
  /** Absolute file path of the package */
32
- path: string;
41
+ filepath: string;
33
42
  /** Direct dependencies of this package */
34
43
  dependencies: Set<string>;
44
+ /** Is this package from local workspace */
45
+ workspace?: boolean;
46
+ /** Is this package private */
47
+ private?: boolean;
48
+ /** Is this package part of devDependencies */
49
+ dev?: boolean;
50
+ /** Is this package part of dependencies */
51
+ prod?: boolean;
52
+ /** Is this package part of optionalDependencies */
53
+ optional?: boolean;
54
+ }
55
+ interface PackageNodeBase extends PackageNodeRaw {
35
56
  /** Direct dependents of this package */
36
57
  dependents: Set<string>;
58
+ /** The lowest depth of this package */
59
+ depth: number;
37
60
  /** All nested dependencies of this package */
38
61
  flatDependencies: Set<string>;
39
62
  /** All nested dependents of this package */
40
63
  flatDependents: Set<string>;
41
- /** Nested levels of this package */
42
- nestedLevels: Set<number>;
43
- /** Is this package part of devDependencies */
44
- dev: boolean;
45
- /** Is this package part of dependencies */
46
- prod: boolean;
47
- /** Is this package part of optionalDependencies */
48
- optional: boolean;
49
64
  }
50
- interface ResolvedPackageNode extends PackageNode {
51
- module: PackageModuleType;
65
+ interface PackageNode extends PackageNodeBase {
66
+ resolved: {
67
+ module: PackageModuleType;
68
+ license?: string;
69
+ author?: string;
70
+ repository?: string;
71
+ homepage?: string;
72
+ engines?: Record<string, string>;
73
+ };
52
74
  }
53
75
 
54
- /**
55
- * Analyze a package node, and return a resolved package node.
56
- * This function mutates the input package node.
57
- *
58
- * - Set `module` to the resolved module type (cjs, esm, dual, faux).
59
- */
60
- declare function analyzePackage(pkg: PackageNode): Promise<ResolvedPackageNode>;
76
+ declare const PackageModuleTypes: readonly PackageModuleType[];
61
77
 
62
78
  /**
63
79
  * List dependencies of packages in the current project.
64
80
  *
65
81
  * This function will automatically detect the package manager in the current project, and list the dependencies of the packages.
66
82
  */
83
+ declare function listPackageDependenciesRaw(options: ListPackageDependenciesOptions): Promise<ListPackageDependenciesRawResult>;
67
84
  declare function listPackageDependencies(options: ListPackageDependenciesOptions): Promise<ListPackageDependenciesResult>;
68
85
 
69
- export { type ListPackageDependenciesOptions, type ListPackageDependenciesResult, type PackageModuleType, type PackageModuleTypeSimple, type PackageNode, type ResolvedPackageNode, analyzePackage, listPackageDependencies };
86
+ /**
87
+ * Analyze a package node, and return a resolved package node.
88
+ * This function mutates the input package node.
89
+ *
90
+ * - Set `module` to the resolved module type (cjs, esm, dual, faux, none).
91
+ */
92
+ declare function resolvePackage(pkg: PackageNodeBase): Promise<PackageNode>;
93
+
94
+ declare function stripBomTag(content: string): string;
95
+
96
+ export { type ListPackageDependenciesBaseResult, type ListPackageDependenciesOptions, type ListPackageDependenciesRawResult, type ListPackageDependenciesResult, type PackageModuleType, type PackageModuleTypeSimple, PackageModuleTypes, type PackageNode, type PackageNodeBase, type PackageNodeRaw, listPackageDependencies, listPackageDependenciesRaw, resolvePackage, stripBomTag };
package/dist/index.mjs CHANGED
@@ -1,8 +1,10 @@
1
- import fs from 'node:fs/promises';
2
- import { join } from 'node:path';
3
1
  import { detect } from 'package-manager-detector';
2
+ import fs from 'node:fs/promises';
3
+ import { join } from 'pathe';
4
4
 
5
- function analyzePackageJson(pkgJson) {
5
+ const PackageModuleTypes = Object.freeze(["cjs", "esm", "dual", "faux", "dts"]);
6
+
7
+ function analyzePackageModuleType(pkgJson) {
6
8
  const { exports, main, type } = pkgJson;
7
9
  let cjs;
8
10
  let esm;
@@ -30,12 +32,19 @@ function analyzePackageJson(pkgJson) {
30
32
  if (cjs === undefined && esm === undefined) {
31
33
  if (type === "module" || main && /\.mjs$/.test(main)) {
32
34
  esm = true;
33
- } else {
35
+ } else if (main) {
34
36
  cjs = true;
35
37
  }
36
38
  }
37
- const style = esm && cjs ? "dual" : esm ? "esm" : fauxEsm ? "faux" : "cjs";
38
- return style;
39
+ if (esm && cjs)
40
+ return "dual";
41
+ if (esm)
42
+ return "esm";
43
+ if (fauxEsm)
44
+ return "faux";
45
+ if (!esm && !cjs && !pkgJson.main && !pkgJson.exports && pkgJson.types)
46
+ return "dts";
47
+ return "cjs";
39
48
  function analyzeThing(value, path) {
40
49
  if (value && typeof value === "object") {
41
50
  if (Array.isArray(value)) {
@@ -100,26 +109,115 @@ function stripBomTag(content) {
100
109
  return content;
101
110
  }
102
111
 
103
- async function analyzePackage(pkg) {
112
+ async function resolvePackage(pkg) {
104
113
  const _pkg = pkg;
105
- if (_pkg.module)
114
+ if (_pkg.resolved)
106
115
  return _pkg;
107
- const content = await fs.readFile(join(pkg.path, "package.json"), "utf-8");
116
+ const content = await fs.readFile(join(pkg.filepath, "package.json"), "utf-8");
108
117
  const json = JSON.parse(stripBomTag(content));
109
- _pkg.module = analyzePackageJson(json);
118
+ let repository = typeof json.repository === "string" ? json.repository : json.repository?.url;
119
+ if (repository?.startsWith("git+"))
120
+ repository = repository.slice(4);
121
+ if (repository?.endsWith(".git"))
122
+ repository = repository.slice(0, -4);
123
+ if (repository?.startsWith("git://"))
124
+ repository = `https://${repository.slice(6)}`;
125
+ if (json.repository && typeof json.repository !== "string" && json.repository.directory)
126
+ repository += `/tree/HEAD/${json.repository.directory}`;
127
+ _pkg.resolved = {
128
+ module: analyzePackageModuleType(json),
129
+ engines: json.engines,
130
+ license: json.license,
131
+ author: typeof json.author === "string" ? json.author : json.author?.url,
132
+ repository,
133
+ homepage: json.homepage
134
+ };
110
135
  return _pkg;
111
136
  }
112
137
 
113
- async function listPackageDependencies(options) {
138
+ async function listPackageDependenciesRaw(options) {
114
139
  const manager = await detect({
115
140
  cwd: options.cwd
116
141
  });
117
142
  if (!manager)
118
143
  throw new Error("Cannot detect package manager in the current patch");
144
+ let result;
119
145
  if (manager.name === "pnpm")
120
- return await import('./chunks/pnpm.mjs').then((r) => r.listPackageDependencies(options));
146
+ result = await import('./chunks/pnpm.mjs').then((r) => r.listPackageDependencies(options));
121
147
  else
122
148
  throw new Error(`Package manager ${manager.name} is not yet supported`);
149
+ return populateRawResult(result);
150
+ }
151
+ function populateRawResult(input) {
152
+ const result = {
153
+ ...input,
154
+ packages: /* @__PURE__ */ new Map()
155
+ };
156
+ for (const [spec, pkg] of input.packages) {
157
+ const node = Object.assign(pkg, {
158
+ dependents: /* @__PURE__ */ new Set(),
159
+ flatDependencies: /* @__PURE__ */ new Set(),
160
+ flatDependents: /* @__PURE__ */ new Set(),
161
+ depth: pkg.workspace ? 0 : Infinity
162
+ });
163
+ result.packages.set(spec, node);
164
+ }
165
+ for (const pkg of result.packages.values()) {
166
+ for (const dep of pkg.dependencies) {
167
+ result.packages.get(dep)?.dependents.add(pkg.spec);
168
+ }
169
+ }
170
+ function resloveFlatDependencies(pkg) {
171
+ const postTasks = [];
172
+ function traverseDependencies(node, seen = /* @__PURE__ */ new Set()) {
173
+ for (const dep of node.dependencies) {
174
+ const level = node.depth + 1;
175
+ const depNode = result.packages.get(dep);
176
+ if (!node.workspace) {
177
+ if (node.dev)
178
+ depNode.dev = true;
179
+ if (node.prod)
180
+ depNode.prod = true;
181
+ if (node.optional)
182
+ depNode.optional = true;
183
+ }
184
+ if (depNode.depth > level)
185
+ depNode.depth = level;
186
+ if (seen.has(depNode))
187
+ continue;
188
+ pkg.flatDependencies.add(dep);
189
+ seen.add(depNode);
190
+ postTasks.push(() => {
191
+ depNode.flatDependents.add(pkg.spec);
192
+ });
193
+ traverseDependencies(depNode, seen);
194
+ }
195
+ }
196
+ function traverseDependents(node) {
197
+ for (const dep of node.dependents) {
198
+ if (pkg.flatDependents.has(dep))
199
+ continue;
200
+ pkg.flatDependents.add(dep);
201
+ const parentNode = result.packages.get(dep);
202
+ postTasks.push(() => {
203
+ parentNode.flatDependencies.add(pkg.spec);
204
+ });
205
+ traverseDependents(parentNode);
206
+ }
207
+ }
208
+ traverseDependencies(pkg);
209
+ traverseDependents(pkg);
210
+ for (const task of postTasks)
211
+ task();
212
+ }
213
+ for (const pkg of result.packages.values())
214
+ resloveFlatDependencies(pkg);
215
+ return result;
216
+ }
217
+ async function listPackageDependencies(options) {
218
+ const result = await listPackageDependenciesRaw(options);
219
+ await Promise.all(Array.from(result.packages.values()).map(async (pkg) => resolvePackage(pkg)));
220
+ return result;
123
221
  }
124
222
 
125
- export { analyzePackage, listPackageDependencies };
223
+ export { PackageModuleTypes, listPackageDependencies, listPackageDependenciesRaw, resolvePackage, stripBomTag };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-modules-tools",
3
3
  "type": "module",
4
- "version": "0.0.0",
4
+ "version": "0.0.2",
5
5
  "description": "Tools for inspecting node_modules",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -33,18 +33,19 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "package-manager-detector": "^0.2.8",
36
+ "package-manager-detector": "^0.2.9",
37
+ "pathe": "^2.0.2",
37
38
  "tinyexec": "^0.3.2"
38
39
  },
39
40
  "devDependencies": {
40
- "@pnpm/list": "^1000.0.5",
41
- "@pnpm/types": "^1000.1.0",
41
+ "@pnpm/list": "^1000.0.6",
42
+ "@pnpm/types": "^1000.1.1",
42
43
  "pkg-types": "^1.3.1",
43
44
  "unbuild": "^3.3.1"
44
45
  },
45
46
  "scripts": {
46
47
  "build": "unbuild",
47
- "dev": "unbuild --stub",
48
+ "stub": "unbuild --stub",
48
49
  "start": "tsx src/index.ts",
49
50
  "test": "vitest"
50
51
  }