@augment-vir/node 31.44.0 → 31.46.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/dist/augments/fs/grep.d.ts +183 -0
- package/dist/augments/fs/grep.js +153 -0
- package/dist/augments/npm/npm-deps.d.ts +38 -0
- package/dist/augments/npm/npm-deps.js +47 -0
- package/dist/augments/npm/package-json.d.ts +21 -0
- package/dist/augments/npm/package-json.js +48 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/package.json +4 -4
- package/src/augments/fs/grep.ts +379 -0
- package/src/augments/npm/npm-deps.ts +85 -0
- package/src/augments/npm/package-json.ts +75 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type IsEqual, type RequireExactlyOne } from 'type-fest';
|
|
3
|
+
/**
|
|
4
|
+
* Optional options for {@link grep}.
|
|
5
|
+
*
|
|
6
|
+
* @category Internal
|
|
7
|
+
* @category Package : @augment-vir/node
|
|
8
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
9
|
+
*/
|
|
10
|
+
export type GrepOptions<CountOnly extends boolean = false> = PartialWithUndefined<{
|
|
11
|
+
patternSyntax: RequireExactlyOne<{
|
|
12
|
+
/**
|
|
13
|
+
* -E, --extended-regexp: Interpret PATTERNS as extended regular expressions (EREs, see
|
|
14
|
+
* below).
|
|
15
|
+
*/
|
|
16
|
+
extendedRegExp: true;
|
|
17
|
+
/** -F, --fixed-strings: Interpret PATTERNS as fixed strings, not regular expressions. */
|
|
18
|
+
fixedStrings: true;
|
|
19
|
+
/**
|
|
20
|
+
* -G, --basic-regexp: Interpret PATTERNS as basic regular expressions (BREs, see below).
|
|
21
|
+
* This is the default.
|
|
22
|
+
*/
|
|
23
|
+
basicRegExp: true;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* If set to true, sets: -i, --ignore-case: Ignore case distinctions in patterns and input data,
|
|
27
|
+
* so that characters that differ only in case match each other.
|
|
28
|
+
*/
|
|
29
|
+
ignoreCase: boolean;
|
|
30
|
+
/** -v, --invert-match: Invert the sense of matching, to select non-matching lines. */
|
|
31
|
+
invertMatch: boolean;
|
|
32
|
+
matchType: RequireExactlyOne<{
|
|
33
|
+
/**
|
|
34
|
+
* -w, --word-regexp: Select only those lines containing matches that form whole words. The
|
|
35
|
+
* test is that the matching substring must either be at the beginning of the line, or
|
|
36
|
+
* preceded by a non-word constituent character. Similarly, it must be either at the end of
|
|
37
|
+
* the line or followed by a non-word constituent character. Word-constituent characters are
|
|
38
|
+
* letters, digits, and the underscore. This option has no effect if -x is also specified.
|
|
39
|
+
*/
|
|
40
|
+
wordRegExp: true;
|
|
41
|
+
/**
|
|
42
|
+
* -x, --line-regexp: Select only those matches that exactly match the whole line. For a
|
|
43
|
+
* regular expression pattern, this is like parenthesizing the pattern and then surrounding
|
|
44
|
+
* it with ^ and $.
|
|
45
|
+
*/
|
|
46
|
+
lineRegExp: true;
|
|
47
|
+
}>;
|
|
48
|
+
output: RequireExactlyOne<{
|
|
49
|
+
/**
|
|
50
|
+
* -c, --count: Suppress normal output; instead print a count of matching lines for each
|
|
51
|
+
* input file. With the -v, --invert-match option (see above), count non-matching lines.
|
|
52
|
+
*/
|
|
53
|
+
countOnly: CountOnly;
|
|
54
|
+
/**
|
|
55
|
+
* -l, --files-with-matches: Suppress normal output; instead print the name of each input
|
|
56
|
+
* file from which output would normally have been printed. Scanning each input file stops
|
|
57
|
+
* upon first match.
|
|
58
|
+
*/
|
|
59
|
+
filesOnly: true;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* --exclude=GLOB: Skip any command-line file with a name suffix that matches the pattern GLOB,
|
|
63
|
+
* using wildcard matching; a name suffix is either the whole name, or a trailing part that
|
|
64
|
+
* starts with a non-slash character immediately after a slash (/) in the name. When searching
|
|
65
|
+
* recursively, skip any subfile whose base name matches GLOB; the base name is the part after
|
|
66
|
+
* the last slash. A pattern can use *, ?, and [...] as wildcards, and \ to quote a wildcard or
|
|
67
|
+
* backslash character literally.
|
|
68
|
+
*/
|
|
69
|
+
excludePatterns: string[];
|
|
70
|
+
/**
|
|
71
|
+
* -m NUM, --max-count=NUM: Stop reading a file after NUM matching lines. If NUM is zero, grep
|
|
72
|
+
* stops right away without reading input. A NUM of -1 is treated as infinity and grep does not
|
|
73
|
+
* stop; this is the default. If the input is standard input from a regular file, and NUM
|
|
74
|
+
* matching lines are output, grep ensures that the standard input is positioned to just after
|
|
75
|
+
* the last matching line before exiting, regardless of the presence of trailing context lines.
|
|
76
|
+
* This enables a calling process to resume a search. When grep stops after NUM matching lines,
|
|
77
|
+
* it outputs any trailing context lines. When the -c or --count option is also used, grep does
|
|
78
|
+
* not output a count greater than NUM. When the -v or --invert-match option is also used, grep
|
|
79
|
+
* stops after outputting NUM non-matching lines.
|
|
80
|
+
*/
|
|
81
|
+
maxCount: number;
|
|
82
|
+
/**
|
|
83
|
+
* -r, --recursive: Read all files under each directory, recursively, following symbolic links
|
|
84
|
+
* only if they are on the command line. Note that if no file operand is given, grep searches
|
|
85
|
+
* the working directory. This is equivalent to the -d recurse option.
|
|
86
|
+
*/
|
|
87
|
+
recursive: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* If `true`, sets `--dereference-recursive` instead of `--recursive` when searching
|
|
90
|
+
* recursively.
|
|
91
|
+
*
|
|
92
|
+
* -R, --dereference-recursive: Read all files under each directory, recursively. Follow all
|
|
93
|
+
* symbolic links, unlike -r.
|
|
94
|
+
*/
|
|
95
|
+
followSymLinks: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* -U, --binary: Treat the file(s) as binary. By default, under MS-DOS and MS-Windows, grep
|
|
98
|
+
* guesses whether a file is text or binary as described for the --binary-files option. If grep
|
|
99
|
+
* decides the file is a text file, it strips the CR characters from the original file contents
|
|
100
|
+
* (to make regular expressions with ^ and $ work correctly). Specifying -U overrules this
|
|
101
|
+
* guesswork, causing all files to be read and passed to the matching mechanism verbatim; if the
|
|
102
|
+
* file is a text file with CR/LF pairs at the end of each line, this will cause some regular
|
|
103
|
+
* expressions to fail. This option has no effect on platforms other than MS-DOS and MS-
|
|
104
|
+
* Windows.
|
|
105
|
+
*/
|
|
106
|
+
binary: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* --exclude-dir=GLOB: Skip any command-line directory with a name suffix that matches the
|
|
109
|
+
* pattern GLOB. When searching recursively, skip any subdirectory whose base name matches GLOB.
|
|
110
|
+
* Ignore any redundant trailing slashes in GLOB.
|
|
111
|
+
*/
|
|
112
|
+
excludeDirs: string[];
|
|
113
|
+
/**
|
|
114
|
+
* --include=GLOB: Search only files whose base name matches GLOB (using wildcard matching as
|
|
115
|
+
* described under --exclude). If contradictory --include and --exclude options are given, the
|
|
116
|
+
* last matching one wins. If no --include or --exclude options match, a file is included unless
|
|
117
|
+
* the first such option is --include.
|
|
118
|
+
*/
|
|
119
|
+
includeFiles: string[];
|
|
120
|
+
/** Debugging option: if set to `true`, the grep CLI command will be printed before execution. */
|
|
121
|
+
printCommand: boolean;
|
|
122
|
+
/** The directory where the grep command where be run within. */
|
|
123
|
+
cwd: string;
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* Search location options for {@link grep}.
|
|
127
|
+
*
|
|
128
|
+
* @category Internal
|
|
129
|
+
* @category Package : @augment-vir/node
|
|
130
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
131
|
+
*/
|
|
132
|
+
export type GrepSearchLocation = RequireExactlyOne<{
|
|
133
|
+
/** Search within multiple files. */
|
|
134
|
+
files: string[];
|
|
135
|
+
/**
|
|
136
|
+
* Search within multiple directories. Set `recursive` to `true` in options to search the
|
|
137
|
+
* directory recursively.
|
|
138
|
+
*/
|
|
139
|
+
dirs: string[];
|
|
140
|
+
/**
|
|
141
|
+
* Search within a single directory. Set `recursive` to `true` in options to search the
|
|
142
|
+
* directory recursively.
|
|
143
|
+
*/
|
|
144
|
+
dir: string;
|
|
145
|
+
/** Search within a single file. */
|
|
146
|
+
file: string;
|
|
147
|
+
}>;
|
|
148
|
+
/**
|
|
149
|
+
* Search pattern options for {@link grep}.
|
|
150
|
+
*
|
|
151
|
+
* @category Internal
|
|
152
|
+
* @category Package : @augment-vir/node
|
|
153
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
154
|
+
*/
|
|
155
|
+
export type GrepSearchPattern = RequireExactlyOne<{
|
|
156
|
+
pattern: string;
|
|
157
|
+
patterns: string[];
|
|
158
|
+
}>;
|
|
159
|
+
/**
|
|
160
|
+
* Output of {@link grep}. Each key is an absolute file path. Values are array of matches lines for
|
|
161
|
+
* that file.
|
|
162
|
+
*
|
|
163
|
+
* @category Internal
|
|
164
|
+
* @category Package : @augment-vir/node
|
|
165
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
166
|
+
*/
|
|
167
|
+
export type GrepMatches<CountOnly extends boolean = false> = IsEqual<CountOnly, true> extends true ? {
|
|
168
|
+
[FileName in string]: number;
|
|
169
|
+
} : IsEqual<CountOnly, false> extends true ? {
|
|
170
|
+
[FileName in string]: string[];
|
|
171
|
+
} : {
|
|
172
|
+
[FileName in string]: number;
|
|
173
|
+
} | {
|
|
174
|
+
[FileName in string]: string[];
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Run `grep`, matching patterns to specific lines in files or directories.
|
|
178
|
+
*
|
|
179
|
+
* @category Node : File
|
|
180
|
+
* @category Package : @augment-vir/node
|
|
181
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
182
|
+
*/
|
|
183
|
+
export declare function grep<const CountOnly extends boolean = false>(grepSearchPattern: Readonly<GrepSearchPattern>, grepSearchLocation: Readonly<GrepSearchLocation>, options?: Readonly<GrepOptions<CountOnly>>): Promise<GrepMatches<CountOnly>>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { assert, check } from '@augment-vir/assert';
|
|
2
|
+
import { arrayToObject, getOrSet, log, safeMatch, } from '@augment-vir/common';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { runShellCommand } from '../terminal/shell.js';
|
|
5
|
+
function escape(input) {
|
|
6
|
+
return input.replaceAll('"', String.raw `\"`).replaceAll('\n', '');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Run `grep`, matching patterns to specific lines in files or directories.
|
|
10
|
+
*
|
|
11
|
+
* @category Node : File
|
|
12
|
+
* @category Package : @augment-vir/node
|
|
13
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
14
|
+
*/
|
|
15
|
+
export async function grep(grepSearchPattern, grepSearchLocation, options = {}) {
|
|
16
|
+
const searchPatterns = (grepSearchPattern.patterns || [grepSearchPattern.pattern]).filter(check.isTruthy);
|
|
17
|
+
if (!searchPatterns.length) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const searchLocation = grepSearchLocation.files
|
|
21
|
+
? {
|
|
22
|
+
files: grepSearchLocation.files,
|
|
23
|
+
}
|
|
24
|
+
: grepSearchLocation.file
|
|
25
|
+
? {
|
|
26
|
+
files: [grepSearchLocation.file],
|
|
27
|
+
}
|
|
28
|
+
: grepSearchLocation.dirs
|
|
29
|
+
? {
|
|
30
|
+
dirs: grepSearchLocation.dirs,
|
|
31
|
+
}
|
|
32
|
+
: grepSearchLocation.dir
|
|
33
|
+
? {
|
|
34
|
+
dirs: [grepSearchLocation.dir],
|
|
35
|
+
}
|
|
36
|
+
: undefined;
|
|
37
|
+
if (!searchLocation ||
|
|
38
|
+
(searchLocation.dirs && !searchLocation.dirs.length) ||
|
|
39
|
+
(searchLocation.files && !searchLocation.files.length)) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const searchParts = searchLocation.dirs
|
|
43
|
+
? options.recursive
|
|
44
|
+
? searchLocation.dirs
|
|
45
|
+
: searchLocation.dirs.map((dir) => join(dir, '*'))
|
|
46
|
+
: searchLocation.files;
|
|
47
|
+
const fullCommand = [
|
|
48
|
+
'grep',
|
|
49
|
+
options.patternSyntax?.basicRegExp
|
|
50
|
+
? '--basic-regexp'
|
|
51
|
+
: options.patternSyntax?.extendedRegExp
|
|
52
|
+
? '--extended-regexp'
|
|
53
|
+
: options.patternSyntax?.fixedStrings
|
|
54
|
+
? '--fixed-strings'
|
|
55
|
+
: '',
|
|
56
|
+
options.ignoreCase ? '--ignore-case' : '',
|
|
57
|
+
options.invertMatch && !options.output?.filesOnly ? '--invert-match' : '',
|
|
58
|
+
options.matchType?.wordRegExp
|
|
59
|
+
? '--word-regexp'
|
|
60
|
+
: options.matchType?.lineRegExp
|
|
61
|
+
? '--line-regexp'
|
|
62
|
+
: '',
|
|
63
|
+
options.output?.countOnly
|
|
64
|
+
? '--count'
|
|
65
|
+
: options.output?.filesOnly
|
|
66
|
+
? options.invertMatch
|
|
67
|
+
? '--files-without-match'
|
|
68
|
+
: '--files-with-matches'
|
|
69
|
+
: '',
|
|
70
|
+
'--color=never',
|
|
71
|
+
options.maxCount ? `--max-count=${options.maxCount}` : '',
|
|
72
|
+
'--no-messages',
|
|
73
|
+
'--with-filename',
|
|
74
|
+
'--null',
|
|
75
|
+
...(options.excludePatterns?.length
|
|
76
|
+
? options.excludePatterns.map((excludePattern) => `--exclude="${escape(excludePattern)}"`)
|
|
77
|
+
: []),
|
|
78
|
+
options.recursive
|
|
79
|
+
? options.followSymLinks
|
|
80
|
+
? '-RS'
|
|
81
|
+
: '--recursive'
|
|
82
|
+
: '',
|
|
83
|
+
...(options.excludeDirs?.length
|
|
84
|
+
? options.excludeDirs.map((excludeDir) => `--exclude-dir="${escape(excludeDir)}"`)
|
|
85
|
+
: []),
|
|
86
|
+
...(options.includeFiles?.length
|
|
87
|
+
? options.includeFiles.map((includeFile) => `--include="${escape(includeFile)}"`)
|
|
88
|
+
: []),
|
|
89
|
+
options.binary ? '--binary' : '',
|
|
90
|
+
...searchPatterns.map((searchPattern) => `-e "${searchPattern}"`),
|
|
91
|
+
...searchParts,
|
|
92
|
+
]
|
|
93
|
+
.filter(check.isTruthy)
|
|
94
|
+
.join(' ');
|
|
95
|
+
if (options.printCommand) {
|
|
96
|
+
log.faint(`> ${fullCommand}`);
|
|
97
|
+
}
|
|
98
|
+
const result = await runShellCommand(fullCommand, {
|
|
99
|
+
cwd: options.cwd,
|
|
100
|
+
});
|
|
101
|
+
const trimmedOutput = result.stdout.trim();
|
|
102
|
+
if (result.exitCode === 1 || !trimmedOutput) {
|
|
103
|
+
/** No matches. */
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
else if (options.output?.countOnly) {
|
|
107
|
+
return arrayToObject(trimmedOutput.split(/[\0\n]/), (entry) => {
|
|
108
|
+
/** Ignore empty strings. */
|
|
109
|
+
/* node:coverage ignore next 3 */
|
|
110
|
+
if (!entry) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const [, fileName, countString,] = safeMatch(entry, /(^.+):(\d+)$/);
|
|
114
|
+
assert.isDefined(fileName, `Failed parse grep file name from: '${entry}'`);
|
|
115
|
+
const count = Number(countString);
|
|
116
|
+
assert.isNumber(count, `Failed to parse grep number from: '${entry}'`);
|
|
117
|
+
if (!count) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
key: fileName,
|
|
122
|
+
value: count,
|
|
123
|
+
};
|
|
124
|
+
}, {
|
|
125
|
+
useRequired: true,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else if (options.output?.filesOnly) {
|
|
129
|
+
return arrayToObject(trimmedOutput.split(/[\0\n]/), (entry) => {
|
|
130
|
+
/** Ignore empty strings. */
|
|
131
|
+
if (!entry) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
key: entry,
|
|
136
|
+
value: [],
|
|
137
|
+
};
|
|
138
|
+
}, {
|
|
139
|
+
useRequired: true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const outputLines = trimmedOutput.split(/[\0\n]/);
|
|
144
|
+
const fileMatches = {};
|
|
145
|
+
outputLines.forEach((line, index) => {
|
|
146
|
+
if (!(index % 2)) {
|
|
147
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
148
|
+
getOrSet(fileMatches, line, () => []).push(outputLines[index + 1]);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return fileMatches;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All `package.json` keys that are parsed as direct dependencies.
|
|
3
|
+
*
|
|
4
|
+
* @category Internal
|
|
5
|
+
*/
|
|
6
|
+
export declare enum PackageJsonDependencyKey {
|
|
7
|
+
DevDependencies = "devDependencies",
|
|
8
|
+
Dependencies = "dependencies",
|
|
9
|
+
PeerDependencies = "peerDependencies"
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A record of package names to package versions included in the direct dependencies. This is the
|
|
13
|
+
* output from {@link listAllDirectNpmDeps}.
|
|
14
|
+
*
|
|
15
|
+
* @category Internal
|
|
16
|
+
*/
|
|
17
|
+
export type NpmDeps = Record<string, NpmDep[]>;
|
|
18
|
+
export type NpmDep = {
|
|
19
|
+
/** Path to the `package.json` file that depends on this. */
|
|
20
|
+
requiredBy: string;
|
|
21
|
+
dependencyKey: PackageJsonDependencyKey;
|
|
22
|
+
/**
|
|
23
|
+
* The version as it is directly noted in the `package.json` file. This might not necessarily
|
|
24
|
+
* correlate to any actually published versions.
|
|
25
|
+
*/
|
|
26
|
+
versionValue: string;
|
|
27
|
+
/** If true, this dependency is part of the workspace's own packages. */
|
|
28
|
+
isWorkspace: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Finds all direct deps for the workspace at, or that contains, the given dir path.
|
|
32
|
+
*
|
|
33
|
+
* @category Node : Npm
|
|
34
|
+
* @category Package : @augment-vir/node
|
|
35
|
+
* @throws If no directory with a `package-lock.json` file is found.
|
|
36
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
37
|
+
*/
|
|
38
|
+
export declare function listAllDirectNpmDeps(startDirPath: string): Promise<NpmDeps>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { filterMap, getEnumValues, getObjectTypedEntries, getOrSet } from '@augment-vir/common';
|
|
3
|
+
import { readJsonFile } from '../fs/json.js';
|
|
4
|
+
import { findAllPackageJsonFilePaths } from './package-json.js';
|
|
5
|
+
/**
|
|
6
|
+
* All `package.json` keys that are parsed as direct dependencies.
|
|
7
|
+
*
|
|
8
|
+
* @category Internal
|
|
9
|
+
*/
|
|
10
|
+
export var PackageJsonDependencyKey;
|
|
11
|
+
(function (PackageJsonDependencyKey) {
|
|
12
|
+
PackageJsonDependencyKey["DevDependencies"] = "devDependencies";
|
|
13
|
+
PackageJsonDependencyKey["Dependencies"] = "dependencies";
|
|
14
|
+
PackageJsonDependencyKey["PeerDependencies"] = "peerDependencies";
|
|
15
|
+
})(PackageJsonDependencyKey || (PackageJsonDependencyKey = {}));
|
|
16
|
+
/**
|
|
17
|
+
* Finds all direct deps for the workspace at, or that contains, the given dir path.
|
|
18
|
+
*
|
|
19
|
+
* @category Node : Npm
|
|
20
|
+
* @category Package : @augment-vir/node
|
|
21
|
+
* @throws If no directory with a `package-lock.json` file is found.
|
|
22
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
23
|
+
*/
|
|
24
|
+
export async function listAllDirectNpmDeps(startDirPath) {
|
|
25
|
+
const packageJsonFilePaths = await findAllPackageJsonFilePaths(startDirPath);
|
|
26
|
+
const deps = {};
|
|
27
|
+
const packageJsonFiles = await Promise.all(packageJsonFilePaths.map(async (packageJsonFilePath) => {
|
|
28
|
+
return {
|
|
29
|
+
packageJsonFilePath,
|
|
30
|
+
packageJson: (await readJsonFile(packageJsonFilePath)),
|
|
31
|
+
};
|
|
32
|
+
}));
|
|
33
|
+
const allWorkspacePackageNames = filterMap(packageJsonFiles, (packageJsonFile) => packageJsonFile.packageJson.name, check.isTruthy);
|
|
34
|
+
packageJsonFiles.forEach(({ packageJson, packageJsonFilePath }) => {
|
|
35
|
+
getEnumValues(PackageJsonDependencyKey).forEach((dependencyKey) => {
|
|
36
|
+
getObjectTypedEntries(packageJson[dependencyKey] || {}).forEach(([dependencyName, versionValue,]) => {
|
|
37
|
+
getOrSet(deps, dependencyName, () => []).push({
|
|
38
|
+
dependencyKey,
|
|
39
|
+
requiredBy: packageJsonFilePath,
|
|
40
|
+
versionValue,
|
|
41
|
+
isWorkspace: allWorkspacePackageNames.includes(dependencyName),
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
return deps;
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds all `package.json` files contained within the workspaces present in the current
|
|
3
|
+
* `startDirPath`. A workspace root dir is found by recursively looking for a parent
|
|
4
|
+
* `package-lock.json` file.
|
|
5
|
+
*
|
|
6
|
+
* @category Node : Npm
|
|
7
|
+
* @category Package : @augment-vir/node
|
|
8
|
+
* @throws If no directory with a `package-lock.json` file is found.
|
|
9
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
10
|
+
*/
|
|
11
|
+
export declare function findAllPackageJsonFilePaths(startDirPath: string): Promise<string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Get all workspace package.json paths starting at the given directory path. The output is string
|
|
14
|
+
* sorted to keep it stable.
|
|
15
|
+
*
|
|
16
|
+
* @category Node : Npm
|
|
17
|
+
* @category Package : @augment-vir/node
|
|
18
|
+
* @throws Error if there is no `package.json` file at the given `rootDirPath`.
|
|
19
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
20
|
+
*/
|
|
21
|
+
export declare function getWorkspacePackageJsonFilePaths(rootDirPath: string): Promise<string[]>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
2
|
+
import { filterMap, fromAsyncIterable } from '@augment-vir/common';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { glob, readFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { findAncestor, joinFilesToDir } from '../path/ancestor.js';
|
|
7
|
+
/**
|
|
8
|
+
* Finds all `package.json` files contained within the workspaces present in the current
|
|
9
|
+
* `startDirPath`. A workspace root dir is found by recursively looking for a parent
|
|
10
|
+
* `package-lock.json` file.
|
|
11
|
+
*
|
|
12
|
+
* @category Node : Npm
|
|
13
|
+
* @category Package : @augment-vir/node
|
|
14
|
+
* @throws If no directory with a `package-lock.json` file is found.
|
|
15
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
16
|
+
*/
|
|
17
|
+
export async function findAllPackageJsonFilePaths(startDirPath) {
|
|
18
|
+
const packageRootDir = findAncestor(startDirPath, (dir) => existsSync(join(dir, 'package-lock.json')));
|
|
19
|
+
if (!packageRootDir) {
|
|
20
|
+
throw new Error(`Cannot find all package.json files: failed to find any directory with a package-lock.json file. Started at '${startDirPath}'.`);
|
|
21
|
+
}
|
|
22
|
+
const rootPackageJsonPath = join(packageRootDir, 'package.json');
|
|
23
|
+
return [
|
|
24
|
+
rootPackageJsonPath,
|
|
25
|
+
...(await getWorkspacePackageJsonFilePaths(packageRootDir)),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get all workspace package.json paths starting at the given directory path. The output is string
|
|
30
|
+
* sorted to keep it stable.
|
|
31
|
+
*
|
|
32
|
+
* @category Node : Npm
|
|
33
|
+
* @category Package : @augment-vir/node
|
|
34
|
+
* @throws Error if there is no `package.json` file at the given `rootDirPath`.
|
|
35
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
36
|
+
*/
|
|
37
|
+
export async function getWorkspacePackageJsonFilePaths(rootDirPath) {
|
|
38
|
+
const packageJson = JSON.parse(await readFile(join(rootDirPath, 'package.json'), 'utf8'));
|
|
39
|
+
/* node:coverage ignore next 3: this package only uses one type of workspace */
|
|
40
|
+
const patterns = check.isArray(packageJson.workspaces)
|
|
41
|
+
? packageJson.workspaces
|
|
42
|
+
: packageJson.workspaces?.packages || [];
|
|
43
|
+
const matchedPaths = joinFilesToDir(rootDirPath, (await Promise.all(patterns.map(async (pattern) => {
|
|
44
|
+
return await fromAsyncIterable(glob(pattern, { cwd: rootDirPath }));
|
|
45
|
+
}))).flat());
|
|
46
|
+
const workspacePackageJsonPaths = filterMap(matchedPaths, (matchedPath) => join(matchedPath, 'package.json'), (packageJsonPath) => existsSync(packageJsonPath));
|
|
47
|
+
return workspacePackageJsonPaths.sort();
|
|
48
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export * from './augments/fs/dir-contents.js';
|
|
2
2
|
export * from './augments/fs/download.js';
|
|
3
|
+
export * from './augments/fs/grep.js';
|
|
3
4
|
export * from './augments/fs/json.js';
|
|
4
5
|
export * from './augments/fs/read-dir.js';
|
|
5
6
|
export * from './augments/fs/read-file.js';
|
|
6
7
|
export * from './augments/fs/symlink.js';
|
|
7
8
|
export * from './augments/fs/write.js';
|
|
8
9
|
export * from './augments/npm/find-bin-path.js';
|
|
10
|
+
export * from './augments/npm/npm-deps.js';
|
|
11
|
+
export * from './augments/npm/package-json.js';
|
|
9
12
|
export * from './augments/npm/query-workspace.js';
|
|
10
13
|
export * from './augments/npm/read-package-json.js';
|
|
11
14
|
export * from './augments/os/operating-system.js';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export * from './augments/fs/dir-contents.js';
|
|
2
2
|
export * from './augments/fs/download.js';
|
|
3
|
+
export * from './augments/fs/grep.js';
|
|
3
4
|
export * from './augments/fs/json.js';
|
|
4
5
|
export * from './augments/fs/read-dir.js';
|
|
5
6
|
export * from './augments/fs/read-file.js';
|
|
6
7
|
export * from './augments/fs/symlink.js';
|
|
7
8
|
export * from './augments/fs/write.js';
|
|
8
9
|
export * from './augments/npm/find-bin-path.js';
|
|
10
|
+
export * from './augments/npm/npm-deps.js';
|
|
11
|
+
export * from './augments/npm/package-json.js';
|
|
9
12
|
export * from './augments/npm/query-workspace.js';
|
|
10
13
|
export * from './augments/npm/read-package-json.js';
|
|
11
14
|
export * from './augments/os/operating-system.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@augment-vir/node",
|
|
3
|
-
"version": "31.
|
|
3
|
+
"version": "31.46.0",
|
|
4
4
|
"description": "A collection of augments, helpers types, functions, and classes only for Node.js (backend) JavaScript environments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"augment",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"test:update": "npm test"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@augment-vir/assert": "^31.
|
|
42
|
-
"@augment-vir/common": "^31.
|
|
41
|
+
"@augment-vir/assert": "^31.46.0",
|
|
42
|
+
"@augment-vir/common": "^31.46.0",
|
|
43
43
|
"@date-vir/duration": "^8.0.0",
|
|
44
44
|
"ansi-styles": "^6.2.3",
|
|
45
45
|
"sanitize-filename": "^1.6.3",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"typed-event-target": "^4.1.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@augment-vir/test": "^31.
|
|
52
|
+
"@augment-vir/test": "^31.46.0",
|
|
53
53
|
"@types/node": "^24.9.1",
|
|
54
54
|
"@web/dev-server-esbuild": "^1.0.4",
|
|
55
55
|
"@web/test-runner": "^0.20.2",
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import {assert, check} from '@augment-vir/assert';
|
|
2
|
+
import {
|
|
3
|
+
arrayToObject,
|
|
4
|
+
getOrSet,
|
|
5
|
+
log,
|
|
6
|
+
safeMatch,
|
|
7
|
+
type PartialWithUndefined,
|
|
8
|
+
type SelectFrom,
|
|
9
|
+
} from '@augment-vir/common';
|
|
10
|
+
import {join} from 'node:path';
|
|
11
|
+
import {type IsEqual, type RequireExactlyOne} from 'type-fest';
|
|
12
|
+
import {runShellCommand} from '../terminal/shell.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional options for {@link grep}.
|
|
16
|
+
*
|
|
17
|
+
* @category Internal
|
|
18
|
+
* @category Package : @augment-vir/node
|
|
19
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
20
|
+
*/
|
|
21
|
+
export type GrepOptions<CountOnly extends boolean = false> = PartialWithUndefined<{
|
|
22
|
+
patternSyntax: RequireExactlyOne<{
|
|
23
|
+
/**
|
|
24
|
+
* -E, --extended-regexp: Interpret PATTERNS as extended regular expressions (EREs, see
|
|
25
|
+
* below).
|
|
26
|
+
*/
|
|
27
|
+
extendedRegExp: true;
|
|
28
|
+
/** -F, --fixed-strings: Interpret PATTERNS as fixed strings, not regular expressions. */
|
|
29
|
+
fixedStrings: true;
|
|
30
|
+
/**
|
|
31
|
+
* -G, --basic-regexp: Interpret PATTERNS as basic regular expressions (BREs, see below).
|
|
32
|
+
* This is the default.
|
|
33
|
+
*/
|
|
34
|
+
basicRegExp: true;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* If set to true, sets: -i, --ignore-case: Ignore case distinctions in patterns and input data,
|
|
38
|
+
* so that characters that differ only in case match each other.
|
|
39
|
+
*/
|
|
40
|
+
ignoreCase: boolean;
|
|
41
|
+
/** -v, --invert-match: Invert the sense of matching, to select non-matching lines. */
|
|
42
|
+
invertMatch: boolean;
|
|
43
|
+
matchType: RequireExactlyOne<{
|
|
44
|
+
/**
|
|
45
|
+
* -w, --word-regexp: Select only those lines containing matches that form whole words. The
|
|
46
|
+
* test is that the matching substring must either be at the beginning of the line, or
|
|
47
|
+
* preceded by a non-word constituent character. Similarly, it must be either at the end of
|
|
48
|
+
* the line or followed by a non-word constituent character. Word-constituent characters are
|
|
49
|
+
* letters, digits, and the underscore. This option has no effect if -x is also specified.
|
|
50
|
+
*/
|
|
51
|
+
wordRegExp: true;
|
|
52
|
+
/**
|
|
53
|
+
* -x, --line-regexp: Select only those matches that exactly match the whole line. For a
|
|
54
|
+
* regular expression pattern, this is like parenthesizing the pattern and then surrounding
|
|
55
|
+
* it with ^ and $.
|
|
56
|
+
*/
|
|
57
|
+
lineRegExp: true;
|
|
58
|
+
}>;
|
|
59
|
+
|
|
60
|
+
output: RequireExactlyOne<{
|
|
61
|
+
/**
|
|
62
|
+
* -c, --count: Suppress normal output; instead print a count of matching lines for each
|
|
63
|
+
* input file. With the -v, --invert-match option (see above), count non-matching lines.
|
|
64
|
+
*/
|
|
65
|
+
countOnly: CountOnly;
|
|
66
|
+
/**
|
|
67
|
+
* -l, --files-with-matches: Suppress normal output; instead print the name of each input
|
|
68
|
+
* file from which output would normally have been printed. Scanning each input file stops
|
|
69
|
+
* upon first match.
|
|
70
|
+
*/
|
|
71
|
+
filesOnly: true;
|
|
72
|
+
}>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* --exclude=GLOB: Skip any command-line file with a name suffix that matches the pattern GLOB,
|
|
76
|
+
* using wildcard matching; a name suffix is either the whole name, or a trailing part that
|
|
77
|
+
* starts with a non-slash character immediately after a slash (/) in the name. When searching
|
|
78
|
+
* recursively, skip any subfile whose base name matches GLOB; the base name is the part after
|
|
79
|
+
* the last slash. A pattern can use *, ?, and [...] as wildcards, and \ to quote a wildcard or
|
|
80
|
+
* backslash character literally.
|
|
81
|
+
*/
|
|
82
|
+
excludePatterns: string[];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* -m NUM, --max-count=NUM: Stop reading a file after NUM matching lines. If NUM is zero, grep
|
|
86
|
+
* stops right away without reading input. A NUM of -1 is treated as infinity and grep does not
|
|
87
|
+
* stop; this is the default. If the input is standard input from a regular file, and NUM
|
|
88
|
+
* matching lines are output, grep ensures that the standard input is positioned to just after
|
|
89
|
+
* the last matching line before exiting, regardless of the presence of trailing context lines.
|
|
90
|
+
* This enables a calling process to resume a search. When grep stops after NUM matching lines,
|
|
91
|
+
* it outputs any trailing context lines. When the -c or --count option is also used, grep does
|
|
92
|
+
* not output a count greater than NUM. When the -v or --invert-match option is also used, grep
|
|
93
|
+
* stops after outputting NUM non-matching lines.
|
|
94
|
+
*/
|
|
95
|
+
maxCount: number;
|
|
96
|
+
/**
|
|
97
|
+
* -r, --recursive: Read all files under each directory, recursively, following symbolic links
|
|
98
|
+
* only if they are on the command line. Note that if no file operand is given, grep searches
|
|
99
|
+
* the working directory. This is equivalent to the -d recurse option.
|
|
100
|
+
*/
|
|
101
|
+
recursive: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* If `true`, sets `--dereference-recursive` instead of `--recursive` when searching
|
|
104
|
+
* recursively.
|
|
105
|
+
*
|
|
106
|
+
* -R, --dereference-recursive: Read all files under each directory, recursively. Follow all
|
|
107
|
+
* symbolic links, unlike -r.
|
|
108
|
+
*/
|
|
109
|
+
followSymLinks: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* -U, --binary: Treat the file(s) as binary. By default, under MS-DOS and MS-Windows, grep
|
|
112
|
+
* guesses whether a file is text or binary as described for the --binary-files option. If grep
|
|
113
|
+
* decides the file is a text file, it strips the CR characters from the original file contents
|
|
114
|
+
* (to make regular expressions with ^ and $ work correctly). Specifying -U overrules this
|
|
115
|
+
* guesswork, causing all files to be read and passed to the matching mechanism verbatim; if the
|
|
116
|
+
* file is a text file with CR/LF pairs at the end of each line, this will cause some regular
|
|
117
|
+
* expressions to fail. This option has no effect on platforms other than MS-DOS and MS-
|
|
118
|
+
* Windows.
|
|
119
|
+
*/
|
|
120
|
+
binary: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* --exclude-dir=GLOB: Skip any command-line directory with a name suffix that matches the
|
|
123
|
+
* pattern GLOB. When searching recursively, skip any subdirectory whose base name matches GLOB.
|
|
124
|
+
* Ignore any redundant trailing slashes in GLOB.
|
|
125
|
+
*/
|
|
126
|
+
excludeDirs: string[];
|
|
127
|
+
/**
|
|
128
|
+
* --include=GLOB: Search only files whose base name matches GLOB (using wildcard matching as
|
|
129
|
+
* described under --exclude). If contradictory --include and --exclude options are given, the
|
|
130
|
+
* last matching one wins. If no --include or --exclude options match, a file is included unless
|
|
131
|
+
* the first such option is --include.
|
|
132
|
+
*/
|
|
133
|
+
includeFiles: string[];
|
|
134
|
+
/** Debugging option: if set to `true`, the grep CLI command will be printed before execution. */
|
|
135
|
+
printCommand: boolean;
|
|
136
|
+
/** The directory where the grep command where be run within. */
|
|
137
|
+
cwd: string;
|
|
138
|
+
}>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Search location options for {@link grep}.
|
|
142
|
+
*
|
|
143
|
+
* @category Internal
|
|
144
|
+
* @category Package : @augment-vir/node
|
|
145
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
146
|
+
*/
|
|
147
|
+
export type GrepSearchLocation = RequireExactlyOne<{
|
|
148
|
+
/** Search within multiple files. */
|
|
149
|
+
files: string[];
|
|
150
|
+
/**
|
|
151
|
+
* Search within multiple directories. Set `recursive` to `true` in options to search the
|
|
152
|
+
* directory recursively.
|
|
153
|
+
*/
|
|
154
|
+
dirs: string[];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Search within a single directory. Set `recursive` to `true` in options to search the
|
|
158
|
+
* directory recursively.
|
|
159
|
+
*/
|
|
160
|
+
dir: string;
|
|
161
|
+
/** Search within a single file. */
|
|
162
|
+
file: string;
|
|
163
|
+
}>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Search pattern options for {@link grep}.
|
|
167
|
+
*
|
|
168
|
+
* @category Internal
|
|
169
|
+
* @category Package : @augment-vir/node
|
|
170
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
171
|
+
*/
|
|
172
|
+
export type GrepSearchPattern = RequireExactlyOne<{
|
|
173
|
+
pattern: string;
|
|
174
|
+
patterns: string[];
|
|
175
|
+
}>;
|
|
176
|
+
|
|
177
|
+
function escape(input: string) {
|
|
178
|
+
return input.replaceAll('"', String.raw`\"`).replaceAll('\n', '');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Output of {@link grep}. Each key is an absolute file path. Values are array of matches lines for
|
|
183
|
+
* that file.
|
|
184
|
+
*
|
|
185
|
+
* @category Internal
|
|
186
|
+
* @category Package : @augment-vir/node
|
|
187
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
188
|
+
*/
|
|
189
|
+
export type GrepMatches<CountOnly extends boolean = false> =
|
|
190
|
+
IsEqual<CountOnly, true> extends true
|
|
191
|
+
? {[FileName in string]: number}
|
|
192
|
+
: IsEqual<CountOnly, false> extends true
|
|
193
|
+
? {[FileName in string]: string[]}
|
|
194
|
+
: {[FileName in string]: number} | {[FileName in string]: string[]};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Run `grep`, matching patterns to specific lines in files or directories.
|
|
198
|
+
*
|
|
199
|
+
* @category Node : File
|
|
200
|
+
* @category Package : @augment-vir/node
|
|
201
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
202
|
+
*/
|
|
203
|
+
export async function grep<const CountOnly extends boolean = false>(
|
|
204
|
+
grepSearchPattern: Readonly<GrepSearchPattern>,
|
|
205
|
+
grepSearchLocation: Readonly<GrepSearchLocation>,
|
|
206
|
+
options: Readonly<GrepOptions<CountOnly>> = {},
|
|
207
|
+
): Promise<GrepMatches<CountOnly>> {
|
|
208
|
+
const searchPatterns: string[] = (
|
|
209
|
+
grepSearchPattern.patterns || [grepSearchPattern.pattern]
|
|
210
|
+
).filter(check.isTruthy);
|
|
211
|
+
|
|
212
|
+
if (!searchPatterns.length) {
|
|
213
|
+
return {};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const searchLocation: SelectFrom<GrepSearchLocation, {files: true; dirs: true}> | undefined =
|
|
217
|
+
grepSearchLocation.files
|
|
218
|
+
? {
|
|
219
|
+
files: grepSearchLocation.files,
|
|
220
|
+
}
|
|
221
|
+
: grepSearchLocation.file
|
|
222
|
+
? {
|
|
223
|
+
files: [grepSearchLocation.file],
|
|
224
|
+
}
|
|
225
|
+
: grepSearchLocation.dirs
|
|
226
|
+
? {
|
|
227
|
+
dirs: grepSearchLocation.dirs,
|
|
228
|
+
}
|
|
229
|
+
: grepSearchLocation.dir
|
|
230
|
+
? {
|
|
231
|
+
dirs: [grepSearchLocation.dir],
|
|
232
|
+
}
|
|
233
|
+
: undefined;
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
!searchLocation ||
|
|
237
|
+
(searchLocation.dirs && !searchLocation.dirs.length) ||
|
|
238
|
+
(searchLocation.files && !searchLocation.files.length)
|
|
239
|
+
) {
|
|
240
|
+
return {};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const searchParts = searchLocation.dirs
|
|
244
|
+
? options.recursive
|
|
245
|
+
? searchLocation.dirs
|
|
246
|
+
: searchLocation.dirs.map((dir) => join(dir, '*'))
|
|
247
|
+
: searchLocation.files;
|
|
248
|
+
|
|
249
|
+
const fullCommand = [
|
|
250
|
+
'grep',
|
|
251
|
+
options.patternSyntax?.basicRegExp
|
|
252
|
+
? '--basic-regexp'
|
|
253
|
+
: options.patternSyntax?.extendedRegExp
|
|
254
|
+
? '--extended-regexp'
|
|
255
|
+
: options.patternSyntax?.fixedStrings
|
|
256
|
+
? '--fixed-strings'
|
|
257
|
+
: '',
|
|
258
|
+
options.ignoreCase ? '--ignore-case' : '',
|
|
259
|
+
options.invertMatch && !options.output?.filesOnly ? '--invert-match' : '',
|
|
260
|
+
options.matchType?.wordRegExp
|
|
261
|
+
? '--word-regexp'
|
|
262
|
+
: options.matchType?.lineRegExp
|
|
263
|
+
? '--line-regexp'
|
|
264
|
+
: '',
|
|
265
|
+
options.output?.countOnly
|
|
266
|
+
? '--count'
|
|
267
|
+
: options.output?.filesOnly
|
|
268
|
+
? options.invertMatch
|
|
269
|
+
? '--files-without-match'
|
|
270
|
+
: '--files-with-matches'
|
|
271
|
+
: '',
|
|
272
|
+
'--color=never',
|
|
273
|
+
options.maxCount ? `--max-count=${options.maxCount}` : '',
|
|
274
|
+
'--no-messages',
|
|
275
|
+
'--with-filename',
|
|
276
|
+
'--null',
|
|
277
|
+
...(options.excludePatterns?.length
|
|
278
|
+
? options.excludePatterns.map(
|
|
279
|
+
(excludePattern) => `--exclude="${escape(excludePattern)}"`,
|
|
280
|
+
)
|
|
281
|
+
: []),
|
|
282
|
+
options.recursive
|
|
283
|
+
? options.followSymLinks
|
|
284
|
+
? '-RS'
|
|
285
|
+
: '--recursive'
|
|
286
|
+
: '',
|
|
287
|
+
...(options.excludeDirs?.length
|
|
288
|
+
? options.excludeDirs.map((excludeDir) => `--exclude-dir="${escape(excludeDir)}"`)
|
|
289
|
+
: []),
|
|
290
|
+
...(options.includeFiles?.length
|
|
291
|
+
? options.includeFiles.map((includeFile) => `--include="${escape(includeFile)}"`)
|
|
292
|
+
: []),
|
|
293
|
+
options.binary ? '--binary' : '',
|
|
294
|
+
...searchPatterns.map((searchPattern) => `-e "${searchPattern}"`),
|
|
295
|
+
...searchParts,
|
|
296
|
+
]
|
|
297
|
+
.filter(check.isTruthy)
|
|
298
|
+
.join(' ');
|
|
299
|
+
|
|
300
|
+
if (options.printCommand) {
|
|
301
|
+
log.faint(`> ${fullCommand}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const result = await runShellCommand(fullCommand, {
|
|
305
|
+
cwd: options.cwd,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const trimmedOutput = result.stdout.trim();
|
|
309
|
+
|
|
310
|
+
if (result.exitCode === 1 || !trimmedOutput) {
|
|
311
|
+
/** No matches. */
|
|
312
|
+
return {};
|
|
313
|
+
} else if (options.output?.countOnly) {
|
|
314
|
+
return arrayToObject(
|
|
315
|
+
trimmedOutput.split(/[\0\n]/),
|
|
316
|
+
(entry) => {
|
|
317
|
+
/** Ignore empty strings. */
|
|
318
|
+
/* node:coverage ignore next 3 */
|
|
319
|
+
if (!entry) {
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const [
|
|
324
|
+
,
|
|
325
|
+
fileName,
|
|
326
|
+
countString,
|
|
327
|
+
] = safeMatch(entry, /(^.+):(\d+)$/);
|
|
328
|
+
|
|
329
|
+
assert.isDefined(fileName, `Failed parse grep file name from: '${entry}'`);
|
|
330
|
+
|
|
331
|
+
const count = Number(countString);
|
|
332
|
+
|
|
333
|
+
assert.isNumber(count, `Failed to parse grep number from: '${entry}'`);
|
|
334
|
+
if (!count) {
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
key: fileName,
|
|
340
|
+
value: count,
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
useRequired: true,
|
|
345
|
+
},
|
|
346
|
+
) satisfies Record<string, number> as GrepMatches<CountOnly>;
|
|
347
|
+
} else if (options.output?.filesOnly) {
|
|
348
|
+
return arrayToObject(
|
|
349
|
+
trimmedOutput.split(/[\0\n]/),
|
|
350
|
+
(entry) => {
|
|
351
|
+
/** Ignore empty strings. */
|
|
352
|
+
if (!entry) {
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
key: entry,
|
|
358
|
+
value: [],
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
useRequired: true,
|
|
363
|
+
},
|
|
364
|
+
) satisfies Record<string, string[]> as GrepMatches<CountOnly>;
|
|
365
|
+
} else {
|
|
366
|
+
const outputLines = trimmedOutput.split(/[\0\n]/);
|
|
367
|
+
|
|
368
|
+
const fileMatches: Record<string, string[]> = {};
|
|
369
|
+
|
|
370
|
+
outputLines.forEach((line, index) => {
|
|
371
|
+
if (!(index % 2)) {
|
|
372
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
373
|
+
getOrSet(fileMatches, line, () => []).push(outputLines[index + 1]!);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return fileMatches;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {check} from '@augment-vir/assert';
|
|
2
|
+
import {filterMap, getEnumValues, getObjectTypedEntries, getOrSet} from '@augment-vir/common';
|
|
3
|
+
import {type PackageJson} from 'type-fest';
|
|
4
|
+
import {readJsonFile} from '../fs/json.js';
|
|
5
|
+
import {findAllPackageJsonFilePaths} from './package-json.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* All `package.json` keys that are parsed as direct dependencies.
|
|
9
|
+
*
|
|
10
|
+
* @category Internal
|
|
11
|
+
*/
|
|
12
|
+
export enum PackageJsonDependencyKey {
|
|
13
|
+
DevDependencies = 'devDependencies',
|
|
14
|
+
Dependencies = 'dependencies',
|
|
15
|
+
PeerDependencies = 'peerDependencies',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A record of package names to package versions included in the direct dependencies. This is the
|
|
20
|
+
* output from {@link listAllDirectNpmDeps}.
|
|
21
|
+
*
|
|
22
|
+
* @category Internal
|
|
23
|
+
*/
|
|
24
|
+
export type NpmDeps = Record<string, NpmDep[]>;
|
|
25
|
+
export type NpmDep = {
|
|
26
|
+
/** Path to the `package.json` file that depends on this. */
|
|
27
|
+
requiredBy: string;
|
|
28
|
+
dependencyKey: PackageJsonDependencyKey;
|
|
29
|
+
/**
|
|
30
|
+
* The version as it is directly noted in the `package.json` file. This might not necessarily
|
|
31
|
+
* correlate to any actually published versions.
|
|
32
|
+
*/
|
|
33
|
+
versionValue: string;
|
|
34
|
+
/** If true, this dependency is part of the workspace's own packages. */
|
|
35
|
+
isWorkspace: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Finds all direct deps for the workspace at, or that contains, the given dir path.
|
|
40
|
+
*
|
|
41
|
+
* @category Node : Npm
|
|
42
|
+
* @category Package : @augment-vir/node
|
|
43
|
+
* @throws If no directory with a `package-lock.json` file is found.
|
|
44
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
45
|
+
*/
|
|
46
|
+
export async function listAllDirectNpmDeps(startDirPath: string): Promise<NpmDeps> {
|
|
47
|
+
const packageJsonFilePaths = await findAllPackageJsonFilePaths(startDirPath);
|
|
48
|
+
|
|
49
|
+
const deps: NpmDeps = {};
|
|
50
|
+
|
|
51
|
+
const packageJsonFiles = await Promise.all(
|
|
52
|
+
packageJsonFilePaths.map(async (packageJsonFilePath) => {
|
|
53
|
+
return {
|
|
54
|
+
packageJsonFilePath,
|
|
55
|
+
packageJson: (await readJsonFile(packageJsonFilePath)) as PackageJson,
|
|
56
|
+
};
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const allWorkspacePackageNames = filterMap(
|
|
61
|
+
packageJsonFiles,
|
|
62
|
+
(packageJsonFile) => packageJsonFile.packageJson.name,
|
|
63
|
+
check.isTruthy,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
packageJsonFiles.forEach(({packageJson, packageJsonFilePath}) => {
|
|
67
|
+
getEnumValues(PackageJsonDependencyKey).forEach((dependencyKey) => {
|
|
68
|
+
getObjectTypedEntries(packageJson[dependencyKey] || {}).forEach(
|
|
69
|
+
([
|
|
70
|
+
dependencyName,
|
|
71
|
+
versionValue,
|
|
72
|
+
]) => {
|
|
73
|
+
getOrSet(deps, dependencyName, () => []).push({
|
|
74
|
+
dependencyKey,
|
|
75
|
+
requiredBy: packageJsonFilePath,
|
|
76
|
+
versionValue,
|
|
77
|
+
isWorkspace: allWorkspacePackageNames.includes(dependencyName),
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return deps;
|
|
85
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {check} from '@augment-vir/assert';
|
|
2
|
+
import {filterMap, fromAsyncIterable} from '@augment-vir/common';
|
|
3
|
+
import {existsSync} from 'node:fs';
|
|
4
|
+
import {glob, readFile} from 'node:fs/promises';
|
|
5
|
+
import {join} from 'node:path';
|
|
6
|
+
import {type PackageJson} from 'type-fest';
|
|
7
|
+
import {findAncestor, joinFilesToDir} from '../path/ancestor.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Finds all `package.json` files contained within the workspaces present in the current
|
|
11
|
+
* `startDirPath`. A workspace root dir is found by recursively looking for a parent
|
|
12
|
+
* `package-lock.json` file.
|
|
13
|
+
*
|
|
14
|
+
* @category Node : Npm
|
|
15
|
+
* @category Package : @augment-vir/node
|
|
16
|
+
* @throws If no directory with a `package-lock.json` file is found.
|
|
17
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
18
|
+
*/
|
|
19
|
+
export async function findAllPackageJsonFilePaths(startDirPath: string) {
|
|
20
|
+
const packageRootDir = findAncestor(startDirPath, (dir) =>
|
|
21
|
+
existsSync(join(dir, 'package-lock.json')),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (!packageRootDir) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Cannot find all package.json files: failed to find any directory with a package-lock.json file. Started at '${startDirPath}'.`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rootPackageJsonPath = join(packageRootDir, 'package.json');
|
|
31
|
+
|
|
32
|
+
return [
|
|
33
|
+
rootPackageJsonPath,
|
|
34
|
+
...(await getWorkspacePackageJsonFilePaths(packageRootDir)),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get all workspace package.json paths starting at the given directory path. The output is string
|
|
40
|
+
* sorted to keep it stable.
|
|
41
|
+
*
|
|
42
|
+
* @category Node : Npm
|
|
43
|
+
* @category Package : @augment-vir/node
|
|
44
|
+
* @throws Error if there is no `package.json` file at the given `rootDirPath`.
|
|
45
|
+
* @package [`@augment-vir/node`](https://www.npmjs.com/package/@augment-vir/node)
|
|
46
|
+
*/
|
|
47
|
+
export async function getWorkspacePackageJsonFilePaths(rootDirPath: string): Promise<string[]> {
|
|
48
|
+
const packageJson: PackageJson = JSON.parse(
|
|
49
|
+
await readFile(join(rootDirPath, 'package.json'), 'utf8'),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/* node:coverage ignore next 3: this package only uses one type of workspace */
|
|
53
|
+
const patterns = check.isArray(packageJson.workspaces)
|
|
54
|
+
? packageJson.workspaces
|
|
55
|
+
: packageJson.workspaces?.packages || [];
|
|
56
|
+
|
|
57
|
+
const matchedPaths: string[] = joinFilesToDir(
|
|
58
|
+
rootDirPath,
|
|
59
|
+
(
|
|
60
|
+
await Promise.all(
|
|
61
|
+
patterns.map(async (pattern) => {
|
|
62
|
+
return await fromAsyncIterable(glob(pattern, {cwd: rootDirPath}));
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
65
|
+
).flat(),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const workspacePackageJsonPaths = filterMap(
|
|
69
|
+
matchedPaths,
|
|
70
|
+
(matchedPath) => join(matchedPath, 'package.json'),
|
|
71
|
+
(packageJsonPath) => existsSync(packageJsonPath),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return workspacePackageJsonPaths.sort();
|
|
75
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export * from './augments/fs/dir-contents.js';
|
|
2
2
|
export * from './augments/fs/download.js';
|
|
3
|
+
export * from './augments/fs/grep.js';
|
|
3
4
|
export * from './augments/fs/json.js';
|
|
4
5
|
export * from './augments/fs/read-dir.js';
|
|
5
6
|
export * from './augments/fs/read-file.js';
|
|
6
7
|
export * from './augments/fs/symlink.js';
|
|
7
8
|
export * from './augments/fs/write.js';
|
|
8
9
|
export * from './augments/npm/find-bin-path.js';
|
|
10
|
+
export * from './augments/npm/npm-deps.js';
|
|
11
|
+
export * from './augments/npm/package-json.js';
|
|
9
12
|
export * from './augments/npm/query-workspace.js';
|
|
10
13
|
export * from './augments/npm/read-package-json.js';
|
|
11
14
|
export * from './augments/os/operating-system.js';
|