@rainy-updates/cli 0.4.4 → 0.5.0-rc.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/CHANGELOG.md +68 -0
- package/README.md +24 -1
- package/dist/bin/cli.js +132 -18
- package/dist/cache/cache.d.ts +1 -1
- package/dist/cache/cache.js +62 -9
- package/dist/config/loader.d.ts +8 -1
- package/dist/core/baseline.d.ts +23 -0
- package/dist/core/baseline.js +72 -0
- package/dist/core/check.js +25 -13
- package/dist/core/fix-pr.d.ts +7 -0
- package/dist/core/fix-pr.js +68 -0
- package/dist/core/init-ci.d.ts +1 -1
- package/dist/core/init-ci.js +9 -1
- package/dist/core/options.d.ts +6 -1
- package/dist/core/options.js +212 -9
- package/dist/core/warm-cache.js +5 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/output/format.js +3 -0
- package/dist/output/github.js +3 -0
- package/dist/output/sarif.js +21 -1
- package/dist/registry/npm.d.ts +14 -3
- package/dist/registry/npm.js +117 -25
- package/dist/types/index.d.ts +19 -0
- package/dist/utils/semver.d.ts +1 -0
- package/dist/utils/semver.js +24 -0
- package/dist/workspace/discover.js +50 -18
- package/package.json +1 -1
package/dist/output/sarif.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
1
4
|
export function createSarifReport(result) {
|
|
2
5
|
const dependencyRuleId = "rainy-updates/dependency-update";
|
|
3
6
|
const runtimeRuleId = "rainy-updates/runtime-error";
|
|
@@ -38,7 +41,7 @@ export function createSarifReport(result) {
|
|
|
38
41
|
tool: {
|
|
39
42
|
driver: {
|
|
40
43
|
name: "@rainy-updates/cli",
|
|
41
|
-
version:
|
|
44
|
+
version: getToolVersion(),
|
|
42
45
|
rules: [
|
|
43
46
|
{
|
|
44
47
|
id: dependencyRuleId,
|
|
@@ -58,3 +61,20 @@ export function createSarifReport(result) {
|
|
|
58
61
|
],
|
|
59
62
|
};
|
|
60
63
|
}
|
|
64
|
+
let TOOL_VERSION_CACHE = null;
|
|
65
|
+
function getToolVersion() {
|
|
66
|
+
if (TOOL_VERSION_CACHE)
|
|
67
|
+
return TOOL_VERSION_CACHE;
|
|
68
|
+
try {
|
|
69
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
70
|
+
const packageJsonPath = path.resolve(path.dirname(currentFile), "../../package.json");
|
|
71
|
+
const content = readFileSync(packageJsonPath, "utf8");
|
|
72
|
+
const parsed = JSON.parse(content);
|
|
73
|
+
TOOL_VERSION_CACHE = parsed.version ?? "0.0.0";
|
|
74
|
+
return TOOL_VERSION_CACHE;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
TOOL_VERSION_CACHE = "0.0.0";
|
|
78
|
+
return TOOL_VERSION_CACHE;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/registry/npm.d.ts
CHANGED
|
@@ -3,12 +3,23 @@ export interface ResolveManyOptions {
|
|
|
3
3
|
timeoutMs?: number;
|
|
4
4
|
}
|
|
5
5
|
export interface ResolveManyResult {
|
|
6
|
-
|
|
6
|
+
metadata: Map<string, {
|
|
7
|
+
latestVersion: string | null;
|
|
8
|
+
versions: string[];
|
|
9
|
+
}>;
|
|
7
10
|
errors: Map<string, string>;
|
|
8
11
|
}
|
|
9
12
|
export declare class NpmRegistryClient {
|
|
10
13
|
private readonly requesterPromise;
|
|
11
|
-
constructor();
|
|
14
|
+
constructor(cwd?: string);
|
|
15
|
+
resolvePackageMetadata(packageName: string, timeoutMs?: number): Promise<{
|
|
16
|
+
latestVersion: string | null;
|
|
17
|
+
versions: string[];
|
|
18
|
+
}>;
|
|
12
19
|
resolveLatestVersion(packageName: string, timeoutMs?: number): Promise<string | null>;
|
|
13
|
-
|
|
20
|
+
resolveManyPackageMetadata(packageNames: string[], options: ResolveManyOptions): Promise<ResolveManyResult>;
|
|
21
|
+
resolveManyLatestVersions(packageNames: string[], options: ResolveManyOptions): Promise<{
|
|
22
|
+
versions: Map<string, string | null>;
|
|
23
|
+
errors: Map<string, string>;
|
|
24
|
+
}>;
|
|
14
25
|
}
|
package/dist/registry/npm.js
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
1
5
|
import { asyncPool } from "../utils/async-pool.js";
|
|
2
6
|
const DEFAULT_TIMEOUT_MS = 8000;
|
|
3
7
|
const USER_AGENT = "@rainy-updates/cli";
|
|
8
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
4
9
|
export class NpmRegistryClient {
|
|
5
10
|
requesterPromise;
|
|
6
|
-
constructor() {
|
|
7
|
-
this.requesterPromise = createRequester();
|
|
11
|
+
constructor(cwd) {
|
|
12
|
+
this.requesterPromise = createRequester(cwd);
|
|
8
13
|
}
|
|
9
|
-
async
|
|
14
|
+
async resolvePackageMetadata(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
10
15
|
const requester = await this.requesterPromise;
|
|
11
16
|
let lastError = null;
|
|
12
17
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
13
18
|
try {
|
|
14
19
|
const response = await requester(packageName, timeoutMs);
|
|
15
|
-
if (response.status === 404)
|
|
16
|
-
return null;
|
|
20
|
+
if (response.status === 404) {
|
|
21
|
+
return { latestVersion: null, versions: [] };
|
|
22
|
+
}
|
|
17
23
|
if (response.status === 429 || response.status >= 500) {
|
|
18
24
|
throw new Error(`Registry temporary error: ${response.status}`);
|
|
19
25
|
}
|
|
20
26
|
if (response.status < 200 || response.status >= 300) {
|
|
21
27
|
throw new Error(`Registry request failed: ${response.status}`);
|
|
22
28
|
}
|
|
23
|
-
|
|
29
|
+
const versions = Object.keys(response.data?.versions ?? {});
|
|
30
|
+
return { latestVersion: response.data?.["dist-tags"]?.latest ?? null, versions };
|
|
24
31
|
}
|
|
25
32
|
catch (error) {
|
|
26
33
|
lastError = String(error);
|
|
@@ -31,18 +38,22 @@ export class NpmRegistryClient {
|
|
|
31
38
|
}
|
|
32
39
|
throw new Error(`Unable to resolve ${packageName}: ${lastError ?? "unknown error"}`);
|
|
33
40
|
}
|
|
34
|
-
async
|
|
41
|
+
async resolveLatestVersion(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
42
|
+
const metadata = await this.resolvePackageMetadata(packageName, timeoutMs);
|
|
43
|
+
return metadata.latestVersion;
|
|
44
|
+
}
|
|
45
|
+
async resolveManyPackageMetadata(packageNames, options) {
|
|
35
46
|
const unique = Array.from(new Set(packageNames));
|
|
36
|
-
const
|
|
47
|
+
const metadata = new Map();
|
|
37
48
|
const errors = new Map();
|
|
38
49
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
39
50
|
const results = await asyncPool(options.concurrency, unique.map((pkg) => async () => {
|
|
40
51
|
try {
|
|
41
|
-
const
|
|
42
|
-
return { pkg,
|
|
52
|
+
const packageMetadata = await this.resolvePackageMetadata(pkg, timeoutMs);
|
|
53
|
+
return { pkg, packageMetadata, error: null };
|
|
43
54
|
}
|
|
44
55
|
catch (error) {
|
|
45
|
-
return { pkg,
|
|
56
|
+
return { pkg, packageMetadata: null, error: String(error) };
|
|
46
57
|
}
|
|
47
58
|
}));
|
|
48
59
|
for (const result of results) {
|
|
@@ -52,25 +63,39 @@ export class NpmRegistryClient {
|
|
|
52
63
|
if (result.error) {
|
|
53
64
|
errors.set(result.pkg, result.error);
|
|
54
65
|
}
|
|
55
|
-
else {
|
|
56
|
-
|
|
66
|
+
else if (result.packageMetadata) {
|
|
67
|
+
metadata.set(result.pkg, result.packageMetadata);
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
|
-
return {
|
|
70
|
+
return { metadata, errors };
|
|
71
|
+
}
|
|
72
|
+
async resolveManyLatestVersions(packageNames, options) {
|
|
73
|
+
const metadataResult = await this.resolveManyPackageMetadata(packageNames, options);
|
|
74
|
+
const versions = new Map();
|
|
75
|
+
for (const [name, value] of metadataResult.metadata) {
|
|
76
|
+
versions.set(name, value.latestVersion);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
versions,
|
|
80
|
+
errors: metadataResult.errors,
|
|
81
|
+
};
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
84
|
function sleep(ms) {
|
|
63
85
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
64
86
|
}
|
|
65
|
-
async function createRequester() {
|
|
66
|
-
const
|
|
87
|
+
async function createRequester(cwd) {
|
|
88
|
+
const registryConfig = await loadRegistryConfig(cwd ?? process.cwd());
|
|
89
|
+
const undiciRequester = await tryCreateUndiciRequester(registryConfig);
|
|
67
90
|
if (undiciRequester)
|
|
68
91
|
return undiciRequester;
|
|
69
92
|
return async (packageName, timeoutMs) => {
|
|
70
93
|
const controller = new AbortController();
|
|
71
94
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
95
|
+
const registry = resolveRegistryForPackage(packageName, registryConfig);
|
|
96
|
+
const url = buildRegistryUrl(registry, packageName);
|
|
72
97
|
try {
|
|
73
|
-
const response = await fetch(
|
|
98
|
+
const response = await fetch(url, {
|
|
74
99
|
headers: {
|
|
75
100
|
accept: "application/json",
|
|
76
101
|
"user-agent": USER_AGENT,
|
|
@@ -85,21 +110,17 @@ async function createRequester() {
|
|
|
85
110
|
}
|
|
86
111
|
};
|
|
87
112
|
}
|
|
88
|
-
async function tryCreateUndiciRequester() {
|
|
113
|
+
async function tryCreateUndiciRequester(registryConfig) {
|
|
89
114
|
try {
|
|
90
115
|
const dynamicImport = Function("specifier", "return import(specifier)");
|
|
91
116
|
const undici = await dynamicImport("undici");
|
|
92
|
-
const pool = new undici.Pool("https://registry.npmjs.org", {
|
|
93
|
-
connections: 20,
|
|
94
|
-
pipelining: 10,
|
|
95
|
-
allowH2: true,
|
|
96
|
-
});
|
|
97
117
|
return async (packageName, timeoutMs) => {
|
|
98
118
|
const controller = new AbortController();
|
|
99
119
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
120
|
+
const registry = resolveRegistryForPackage(packageName, registryConfig);
|
|
121
|
+
const url = buildRegistryUrl(registry, packageName);
|
|
100
122
|
try {
|
|
101
|
-
const res = await
|
|
102
|
-
path: `/${encodeURIComponent(packageName)}`,
|
|
123
|
+
const res = await undici.request(url, {
|
|
103
124
|
method: "GET",
|
|
104
125
|
headers: {
|
|
105
126
|
accept: "application/json",
|
|
@@ -126,3 +147,74 @@ async function tryCreateUndiciRequester() {
|
|
|
126
147
|
return null;
|
|
127
148
|
}
|
|
128
149
|
}
|
|
150
|
+
async function loadRegistryConfig(cwd) {
|
|
151
|
+
const homeNpmrc = path.join(os.homedir(), ".npmrc");
|
|
152
|
+
const projectNpmrc = path.join(cwd, ".npmrc");
|
|
153
|
+
const merged = new Map();
|
|
154
|
+
for (const filePath of [homeNpmrc, projectNpmrc]) {
|
|
155
|
+
try {
|
|
156
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
157
|
+
const parsed = parseNpmrc(content);
|
|
158
|
+
for (const [key, value] of parsed) {
|
|
159
|
+
merged.set(key, value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// ignore missing/unreadable file
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const defaultRegistry = normalizeRegistryUrl(merged.get("registry") ?? DEFAULT_REGISTRY);
|
|
167
|
+
const scopedRegistries = new Map();
|
|
168
|
+
for (const [key, value] of merged) {
|
|
169
|
+
if (!key.startsWith("@") || !key.endsWith(":registry"))
|
|
170
|
+
continue;
|
|
171
|
+
const scope = key.slice(0, key.indexOf(":registry"));
|
|
172
|
+
if (scope.length > 1) {
|
|
173
|
+
scopedRegistries.set(scope, normalizeRegistryUrl(value));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { defaultRegistry, scopedRegistries };
|
|
177
|
+
}
|
|
178
|
+
function parseNpmrc(content) {
|
|
179
|
+
const values = new Map();
|
|
180
|
+
const lines = content.split(/\r?\n/);
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
const trimmed = line.trim();
|
|
183
|
+
if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.startsWith(";"))
|
|
184
|
+
continue;
|
|
185
|
+
const separator = trimmed.indexOf("=");
|
|
186
|
+
if (separator <= 0)
|
|
187
|
+
continue;
|
|
188
|
+
const key = trimmed.slice(0, separator).trim();
|
|
189
|
+
const value = trimmed.slice(separator + 1).trim();
|
|
190
|
+
if (key.length > 0 && value.length > 0) {
|
|
191
|
+
values.set(key, value);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return values;
|
|
195
|
+
}
|
|
196
|
+
function normalizeRegistryUrl(value) {
|
|
197
|
+
const normalized = value.endsWith("/") ? value : `${value}/`;
|
|
198
|
+
return normalized;
|
|
199
|
+
}
|
|
200
|
+
function resolveRegistryForPackage(packageName, config) {
|
|
201
|
+
const scope = extractScope(packageName);
|
|
202
|
+
if (scope) {
|
|
203
|
+
const scoped = config.scopedRegistries.get(scope);
|
|
204
|
+
if (scoped)
|
|
205
|
+
return scoped;
|
|
206
|
+
}
|
|
207
|
+
return config.defaultRegistry;
|
|
208
|
+
}
|
|
209
|
+
function extractScope(packageName) {
|
|
210
|
+
if (!packageName.startsWith("@"))
|
|
211
|
+
return null;
|
|
212
|
+
const firstSlash = packageName.indexOf("/");
|
|
213
|
+
if (firstSlash <= 1)
|
|
214
|
+
return null;
|
|
215
|
+
return packageName.slice(0, firstSlash);
|
|
216
|
+
}
|
|
217
|
+
function buildRegistryUrl(registry, packageName) {
|
|
218
|
+
const base = normalizeRegistryUrl(registry);
|
|
219
|
+
return new URL(encodeURIComponent(packageName), base).toString();
|
|
220
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type DependencyKind = "dependencies" | "devDependencies" | "optionalDependencies" | "peerDependencies";
|
|
2
2
|
export type TargetLevel = "patch" | "minor" | "major" | "latest";
|
|
3
3
|
export type OutputFormat = "table" | "json" | "minimal" | "github";
|
|
4
|
+
export type FailOnLevel = "none" | "patch" | "minor" | "major" | "any";
|
|
4
5
|
export interface RunOptions {
|
|
5
6
|
cwd: string;
|
|
6
7
|
target: TargetLevel;
|
|
@@ -18,6 +19,13 @@ export interface RunOptions {
|
|
|
18
19
|
offline: boolean;
|
|
19
20
|
policyFile?: string;
|
|
20
21
|
prReportFile?: string;
|
|
22
|
+
failOn?: FailOnLevel;
|
|
23
|
+
maxUpdates?: number;
|
|
24
|
+
fixPr?: boolean;
|
|
25
|
+
fixBranch?: string;
|
|
26
|
+
fixCommitMessage?: string;
|
|
27
|
+
fixDryRun?: boolean;
|
|
28
|
+
noPrReport?: boolean;
|
|
21
29
|
}
|
|
22
30
|
export interface CheckOptions extends RunOptions {
|
|
23
31
|
}
|
|
@@ -26,6 +34,13 @@ export interface UpgradeOptions extends RunOptions {
|
|
|
26
34
|
packageManager: "auto" | "npm" | "pnpm";
|
|
27
35
|
sync: boolean;
|
|
28
36
|
}
|
|
37
|
+
export interface BaselineOptions {
|
|
38
|
+
cwd: string;
|
|
39
|
+
workspace: boolean;
|
|
40
|
+
includeKinds: DependencyKind[];
|
|
41
|
+
filePath: string;
|
|
42
|
+
ci: boolean;
|
|
43
|
+
}
|
|
29
44
|
export interface PackageDependency {
|
|
30
45
|
name: string;
|
|
31
46
|
range: string;
|
|
@@ -50,6 +65,9 @@ export interface Summary {
|
|
|
50
65
|
upgraded: number;
|
|
51
66
|
skipped: number;
|
|
52
67
|
warmedPackages: number;
|
|
68
|
+
fixPrApplied?: boolean;
|
|
69
|
+
fixBranchName?: string;
|
|
70
|
+
fixCommitSha?: string;
|
|
53
71
|
}
|
|
54
72
|
export interface CheckResult {
|
|
55
73
|
projectPath: string;
|
|
@@ -78,6 +96,7 @@ export interface CachedVersion {
|
|
|
78
96
|
packageName: string;
|
|
79
97
|
target: TargetLevel;
|
|
80
98
|
latestVersion: string;
|
|
99
|
+
availableVersions: string[];
|
|
81
100
|
fetchedAt: number;
|
|
82
101
|
ttlSeconds: number;
|
|
83
102
|
}
|
package/dist/utils/semver.d.ts
CHANGED
|
@@ -9,5 +9,6 @@ export declare function parseVersion(raw: string): ParsedVersion | null;
|
|
|
9
9
|
export declare function compareVersions(a: ParsedVersion, b: ParsedVersion): number;
|
|
10
10
|
export declare function classifyDiff(currentRange: string, nextVersion: string): TargetLevel;
|
|
11
11
|
export declare function pickTargetVersion(currentRange: string, latestVersion: string, target: TargetLevel): string | null;
|
|
12
|
+
export declare function pickTargetVersionFromAvailable(currentRange: string, availableVersions: string[], latestVersion: string, target: TargetLevel): string | null;
|
|
12
13
|
export declare function applyRangeStyle(previousRange: string, version: string): string;
|
|
13
14
|
export declare function clampTarget(requested: TargetLevel, maxAllowed?: TargetLevel): TargetLevel;
|
package/dist/utils/semver.js
CHANGED
|
@@ -64,6 +64,30 @@ export function pickTargetVersion(currentRange, latestVersion, target) {
|
|
|
64
64
|
}
|
|
65
65
|
return latestVersion;
|
|
66
66
|
}
|
|
67
|
+
export function pickTargetVersionFromAvailable(currentRange, availableVersions, latestVersion, target) {
|
|
68
|
+
const current = parseVersion(currentRange);
|
|
69
|
+
if (!current || target === "latest")
|
|
70
|
+
return latestVersion;
|
|
71
|
+
const parsed = availableVersions
|
|
72
|
+
.map((version) => ({ raw: version, parsed: parseVersion(version) }))
|
|
73
|
+
.filter((item) => item.parsed !== null)
|
|
74
|
+
.filter((item) => compareVersions(item.parsed, current) > 0)
|
|
75
|
+
.sort((a, b) => compareVersions(a.parsed, b.parsed));
|
|
76
|
+
if (parsed.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
if (target === "major") {
|
|
79
|
+
return parsed[parsed.length - 1]?.raw ?? null;
|
|
80
|
+
}
|
|
81
|
+
if (target === "minor") {
|
|
82
|
+
const sameMajor = parsed.filter((item) => item.parsed.major === current.major);
|
|
83
|
+
return sameMajor.length > 0 ? sameMajor[sameMajor.length - 1].raw : null;
|
|
84
|
+
}
|
|
85
|
+
if (target === "patch") {
|
|
86
|
+
const sameLine = parsed.filter((item) => item.parsed.major === current.major && item.parsed.minor === current.minor);
|
|
87
|
+
return sameLine.length > 0 ? sameLine[sameLine.length - 1].raw : null;
|
|
88
|
+
}
|
|
89
|
+
return latestVersion;
|
|
90
|
+
}
|
|
67
91
|
export function applyRangeStyle(previousRange, version) {
|
|
68
92
|
const prefix = normalizeRangePrefix(previousRange);
|
|
69
93
|
return `${prefix}${version}`;
|
|
@@ -5,14 +5,21 @@ export async function discoverPackageDirs(cwd, workspaceMode) {
|
|
|
5
5
|
return [cwd];
|
|
6
6
|
}
|
|
7
7
|
const roots = new Set([cwd]);
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const patterns = [...(await readPackageJsonWorkspacePatterns(cwd)), ...(await readPnpmWorkspacePatterns(cwd))];
|
|
9
|
+
const include = patterns.filter((item) => !item.startsWith("!"));
|
|
10
|
+
const exclude = patterns.filter((item) => item.startsWith("!")).map((item) => item.slice(1));
|
|
11
|
+
for (const pattern of include) {
|
|
12
|
+
const dirs = await expandWorkspacePattern(cwd, pattern);
|
|
12
13
|
for (const dir of dirs) {
|
|
13
14
|
roots.add(dir);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
17
|
+
for (const pattern of exclude) {
|
|
18
|
+
const dirs = await expandWorkspacePattern(cwd, pattern);
|
|
19
|
+
for (const dir of dirs) {
|
|
20
|
+
roots.delete(dir);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
16
23
|
const existing = [];
|
|
17
24
|
for (const dir of roots) {
|
|
18
25
|
const packageJsonPath = path.join(dir, "package.json");
|
|
@@ -21,7 +28,7 @@ export async function discoverPackageDirs(cwd, workspaceMode) {
|
|
|
21
28
|
existing.push(dir);
|
|
22
29
|
}
|
|
23
30
|
catch {
|
|
24
|
-
// ignore
|
|
31
|
+
// ignore missing package.json
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
34
|
return existing.sort();
|
|
@@ -53,7 +60,7 @@ async function readPnpmWorkspacePatterns(cwd) {
|
|
|
53
60
|
const trimmed = line.trim();
|
|
54
61
|
if (!trimmed.startsWith("-"))
|
|
55
62
|
continue;
|
|
56
|
-
const value = trimmed.replace(/^-\s*/, "").replace(/^['
|
|
63
|
+
const value = trimmed.replace(/^-\s*/, "").replace(/^['"]|['"]$/g, "");
|
|
57
64
|
if (value.length > 0) {
|
|
58
65
|
patterns.push(value);
|
|
59
66
|
}
|
|
@@ -64,23 +71,48 @@ async function readPnpmWorkspacePatterns(cwd) {
|
|
|
64
71
|
return [];
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
|
-
async function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
const normalized = pattern.replace(/\\/g, "/");
|
|
72
|
-
const starIndex = normalized.indexOf("*");
|
|
73
|
-
const basePart = normalized.slice(0, starIndex).replace(/\/$/, "");
|
|
74
|
-
const suffix = normalized.slice(starIndex + 1);
|
|
75
|
-
if (suffix.length > 0 && suffix !== "/") {
|
|
74
|
+
async function expandWorkspacePattern(cwd, pattern) {
|
|
75
|
+
const normalized = pattern.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
76
|
+
if (normalized.length === 0)
|
|
76
77
|
return [];
|
|
78
|
+
if (!normalized.includes("*")) {
|
|
79
|
+
return [path.resolve(cwd, normalized)];
|
|
80
|
+
}
|
|
81
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
82
|
+
const results = new Set();
|
|
83
|
+
await collectMatches(path.resolve(cwd), segments, 0, results);
|
|
84
|
+
return Array.from(results);
|
|
85
|
+
}
|
|
86
|
+
async function collectMatches(baseDir, segments, index, out) {
|
|
87
|
+
if (index >= segments.length) {
|
|
88
|
+
out.add(baseDir);
|
|
89
|
+
return;
|
|
77
90
|
}
|
|
78
|
-
const
|
|
91
|
+
const segment = segments[index];
|
|
92
|
+
if (segment === "**") {
|
|
93
|
+
await collectMatches(baseDir, segments, index + 1, out);
|
|
94
|
+
const children = await readChildDirs(baseDir);
|
|
95
|
+
for (const child of children) {
|
|
96
|
+
await collectMatches(child, segments, index, out);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (segment === "*") {
|
|
101
|
+
const children = await readChildDirs(baseDir);
|
|
102
|
+
for (const child of children) {
|
|
103
|
+
await collectMatches(child, segments, index + 1, out);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await collectMatches(path.join(baseDir, segment), segments, index + 1, out);
|
|
108
|
+
}
|
|
109
|
+
async function readChildDirs(dir) {
|
|
79
110
|
try {
|
|
80
|
-
const entries = await fs.readdir(
|
|
111
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
81
112
|
return entries
|
|
82
113
|
.filter((entry) => entry.isDirectory())
|
|
83
|
-
.
|
|
114
|
+
.filter((entry) => entry.name !== "node_modules" && !entry.name.startsWith("."))
|
|
115
|
+
.map((entry) => path.join(dir, entry.name));
|
|
84
116
|
}
|
|
85
117
|
catch {
|
|
86
118
|
return [];
|