knip 0.9.1 → 0.10.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 CHANGED
@@ -125,10 +125,12 @@ following types of issues:
125
125
  - `nsTypes` - Unused types in namespaces: did not find direct references to this exported variable (2)
126
126
  - `duplicates` - Duplicate exports: the same thing is exported more than once with different names from the same file
127
127
 
128
- 1. This may also include dependencies that could not be resolved properly (such as non-relative `local/dir/file.ts` not
129
- and `local` not being in `node_modules`).
128
+ 1. This includes dependencies that could not be resolved. For instance, what does `unresolved/dir/module` mean?
129
+ - To target something in the (missing) `node_modules/unresolved` package?
130
+ - Target a local module that should have a relative path?
131
+ - It does not match any `paths` entry in `tsconfig.json#compilerOptions`.
130
132
  2. The variable or type is not referenced directly, and has become a member of a namespace. That's why Knip is not sure
131
- whether this export can be removed, so please look into it:
133
+ whether this export can be removed, so please look into it.
132
134
 
133
135
  You can `--include` or `--exclude` any of the types to slice & dice the report to your needs. Alternatively, they can be
134
136
  added to the configuration (e.g. `"exclude": ["dependencies"]`).
@@ -140,8 +142,8 @@ As always, make sure to backup files or use Git before deleting files or making
140
142
  - Unused files can be removed.
141
143
  - Unused dependencies can be removed from `package.json`.
142
144
  - Unlisted dependencies should be added to `package.json`.
143
- - Unused exports and types: remove the `export` keyword in front of unused exports. Then you (or tools such as the
144
- TypeScript language server in VS Code and/or ESLint) can see whether the variable or type is used within the same
145
+ - Unused exports and types: remove the `export` keyword in front of unused exports. Then you (or tools such as
146
+ TypeScript language services in VS Code and/or ESLint) can see whether the variable or type is used within the same
145
147
  file. If this is not the case, it can be removed.
146
148
 
147
149
  🔁 Repeat the process to reveal new unused files and exports. Sometimes it's so liberating to remove things!
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { UnresolvedConfiguration } from './types';
2
- export declare const main: (options: UnresolvedConfiguration) => Promise<{
2
+ export declare const main: (unresolvedConfiguration: UnresolvedConfiguration) => Promise<{
3
3
  report: import("./types").Report;
4
4
  issues: import("./types").Issues;
5
5
  counters: import("./types").Counters;
package/dist/index.js CHANGED
@@ -13,10 +13,10 @@ const runner_1 = require("./runner");
13
13
  const errors_1 = require("./util/errors");
14
14
  const debug_1 = require("./util/debug");
15
15
  const progress_1 = require("./progress");
16
- const main = async (options) => {
17
- const { cwd, workingDir, configFilePath: configFilePathArg, tsConfigFilePath: tsConfigFilePathArg, include, exclude, ignore, gitignore, isIncludeEntryFiles, isDev, isShowProgress, jsDoc, debug, } = options;
18
- const updateMessage = (0, progress_1.getMessageUpdater)(options);
19
- (0, debug_1.debugLogObject)(options, 1, 'Unresolved configuration', options);
16
+ const main = async (unresolvedConfiguration) => {
17
+ const { cwd, workingDir, configFilePath: configFilePathArg, tsConfigFilePath: tsConfigFilePathArg, include, exclude, ignore, gitignore, isIncludeEntryFiles, isDev, isShowProgress, jsDoc, debug, } = unresolvedConfiguration;
18
+ const updateMessage = (0, progress_1.getMessageUpdater)(unresolvedConfiguration);
19
+ (0, debug_1.debugLogObject)(debug, 1, 'Unresolved configuration', unresolvedConfiguration);
20
20
  updateMessage('Reading configuration and manifest files...');
21
21
  const manifestPath = await (0, fs_1.findFile)(cwd, workingDir, 'package.json');
22
22
  const manifest = manifestPath && require(manifestPath);
@@ -31,10 +31,10 @@ const main = async (options) => {
31
31
  if (tsConfigFilePathArg && !resolvedTsConfigFilePath) {
32
32
  throw new errors_1.ConfigurationError(`Unable to find ${tsConfigFilePathArg}`);
33
33
  }
34
- let tsConfigPaths = [];
34
+ let tsConfigPathGlobs = [];
35
35
  if (resolvedTsConfigFilePath) {
36
36
  const config = typescript_1.default.readConfigFile(resolvedTsConfigFilePath, typescript_1.default.sys.readFile);
37
- tsConfigPaths = config.config.compilerOptions?.paths
37
+ tsConfigPathGlobs = config.config.compilerOptions?.paths
38
38
  ? Object.keys(config.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
39
39
  : [];
40
40
  if (config.error) {
@@ -43,7 +43,7 @@ const main = async (options) => {
43
43
  }
44
44
  const dir = (0, path_1.relative)(workingDir);
45
45
  const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfig, { workingDir: dir, isDev });
46
- (0, debug_1.debugLogObject)(options, 1, 'Resolved configuration', resolvedConfig);
46
+ (0, debug_1.debugLogObject)(debug, 1, 'Resolved configuration', resolvedConfig);
47
47
  if (!resolvedConfigFilePath && !manifest.knip && !resolvedTsConfigFilePath) {
48
48
  throw new errors_1.ConfigurationError(`Unable to find ${configFilePath} or package.json#knip or ${tsConfigFilePath}`);
49
49
  }
@@ -55,29 +55,31 @@ const main = async (options) => {
55
55
  : { compilerOptions: { allowJs: true } };
56
56
  updateMessage('Resolving entry files...');
57
57
  const entryPaths = await (0, path_1.resolvePaths)({
58
+ cwd,
58
59
  workingDir,
59
60
  patterns: resolvedConfig.entryFiles,
60
61
  ignore,
61
62
  gitignore,
62
63
  });
63
- (0, debug_1.debugLogFiles)(options, 1, 'Globbed entry paths', entryPaths);
64
+ (0, debug_1.debugLogFiles)(debug, 1, 'Globbed entry paths', entryPaths);
64
65
  const production = (0, project_1.createProject)({ ...projectOptions, ...skipAddFiles }, entryPaths);
65
66
  const entryFiles = production.getSourceFiles();
66
- (0, debug_1.debugLogSourceFiles)(options, 1, 'Included entry source files', entryFiles);
67
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Included entry source files', entryFiles);
67
68
  production.resolveSourceFileDependencies();
68
69
  const productionFiles = production.getSourceFiles();
69
- (0, debug_1.debugLogSourceFiles)(options, 1, 'Included production source files', productionFiles);
70
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Included production source files', productionFiles);
70
71
  updateMessage('Resolving project files...');
71
72
  const projectPaths = await (0, path_1.resolvePaths)({
73
+ cwd,
72
74
  workingDir,
73
75
  patterns: resolvedConfig.projectFiles,
74
76
  ignore,
75
77
  gitignore,
76
78
  });
77
- (0, debug_1.debugLogFiles)(options, 1, 'Globbed project paths', projectPaths);
79
+ (0, debug_1.debugLogFiles)(debug, 1, 'Globbed project paths', projectPaths);
78
80
  const project = (0, project_1.createProject)({ ...projectOptions, ...skipAddFiles }, projectPaths);
79
81
  const projectFiles = project.getSourceFiles();
80
- (0, debug_1.debugLogSourceFiles)(options, 1, 'Included project source files', projectFiles);
82
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Included project source files', projectFiles);
81
83
  return { entryFiles, productionFiles, projectFiles };
82
84
  }
83
85
  else {
@@ -100,7 +102,7 @@ const main = async (options) => {
100
102
  optionalDependencies: Object.keys(manifest.optionalDependencies ?? {}),
101
103
  devDependencies: Object.keys(manifest.devDependencies ?? {}),
102
104
  isDev: Boolean(resolvedConfig?.dev),
103
- tsConfigPaths,
105
+ tsConfigPathGlobs: tsConfigPathGlobs,
104
106
  isShowProgress,
105
107
  jsDocOptions: {
106
108
  isReadPublicTag: jsDoc.includes('public'),
@@ -108,7 +110,7 @@ const main = async (options) => {
108
110
  debug,
109
111
  };
110
112
  const { issues, counters } = await (0, runner_1.findIssues)(config);
111
- (0, debug_1.debugLogObject)(options, 2, 'Issues', issues);
113
+ (0, debug_1.debugLogObject)(debug, 2, 'Issues', issues);
112
114
  return { report, issues, counters };
113
115
  };
114
116
  exports.main = main;
package/dist/runner.js CHANGED
@@ -13,16 +13,16 @@ const dependencies_1 = require("./util/dependencies");
13
13
  const debug_1 = require("./util/debug");
14
14
  const progress_1 = require("./progress");
15
15
  async function findIssues(configuration) {
16
- const { workingDir, report, isDev, jsDocOptions } = configuration;
16
+ const { workingDir, report, isDev, jsDocOptions, debug } = configuration;
17
17
  const { entryFiles, productionFiles, projectFiles, isIncludeEntryFiles } = configuration;
18
18
  const updateMessage = (0, progress_1.getMessageUpdater)(configuration);
19
19
  const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);
20
20
  const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);
21
21
  const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
22
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used production files', usedProductionFiles);
23
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Unreferenced production files', unreferencedProductionFiles);
24
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used entry files', usedEntryFiles);
25
- (0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used non-entry files', usedNonEntryFiles);
22
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Used production files', usedProductionFiles);
23
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Unreferenced production files', unreferencedProductionFiles);
24
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Used entry files', usedEntryFiles);
25
+ (0, debug_1.debugLogSourceFiles)(debug, 1, 'Used non-entry files', usedNonEntryFiles);
26
26
  const issues = {
27
27
  files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),
28
28
  dependencies: new Set(),
@@ -50,7 +50,7 @@ async function findIssues(configuration) {
50
50
  const updateCounters = (0, progress_1.getCountersUpdater)(configuration, counters);
51
51
  const addSymbolIssue = (issueType, issue) => {
52
52
  const { filePath, symbol } = issue;
53
- const key = node_path_1.default.relative(workingDir, filePath);
53
+ const key = node_path_1.default.relative(workingDir, filePath).replace(/\\/g, '/');
54
54
  issues[issueType][key] = issues[issueType][key] ?? {};
55
55
  issues[issueType][key][symbol] = issue;
56
56
  counters[issueType]++;
package/dist/types.d.ts CHANGED
@@ -65,7 +65,7 @@ export declare type Configuration = {
65
65
  optionalDependencies: string[];
66
66
  devDependencies: string[];
67
67
  isDev: boolean;
68
- tsConfigPaths: string[];
68
+ tsConfigPathGlobs: string[];
69
69
  isShowProgress: boolean;
70
70
  jsDocOptions: {
71
71
  isReadPublicTag: boolean;
@@ -1,6 +1,6 @@
1
- import type { ImportedConfiguration, LocalConfiguration } from '../types';
1
+ import type { ImportedConfiguration, LocalConfiguration, Report } from '../types';
2
2
  export declare const resolveConfig: (importedConfiguration: ImportedConfiguration, options?: {
3
3
  workingDir?: string;
4
4
  isDev?: boolean;
5
5
  }) => LocalConfiguration | undefined;
6
- export declare const resolveIncludedIssueTypes: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => import("../types").Report;
6
+ export declare const resolveIncludedIssueTypes: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => Report;
@@ -31,15 +31,13 @@ exports.resolveConfig = resolveConfig;
31
31
  const resolveIncludedIssueTypes = (includeArg, excludeArg, resolvedConfig) => {
32
32
  const deps = resolvedConfig?.dev ? ['dependencies', 'devDependencies'] : ['dependencies'];
33
33
  const groups = ['files', ...deps, 'unlisted', 'exports', 'types', 'nsExports', 'nsTypes', 'duplicates'];
34
- const include = [includeArg, resolvedConfig?.include ?? []]
35
- .flat()
36
- .map(value => value.split(','))
37
- .flat();
38
- const exclude = [excludeArg, resolvedConfig?.exclude ?? []]
39
- .flat()
40
- .map(value => value.split(','))
41
- .flat();
42
- const includes = (include.length > 0 ? include : groups).filter(group => !exclude.includes(group));
43
- return groups.reduce((r, group) => ((r[group] = includes.includes(group)), r), {});
34
+ const normalizedIncludesArg = includeArg.map(value => value.split(',')).flat();
35
+ const normalizedExcludesArg = excludeArg.map(value => value.split(',')).flat();
36
+ const excludes = (resolvedConfig?.exclude ?? []).filter(exclude => !normalizedIncludesArg.includes(exclude));
37
+ const includes = (resolvedConfig?.include ?? []).filter(include => !normalizedExcludesArg.includes(include));
38
+ const include = [normalizedIncludesArg, includes].flat();
39
+ const exclude = [normalizedExcludesArg, excludes].flat();
40
+ const included = (include.length > 0 ? include : groups).filter(group => !exclude.includes(group));
41
+ return groups.reduce((types, group) => ((types[group] = included.includes(group)), types), {});
44
42
  };
45
43
  exports.resolveIncludedIssueTypes = resolveIncludedIssueTypes;
@@ -1,11 +1,9 @@
1
1
  import type { SourceFile } from 'ts-morph';
2
- declare type Config = {
3
- debug: {
4
- isEnabled: boolean;
5
- level: number;
6
- };
2
+ declare type Debug = {
3
+ isEnabled: boolean;
4
+ level: number;
7
5
  };
8
- export declare const debugLogObject: (config: Config, minimumLevel: number, name: string, obj: unknown) => void;
9
- export declare const debugLogFiles: (config: Config, minimumLevel: number, name: string, filePaths: string[]) => void;
10
- export declare const debugLogSourceFiles: (config: Config, minimumLevel: number, name: string, sourceFiles: SourceFile[]) => void;
6
+ export declare const debugLogObject: (debug: Debug, minimumLevel: number, name: string, obj: unknown) => void;
7
+ export declare const debugLogFiles: (debug: Debug, minimumLevel: number, name: string, filePaths: string[]) => void;
8
+ export declare const debugLogSourceFiles: (debug: Debug, minimumLevel: number, name: string, sourceFiles: SourceFile[]) => void;
11
9
  export {};
@@ -6,17 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.debugLogSourceFiles = exports.debugLogFiles = exports.debugLogObject = void 0;
7
7
  const node_util_1 = __importDefault(require("node:util"));
8
8
  const logArray = (collection) => console.log(node_util_1.default.inspect(collection, { maxArrayLength: null }));
9
- const debugLogObject = (config, minimumLevel, name, obj) => {
10
- if (minimumLevel > config.debug.level)
9
+ const debugLogObject = (debug, minimumLevel, name, obj) => {
10
+ if (minimumLevel > debug.level)
11
11
  return;
12
12
  console.log(`[knip] ${name}:`);
13
13
  console.log(node_util_1.default.inspect(obj, { depth: null, colors: true }));
14
14
  };
15
15
  exports.debugLogObject = debugLogObject;
16
- const debugLogFiles = (config, minimumLevel, name, filePaths) => {
17
- if (minimumLevel > config.debug.level)
16
+ const debugLogFiles = (debug, minimumLevel, name, filePaths) => {
17
+ if (minimumLevel > debug.level)
18
18
  return;
19
- const { debug } = config;
20
19
  if (debug.level > 1) {
21
20
  console.debug(`[knip] ${name} (${filePaths.length}):`);
22
21
  logArray(filePaths);
@@ -26,10 +25,9 @@ const debugLogFiles = (config, minimumLevel, name, filePaths) => {
26
25
  }
27
26
  };
28
27
  exports.debugLogFiles = debugLogFiles;
29
- const debugLogSourceFiles = (config, minimumLevel, name, sourceFiles) => {
30
- if (minimumLevel > config.debug.level)
28
+ const debugLogSourceFiles = (debug, minimumLevel, name, sourceFiles) => {
29
+ if (minimumLevel > debug.level)
31
30
  return;
32
- const { debug } = config;
33
31
  if (debug.level > 1) {
34
32
  console.debug(`[knip] ${name} (${sourceFiles.length}):`);
35
33
  logArray(sourceFiles.map(sourceFile => sourceFile.getFilePath()));
@@ -39,8 +37,8 @@ const debugLogSourceFiles = (config, minimumLevel, name, sourceFiles) => {
39
37
  }
40
38
  };
41
39
  exports.debugLogSourceFiles = debugLogSourceFiles;
42
- const debugLogDiff = (config, minimumLevel, name, arrA, arrB) => {
43
- if (minimumLevel > config.debug.level)
40
+ const debugLogDiff = (debug, minimumLevel, name, arrA, arrB) => {
41
+ if (minimumLevel > debug.level)
44
42
  return;
45
43
  const onlyInA = arrA.filter(itemA => !arrB.includes(itemA)).sort();
46
44
  const onlyInB = arrB.filter(itemB => !arrA.includes(itemB)).sort();
@@ -7,29 +7,35 @@ exports.getDependencyAnalyzer = void 0;
7
7
  const ts_morph_1 = require("ts-morph");
8
8
  const is_builtin_module_1 = __importDefault(require("is-builtin-module"));
9
9
  const micromatch_1 = __importDefault(require("micromatch"));
10
+ const ts_morph_helpers_1 = require("ts-morph-helpers");
10
11
  const compact = (collection) => Array.from(new Set(collection)).filter((value) => Boolean(value));
12
+ const findRequireModuleSpecifiers = (sourceFile) => (0, ts_morph_helpers_1.findCallExpressionsByName)(sourceFile, 'require').map(expression => expression.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.StringLiteral));
13
+ const isExternalDependency = (moduleSpecifier, tsConfigPathGlobs) => {
14
+ if (moduleSpecifier.startsWith('.'))
15
+ return false;
16
+ if ((0, is_builtin_module_1.default)(moduleSpecifier))
17
+ return false;
18
+ if (tsConfigPathGlobs.length > 0 && micromatch_1.default.isMatch(moduleSpecifier, tsConfigPathGlobs))
19
+ return false;
20
+ return true;
21
+ };
22
+ const resolvePackageName = (moduleSpecifier) => {
23
+ const parts = moduleSpecifier.split('/').slice(0, 2);
24
+ return moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
25
+ };
11
26
  const getDependencyAnalyzer = (configuration) => {
12
- const { dependencies, devDependencies, peerDependencies, optionalDependencies, tsConfigPaths } = configuration;
27
+ const { dependencies, devDependencies, peerDependencies, optionalDependencies, tsConfigPathGlobs } = configuration;
13
28
  const productionDependencies = [...dependencies, ...peerDependencies, ...optionalDependencies];
14
29
  const referencedDependencies = new Set();
15
30
  const getUnresolvedDependencies = (sourceFile) => {
16
31
  const unresolvedDependencies = new Set();
17
32
  const importLiterals = sourceFile.getImportStringLiterals();
18
- const requires = sourceFile
19
- .getDescendantsOfKind(ts_morph_1.ts.SyntaxKind.CallExpression)
20
- .filter(callExpression => callExpression.getExpression().getText() === 'require')
21
- .map(expression => expression.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.StringLiteral));
22
- const literals = compact([importLiterals, requires].flat());
23
- literals.forEach(importLiteral => {
24
- const moduleSpecifier = importLiteral.getLiteralText();
25
- if (moduleSpecifier.startsWith('.'))
26
- return;
27
- if ((0, is_builtin_module_1.default)(moduleSpecifier))
28
- return;
29
- if (tsConfigPaths.length > 0 && micromatch_1.default.isMatch(moduleSpecifier, tsConfigPaths))
33
+ const requireCallExpressions = findRequireModuleSpecifiers(sourceFile);
34
+ const moduleSpecifiers = compact([importLiterals, requireCallExpressions].flat()).map(i => i.getLiteralText());
35
+ moduleSpecifiers.forEach(moduleSpecifier => {
36
+ if (!isExternalDependency(moduleSpecifier, tsConfigPathGlobs))
30
37
  return;
31
- const parts = moduleSpecifier.split('/').slice(0, 2);
32
- const packageName = moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
38
+ const packageName = resolvePackageName(moduleSpecifier);
33
39
  if (!productionDependencies.includes(packageName) && !devDependencies.includes(packageName)) {
34
40
  unresolvedDependencies.add({ filePath: sourceFile.getFilePath(), symbol: moduleSpecifier });
35
41
  }
@@ -1,5 +1,6 @@
1
1
  export declare const relative: (to: string) => string;
2
- export declare const resolvePaths: ({ workingDir, patterns, ignore, gitignore, }: {
2
+ export declare const resolvePaths: ({ cwd, workingDir, patterns, ignore, gitignore, }: {
3
+ cwd: string;
3
4
  workingDir: string;
4
5
  patterns: string[];
5
6
  ignore: string[];
package/dist/util/path.js CHANGED
@@ -18,12 +18,12 @@ const glob = async function (patterns, options) {
18
18
  };
19
19
  const prependDirToPattern = (workingDir, pattern) => {
20
20
  if (pattern.startsWith('!'))
21
- return '!' + node_path_1.default.join(workingDir, pattern.slice(1));
22
- return node_path_1.default.join(workingDir, pattern);
21
+ return '!' + node_path_1.default.posix.join(workingDir, pattern.slice(1));
22
+ return node_path_1.default.posix.join(workingDir, pattern);
23
23
  };
24
- const resolvePaths = async ({ workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => prependDirToPattern((0, exports.relative)(workingDir), pattern)), {
24
+ const resolvePaths = async ({ cwd, workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => prependDirToPattern(node_path_1.default.posix.relative(cwd, workingDir), pattern)), {
25
25
  cwd,
26
- ignore,
26
+ ignore: [...ignore, '**/node_modules'],
27
27
  gitignore,
28
28
  absolute: true,
29
29
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript project",
5
5
  "keywords": [
6
6
  "find",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "scripts": {
27
27
  "knip": "node ./dist/cli.js --jsdoc public",
28
- "test": "node --loader tsx --test test/*.spec.ts",
28
+ "test": "globstar -- node --loader tsx --test \"test/*.spec.ts\"",
29
29
  "watch": "tsc --watch",
30
30
  "build": "rm -rf dist && tsc",
31
31
  "prepublishOnly": "npm test && npm run build && npm run knip",
@@ -50,6 +50,7 @@
50
50
  "devDependencies": {
51
51
  "@types/micromatch": "4.0.2",
52
52
  "@types/node": "18.11.2",
53
+ "globstar": "1.0.0",
53
54
  "prettier": "2.7.1",
54
55
  "release-it": "15.5.0",
55
56
  "tsx": "3.10.3",