js-dev-tool 1.2.0 → 1.2.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.
- package/basic-types.d.ts +26 -0
- package/extras/list-deps-of.js +212 -0
- package/extras/list-deps-of.sh +18 -0
- package/package.json +1 -1
- package/utils.d.ts +2 -1
- package/utils.js +2 -0
package/basic-types.d.ts
CHANGED
|
@@ -73,8 +73,34 @@ type Writable<T> = {
|
|
|
73
73
|
type InterfaceType<T> = {
|
|
74
74
|
[P in keyof T]: T[P];
|
|
75
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* combine intersection(s)
|
|
78
|
+
*/
|
|
76
79
|
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
77
80
|
type Constructor<T> = new (...args: any[]) => T;
|
|
81
|
+
/**
|
|
82
|
+
* mark a specific property as `required`
|
|
83
|
+
*
|
|
84
|
+
* + About `Merge` parameter - Accepts values of `1` or `0`.
|
|
85
|
+
* - If it is **1**, the types are displayed concatenated.
|
|
86
|
+
* - If it is **0**, the types are displayed separated by "&".
|
|
87
|
+
*/
|
|
88
|
+
type RequireThese<
|
|
89
|
+
T, K extends keyof T, Merge extends 1 | 0 = 0,
|
|
90
|
+
Intersected = T & Required<Pick<T, K>>
|
|
91
|
+
> = Merge extends 1 ? Prettify<Intersected> : Intersected;
|
|
92
|
+
/**
|
|
93
|
+
* pick any `property` as excludes
|
|
94
|
+
*/
|
|
95
|
+
type ExcludePick<T, K extends keyof T> = Exclude<Pick<T, K>, T>;
|
|
96
|
+
/**
|
|
97
|
+
* pick any `property` as required
|
|
98
|
+
*/
|
|
99
|
+
type RequiredPick<T, K extends keyof T> = Required<Pick<T, K>>;
|
|
100
|
+
/**
|
|
101
|
+
* pick any `property` as partial
|
|
102
|
+
*/
|
|
103
|
+
type PartialPick<T, K extends keyof T> = Partial<Pick<T, K>>;
|
|
78
104
|
type PickProperties<P, T> = { [K in keyof T]-?: T[K] extends P ? K : never }[keyof T];
|
|
79
105
|
type PickNumberProperties<T> = PickProperties<number, T>;
|
|
80
106
|
type PickStringProperties<T> = PickProperties<string, T>;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
3
|
+
// Copyright (C) 2026 jeffy-g <hirotom1107@gmail.com>
|
|
4
|
+
// Released under the MIT license
|
|
5
|
+
// https://opensource.org/licenses/mit-license.php
|
|
6
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @file list-deps-of.js
|
|
10
|
+
* @command node list-deps-of.mjs typescript-jsdoctag-completions-plugin
|
|
11
|
+
*
|
|
12
|
+
* CAVEAT: yarn v1.x lock file 専用
|
|
13
|
+
*/
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
/** @param {string} raw */
|
|
17
|
+
function parseSelectors(raw) {
|
|
18
|
+
if (raw.includes("\"")) {
|
|
19
|
+
const selectors = [];
|
|
20
|
+
const regex = /"([^"]+)"/g;
|
|
21
|
+
let match = regex.exec(raw);
|
|
22
|
+
while (match) {
|
|
23
|
+
selectors.push(match[1]);
|
|
24
|
+
match = regex.exec(raw);
|
|
25
|
+
}
|
|
26
|
+
if (selectors.length > 0) {
|
|
27
|
+
return selectors;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return raw.split(/\s?,\s?/)
|
|
31
|
+
}
|
|
32
|
+
/** @param {string} selector */
|
|
33
|
+
function selectorToName(selector) {
|
|
34
|
+
const npmIndex = selector.indexOf("@npm:");
|
|
35
|
+
if (npmIndex !== -1) {
|
|
36
|
+
return selector.slice(0, npmIndex);
|
|
37
|
+
}
|
|
38
|
+
const lastAt = selector.lastIndexOf("@");
|
|
39
|
+
if (lastAt > 0) {
|
|
40
|
+
return selector.slice(0, lastAt);
|
|
41
|
+
}
|
|
42
|
+
return selector;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* ```
|
|
46
|
+
* /^[^\s].+:/;
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
const re_ModuleStart = /^[^\s].+:/;
|
|
50
|
+
/**
|
|
51
|
+
* ```
|
|
52
|
+
* /^\x20{2}(?:(?:optionalD|d)ependencies):$/;
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
const re_DepsStart = /^\x20{2}(?:(?:optionalD|d)ependencies):$/;
|
|
56
|
+
/**
|
|
57
|
+
* ```
|
|
58
|
+
* /\x20{4}/;
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
const re_DepsEntryIndent = /^\x20{4}[^\s]/;
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {{
|
|
64
|
+
* name?: string;
|
|
65
|
+
* selectors: string[];
|
|
66
|
+
* deps: { name: string; range: string; }[]
|
|
67
|
+
* }} TDependenciesEntry
|
|
68
|
+
*/
|
|
69
|
+
/** @param {string} yarnLockSource */
|
|
70
|
+
function parseLockfile(yarnLockSource) {
|
|
71
|
+
/** @type {TDependenciesEntry[]} */
|
|
72
|
+
const entries = [];
|
|
73
|
+
const lines = yarnLockSource.split(/\r?\n/);
|
|
74
|
+
/** @type {TDependenciesEntry | null} */
|
|
75
|
+
let current = null;
|
|
76
|
+
let inDepsSection = false;
|
|
77
|
+
const modEntryChecker = re_ModuleStart;
|
|
78
|
+
const depsStartChecker = re_DepsStart;
|
|
79
|
+
const depsEntryChecker = re_DepsEntryIndent;
|
|
80
|
+
const reDepsExtractor = /(?:"([^"]+)"|([^"\s]+))\s+"([^"]+)"\s*$/;
|
|
81
|
+
const commitCurrent = () => {
|
|
82
|
+
if (current) {
|
|
83
|
+
entries.push(current);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
if (line.length === 0) {
|
|
88
|
+
inDepsSection = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (line.startsWith("#")) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (modEntryChecker.test(line)) {
|
|
95
|
+
commitCurrent();
|
|
96
|
+
const rawKeys = line.slice(0, -1);
|
|
97
|
+
const selectors = parseSelectors(rawKeys);
|
|
98
|
+
current = { selectors, deps: [] };
|
|
99
|
+
inDepsSection = false;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!current) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (depsStartChecker.test(line)) {
|
|
106
|
+
inDepsSection = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (inDepsSection && depsEntryChecker.test(line)) {
|
|
110
|
+
const trimmed = line.trim();
|
|
111
|
+
const match = reDepsExtractor.exec(trimmed);
|
|
112
|
+
if (match) {
|
|
113
|
+
const name = match[1] || match[2];
|
|
114
|
+
const range = match[3];
|
|
115
|
+
current.deps.push({ name, range });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
commitCurrent();
|
|
120
|
+
/** @type {Map<string, TDependenciesEntry>} */
|
|
121
|
+
const selectorToEntry = new Map();
|
|
122
|
+
/** @type {Map<string, TDependenciesEntry[]>} */
|
|
123
|
+
const nameToEntries = new Map();
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
if (entry.selectors.length === 0) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const name = selectorToName(entry.selectors[0]);
|
|
129
|
+
entry.name = name;
|
|
130
|
+
for (const selector of entry.selectors) {
|
|
131
|
+
selectorToEntry.set(selector, entry);
|
|
132
|
+
}
|
|
133
|
+
if (!nameToEntries.has(name)) {
|
|
134
|
+
nameToEntries.set(name, []);
|
|
135
|
+
}
|
|
136
|
+
// @ts-expect-error
|
|
137
|
+
nameToEntries.get(name).push(entry);
|
|
138
|
+
}
|
|
139
|
+
return { entries, selectorToEntry, nameToEntries };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} depName
|
|
143
|
+
* @param {string} depRange
|
|
144
|
+
* @param {Map<string, TDependenciesEntry>} selectorToEntry
|
|
145
|
+
* @param {Map<string, TDependenciesEntry[]>} nameToEntries
|
|
146
|
+
*/
|
|
147
|
+
function resolveEntries(depName, depRange, selectorToEntry, nameToEntries) {
|
|
148
|
+
const exactSelector = depName + "@" + depRange;
|
|
149
|
+
const exactEntry = selectorToEntry.get(exactSelector);
|
|
150
|
+
if (exactEntry) {
|
|
151
|
+
return [exactEntry];
|
|
152
|
+
}
|
|
153
|
+
const entries = nameToEntries.get(depName);
|
|
154
|
+
if (entries) {
|
|
155
|
+
return entries;
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @param {TDependenciesEntry[]} startEntries
|
|
161
|
+
* @param {Map<string, TDependenciesEntry>} selectorToEntry
|
|
162
|
+
* @param {Map<string, TDependenciesEntry[]>} nameToEntries
|
|
163
|
+
*/
|
|
164
|
+
function collectDependencies(startEntries, selectorToEntry, nameToEntries) {
|
|
165
|
+
/** @type {Set<string>} */
|
|
166
|
+
const result = new Set();
|
|
167
|
+
/** @type {Set<TDependenciesEntry>} */
|
|
168
|
+
const visitedEntries = new Set();
|
|
169
|
+
const stack = [...startEntries];
|
|
170
|
+
while (stack.length > 0) {
|
|
171
|
+
const entry = stack.pop();
|
|
172
|
+
if (!entry || visitedEntries.has(entry)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
visitedEntries.add(entry);
|
|
176
|
+
for (const dep of entry.deps) {
|
|
177
|
+
result.add(dep.name);
|
|
178
|
+
const resolvedEntries = resolveEntries(
|
|
179
|
+
dep.name,
|
|
180
|
+
dep.range,
|
|
181
|
+
selectorToEntry,
|
|
182
|
+
nameToEntries
|
|
183
|
+
);
|
|
184
|
+
for (const resolvedEntry of resolvedEntries) {
|
|
185
|
+
if (!visitedEntries.has(resolvedEntry)) {
|
|
186
|
+
stack.push(resolvedEntry);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
const lockfilePath = "yarn.lock";
|
|
194
|
+
/**
|
|
195
|
+
* @param {string} targetName
|
|
196
|
+
* @returns {string[]}
|
|
197
|
+
*/
|
|
198
|
+
function listDependenciesOf(targetName) {
|
|
199
|
+
const resolvedLockfilePath = path.resolve(process.cwd(), lockfilePath);
|
|
200
|
+
if (!fs.existsSync(resolvedLockfilePath)) {
|
|
201
|
+
console.error("Lockfile not found: " + resolvedLockfilePath);
|
|
202
|
+
}
|
|
203
|
+
const lockText = fs.readFileSync(resolvedLockfilePath, "utf8");
|
|
204
|
+
const { selectorToEntry, nameToEntries } = parseLockfile(lockText);
|
|
205
|
+
const startEntries = nameToEntries.get(targetName) || [];
|
|
206
|
+
if (startEntries.length === 0) {
|
|
207
|
+
console.error("Module not found in yarn.lock: " + targetName);
|
|
208
|
+
}
|
|
209
|
+
const deps = collectDependencies(startEntries, selectorToEntry, nameToEntries);
|
|
210
|
+
return Array.from(deps).sort();
|
|
211
|
+
}
|
|
212
|
+
module.exports = { listDependenciesOf };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
list-deps-of() {
|
|
5
|
+
local module_name="${1:-}"
|
|
6
|
+
if [ -z "${module_name}" ]; then
|
|
7
|
+
echo "Usage: list-deps-of <module-name> [--lockfile <path>]" >&2
|
|
8
|
+
return 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
local script_dir
|
|
12
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
node "${script_dir}/list-deps-of.js" "$@"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
17
|
+
list-deps-of "$@"
|
|
18
|
+
fi
|
package/package.json
CHANGED
package/utils.d.ts
CHANGED
|
@@ -159,4 +159,5 @@ export const CI: boolean;
|
|
|
159
159
|
/**
|
|
160
160
|
* Nothing is logged in a CI environment.
|
|
161
161
|
*/
|
|
162
|
-
export const log: typeof console.log;
|
|
162
|
+
export const log: typeof console.log;
|
|
163
|
+
export const listDependenciesOf: typeof import("./extras/list-deps-of").listDependenciesOf;
|
package/utils.js
CHANGED
|
@@ -24,6 +24,7 @@ const fs = require("fs");
|
|
|
24
24
|
const path = require("path");
|
|
25
25
|
const lib = require("./common");
|
|
26
26
|
const tinArgs = require("tin-args");
|
|
27
|
+
const { listDependenciesOf } = require("./extras/list-deps-of");
|
|
27
28
|
const CI = !!process.env.CI;
|
|
28
29
|
/**
|
|
29
30
|
* Nothing is logged in a CI environment.
|
|
@@ -406,4 +407,5 @@ module.exports = {
|
|
|
406
407
|
fireReplace,
|
|
407
408
|
CI,
|
|
408
409
|
log,
|
|
410
|
+
listDependenciesOf
|
|
409
411
|
};
|