@rainy-updates/cli 0.5.0-rc.1 → 0.5.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/CHANGELOG.md +68 -0
- package/README.md +10 -0
- package/dist/bin/cli.js +93 -32
- package/dist/cache/cache.d.ts +2 -0
- package/dist/cache/cache.js +8 -3
- package/dist/config/loader.d.ts +8 -1
- package/dist/config/policy.d.ts +15 -4
- package/dist/config/policy.js +35 -7
- package/dist/core/check.js +78 -11
- package/dist/core/fix-pr.d.ts +7 -0
- package/dist/core/fix-pr.js +83 -0
- package/dist/core/options.js +107 -7
- package/dist/core/summary.d.ts +22 -0
- package/dist/core/summary.js +78 -0
- package/dist/core/warm-cache.js +34 -6
- package/dist/output/format.js +23 -1
- package/dist/output/github.js +23 -9
- package/dist/output/sarif.js +16 -2
- package/dist/registry/npm.d.ts +1 -1
- package/dist/registry/npm.js +136 -17
- package/dist/types/index.d.ts +33 -1
- package/dist/utils/io.d.ts +1 -0
- package/dist/utils/io.js +10 -0
- package/dist/utils/stable-json.d.ts +1 -0
- package/dist/utils/stable-json.js +20 -0
- package/dist/workspace/discover.js +56 -18
- package/package.json +2 -1
package/dist/registry/npm.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
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
14
|
async resolvePackageMetadata(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
10
15
|
const requester = await this.requesterPromise;
|
|
@@ -16,7 +21,7 @@ export class NpmRegistryClient {
|
|
|
16
21
|
return { latestVersion: null, versions: [] };
|
|
17
22
|
}
|
|
18
23
|
if (response.status === 429 || response.status >= 500) {
|
|
19
|
-
throw new
|
|
24
|
+
throw new RetryableRegistryError(`Registry temporary error: ${response.status}`, response.retryAfterMs ?? computeBackoffMs(attempt));
|
|
20
25
|
}
|
|
21
26
|
if (response.status < 200 || response.status >= 300) {
|
|
22
27
|
throw new Error(`Registry request failed: ${response.status}`);
|
|
@@ -27,7 +32,8 @@ export class NpmRegistryClient {
|
|
|
27
32
|
catch (error) {
|
|
28
33
|
lastError = String(error);
|
|
29
34
|
if (attempt < 3) {
|
|
30
|
-
|
|
35
|
+
const backoffMs = error instanceof RetryableRegistryError ? error.waitMs : computeBackoffMs(attempt);
|
|
36
|
+
await sleep(backoffMs);
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -79,15 +85,30 @@ export class NpmRegistryClient {
|
|
|
79
85
|
function sleep(ms) {
|
|
80
86
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
81
87
|
}
|
|
82
|
-
|
|
83
|
-
const
|
|
88
|
+
function computeBackoffMs(attempt) {
|
|
89
|
+
const baseMs = Math.max(120, attempt * 180);
|
|
90
|
+
const jitterMs = Math.floor(Math.random() * 120);
|
|
91
|
+
return baseMs + jitterMs;
|
|
92
|
+
}
|
|
93
|
+
class RetryableRegistryError extends Error {
|
|
94
|
+
waitMs;
|
|
95
|
+
constructor(message, waitMs) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.waitMs = waitMs;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function createRequester(cwd) {
|
|
101
|
+
const registryConfig = await loadRegistryConfig(cwd ?? process.cwd());
|
|
102
|
+
const undiciRequester = await tryCreateUndiciRequester(registryConfig);
|
|
84
103
|
if (undiciRequester)
|
|
85
104
|
return undiciRequester;
|
|
86
105
|
return async (packageName, timeoutMs) => {
|
|
87
106
|
const controller = new AbortController();
|
|
88
107
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
108
|
+
const registry = resolveRegistryForPackage(packageName, registryConfig);
|
|
109
|
+
const url = buildRegistryUrl(registry, packageName);
|
|
89
110
|
try {
|
|
90
|
-
const response = await fetch(
|
|
111
|
+
const response = await fetch(url, {
|
|
91
112
|
headers: {
|
|
92
113
|
accept: "application/json",
|
|
93
114
|
"user-agent": USER_AGENT,
|
|
@@ -95,28 +116,28 @@ async function createRequester() {
|
|
|
95
116
|
signal: controller.signal,
|
|
96
117
|
});
|
|
97
118
|
const data = (await response.json().catch(() => null));
|
|
98
|
-
return {
|
|
119
|
+
return {
|
|
120
|
+
status: response.status,
|
|
121
|
+
data,
|
|
122
|
+
retryAfterMs: parseRetryAfterHeader(response.headers.get("retry-after")),
|
|
123
|
+
};
|
|
99
124
|
}
|
|
100
125
|
finally {
|
|
101
126
|
clearTimeout(timeout);
|
|
102
127
|
}
|
|
103
128
|
};
|
|
104
129
|
}
|
|
105
|
-
async function tryCreateUndiciRequester() {
|
|
130
|
+
async function tryCreateUndiciRequester(registryConfig) {
|
|
106
131
|
try {
|
|
107
132
|
const dynamicImport = Function("specifier", "return import(specifier)");
|
|
108
133
|
const undici = await dynamicImport("undici");
|
|
109
|
-
const pool = new undici.Pool("https://registry.npmjs.org", {
|
|
110
|
-
connections: 20,
|
|
111
|
-
pipelining: 10,
|
|
112
|
-
allowH2: true,
|
|
113
|
-
});
|
|
114
134
|
return async (packageName, timeoutMs) => {
|
|
115
135
|
const controller = new AbortController();
|
|
116
136
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
137
|
+
const registry = resolveRegistryForPackage(packageName, registryConfig);
|
|
138
|
+
const url = buildRegistryUrl(registry, packageName);
|
|
117
139
|
try {
|
|
118
|
-
const res = await
|
|
119
|
-
path: `/${encodeURIComponent(packageName)}`,
|
|
140
|
+
const res = await undici.request(url, {
|
|
120
141
|
method: "GET",
|
|
121
142
|
headers: {
|
|
122
143
|
accept: "application/json",
|
|
@@ -132,7 +153,19 @@ async function tryCreateUndiciRequester() {
|
|
|
132
153
|
catch {
|
|
133
154
|
data = null;
|
|
134
155
|
}
|
|
135
|
-
|
|
156
|
+
const retryAfter = (() => {
|
|
157
|
+
const header = res.headers["retry-after"];
|
|
158
|
+
if (Array.isArray(header))
|
|
159
|
+
return header[0] ?? null;
|
|
160
|
+
if (typeof header === "string")
|
|
161
|
+
return header;
|
|
162
|
+
return null;
|
|
163
|
+
})();
|
|
164
|
+
return {
|
|
165
|
+
status: res.statusCode,
|
|
166
|
+
data,
|
|
167
|
+
retryAfterMs: parseRetryAfterHeader(retryAfter),
|
|
168
|
+
};
|
|
136
169
|
}
|
|
137
170
|
finally {
|
|
138
171
|
clearTimeout(timeout);
|
|
@@ -143,3 +176,89 @@ async function tryCreateUndiciRequester() {
|
|
|
143
176
|
return null;
|
|
144
177
|
}
|
|
145
178
|
}
|
|
179
|
+
async function loadRegistryConfig(cwd) {
|
|
180
|
+
const homeNpmrc = path.join(os.homedir(), ".npmrc");
|
|
181
|
+
const projectNpmrc = path.join(cwd, ".npmrc");
|
|
182
|
+
const merged = new Map();
|
|
183
|
+
for (const filePath of [homeNpmrc, projectNpmrc]) {
|
|
184
|
+
try {
|
|
185
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
186
|
+
const parsed = parseNpmrc(content);
|
|
187
|
+
for (const [key, value] of parsed) {
|
|
188
|
+
merged.set(key, value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// ignore missing/unreadable file
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const defaultRegistry = normalizeRegistryUrl(merged.get("registry") ?? DEFAULT_REGISTRY);
|
|
196
|
+
const scopedRegistries = new Map();
|
|
197
|
+
for (const [key, value] of merged) {
|
|
198
|
+
if (!key.startsWith("@") || !key.endsWith(":registry"))
|
|
199
|
+
continue;
|
|
200
|
+
const scope = key.slice(0, key.indexOf(":registry"));
|
|
201
|
+
if (scope.length > 1) {
|
|
202
|
+
scopedRegistries.set(scope, normalizeRegistryUrl(value));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { defaultRegistry, scopedRegistries };
|
|
206
|
+
}
|
|
207
|
+
function parseNpmrc(content) {
|
|
208
|
+
const values = new Map();
|
|
209
|
+
const lines = content.split(/\r?\n/);
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
const trimmed = line.trim();
|
|
212
|
+
if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.startsWith(";"))
|
|
213
|
+
continue;
|
|
214
|
+
const separator = trimmed.indexOf("=");
|
|
215
|
+
if (separator <= 0)
|
|
216
|
+
continue;
|
|
217
|
+
const key = trimmed.slice(0, separator).trim();
|
|
218
|
+
const value = trimmed.slice(separator + 1).trim();
|
|
219
|
+
if (key.length > 0 && value.length > 0) {
|
|
220
|
+
values.set(key, value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return values;
|
|
224
|
+
}
|
|
225
|
+
function normalizeRegistryUrl(value) {
|
|
226
|
+
const normalized = value.endsWith("/") ? value : `${value}/`;
|
|
227
|
+
return normalized;
|
|
228
|
+
}
|
|
229
|
+
function resolveRegistryForPackage(packageName, config) {
|
|
230
|
+
const scope = extractScope(packageName);
|
|
231
|
+
if (scope) {
|
|
232
|
+
const scoped = config.scopedRegistries.get(scope);
|
|
233
|
+
if (scoped)
|
|
234
|
+
return scoped;
|
|
235
|
+
}
|
|
236
|
+
return config.defaultRegistry;
|
|
237
|
+
}
|
|
238
|
+
function extractScope(packageName) {
|
|
239
|
+
if (!packageName.startsWith("@"))
|
|
240
|
+
return null;
|
|
241
|
+
const firstSlash = packageName.indexOf("/");
|
|
242
|
+
if (firstSlash <= 1)
|
|
243
|
+
return null;
|
|
244
|
+
return packageName.slice(0, firstSlash);
|
|
245
|
+
}
|
|
246
|
+
function buildRegistryUrl(registry, packageName) {
|
|
247
|
+
const base = normalizeRegistryUrl(registry);
|
|
248
|
+
return new URL(encodeURIComponent(packageName), base).toString();
|
|
249
|
+
}
|
|
250
|
+
function parseRetryAfterHeader(value) {
|
|
251
|
+
if (!value)
|
|
252
|
+
return null;
|
|
253
|
+
const parsedSeconds = Number(value);
|
|
254
|
+
if (Number.isFinite(parsedSeconds) && parsedSeconds >= 0) {
|
|
255
|
+
return Math.round(parsedSeconds * 1000);
|
|
256
|
+
}
|
|
257
|
+
const untilMs = Date.parse(value);
|
|
258
|
+
if (!Number.isFinite(untilMs))
|
|
259
|
+
return null;
|
|
260
|
+
const delta = untilMs - Date.now();
|
|
261
|
+
if (delta <= 0)
|
|
262
|
+
return 0;
|
|
263
|
+
return delta;
|
|
264
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export type DependencyKind = "dependencies" | "devDependencies" | "optionalDependencies" | "peerDependencies";
|
|
2
2
|
export type TargetLevel = "patch" | "minor" | "major" | "latest";
|
|
3
|
-
export type OutputFormat = "table" | "json" | "minimal" | "github";
|
|
3
|
+
export type OutputFormat = "table" | "json" | "minimal" | "github" | "metrics";
|
|
4
4
|
export type FailOnLevel = "none" | "patch" | "minor" | "major" | "any";
|
|
5
|
+
export type LogLevel = "error" | "warn" | "info" | "debug";
|
|
6
|
+
export type FailReason = "none" | "updates-threshold" | "severity-threshold" | "registry-failure" | "offline-cache-miss" | "policy-blocked";
|
|
5
7
|
export interface RunOptions {
|
|
6
8
|
cwd: string;
|
|
7
9
|
target: TargetLevel;
|
|
@@ -21,6 +23,13 @@ export interface RunOptions {
|
|
|
21
23
|
prReportFile?: string;
|
|
22
24
|
failOn?: FailOnLevel;
|
|
23
25
|
maxUpdates?: number;
|
|
26
|
+
fixPr?: boolean;
|
|
27
|
+
fixBranch?: string;
|
|
28
|
+
fixCommitMessage?: string;
|
|
29
|
+
fixDryRun?: boolean;
|
|
30
|
+
fixPrNoCheckout?: boolean;
|
|
31
|
+
noPrReport?: boolean;
|
|
32
|
+
logLevel: LogLevel;
|
|
24
33
|
}
|
|
25
34
|
export interface CheckOptions extends RunOptions {
|
|
26
35
|
}
|
|
@@ -53,6 +62,7 @@ export interface PackageUpdate {
|
|
|
53
62
|
reason?: string;
|
|
54
63
|
}
|
|
55
64
|
export interface Summary {
|
|
65
|
+
contractVersion: "2";
|
|
56
66
|
scannedPackages: number;
|
|
57
67
|
totalDependencies: number;
|
|
58
68
|
checkedDependencies: number;
|
|
@@ -60,6 +70,28 @@ export interface Summary {
|
|
|
60
70
|
upgraded: number;
|
|
61
71
|
skipped: number;
|
|
62
72
|
warmedPackages: number;
|
|
73
|
+
failReason: FailReason;
|
|
74
|
+
errorCounts: {
|
|
75
|
+
total: number;
|
|
76
|
+
offlineCacheMiss: number;
|
|
77
|
+
registryFailure: number;
|
|
78
|
+
other: number;
|
|
79
|
+
};
|
|
80
|
+
warningCounts: {
|
|
81
|
+
total: number;
|
|
82
|
+
staleCache: number;
|
|
83
|
+
other: number;
|
|
84
|
+
};
|
|
85
|
+
durationMs: {
|
|
86
|
+
total: number;
|
|
87
|
+
discovery: number;
|
|
88
|
+
registry: number;
|
|
89
|
+
cache: number;
|
|
90
|
+
render: number;
|
|
91
|
+
};
|
|
92
|
+
fixPrApplied: boolean;
|
|
93
|
+
fixBranchName: string;
|
|
94
|
+
fixCommitSha: string;
|
|
63
95
|
}
|
|
64
96
|
export interface CheckResult {
|
|
65
97
|
projectPath: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeFileAtomic(filePath: string, content: string): Promise<void>;
|
package/dist/utils/io.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
export async function writeFileAtomic(filePath, content) {
|
|
5
|
+
const dir = path.dirname(filePath);
|
|
6
|
+
await fs.mkdir(dir, { recursive: true });
|
|
7
|
+
const tempPath = path.join(dir, `.tmp-${path.basename(filePath)}-${crypto.randomUUID()}`);
|
|
8
|
+
await fs.writeFile(tempPath, content, "utf8");
|
|
9
|
+
await fs.rename(tempPath, filePath);
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function stableStringify(value: unknown, indent?: number): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function sortValue(value) {
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return value.map((item) => sortValue(item));
|
|
7
|
+
}
|
|
8
|
+
if (!isPlainObject(value)) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
const sorted = {};
|
|
12
|
+
const keys = Object.keys(value).sort((a, b) => a.localeCompare(b));
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
sorted[key] = sortValue(value[key]);
|
|
15
|
+
}
|
|
16
|
+
return sorted;
|
|
17
|
+
}
|
|
18
|
+
export function stableStringify(value, indent = 2) {
|
|
19
|
+
return JSON.stringify(sortValue(value), null, indent);
|
|
20
|
+
}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
const HARD_IGNORE_DIRS = new Set(["node_modules", ".git", ".turbo", ".next", "dist", "coverage"]);
|
|
4
|
+
const MAX_DISCOVERED_DIRS = 20000;
|
|
3
5
|
export async function discoverPackageDirs(cwd, workspaceMode) {
|
|
4
6
|
if (!workspaceMode) {
|
|
5
7
|
return [cwd];
|
|
6
8
|
}
|
|
7
9
|
const roots = new Set([cwd]);
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const patterns = [...(await readPackageJsonWorkspacePatterns(cwd)), ...(await readPnpmWorkspacePatterns(cwd))];
|
|
11
|
+
const include = patterns.filter((item) => !item.startsWith("!"));
|
|
12
|
+
const exclude = patterns.filter((item) => item.startsWith("!")).map((item) => item.slice(1));
|
|
13
|
+
for (const pattern of include) {
|
|
14
|
+
const dirs = await expandWorkspacePattern(cwd, pattern);
|
|
12
15
|
for (const dir of dirs) {
|
|
13
16
|
roots.add(dir);
|
|
14
17
|
}
|
|
15
18
|
}
|
|
19
|
+
for (const pattern of exclude) {
|
|
20
|
+
const dirs = await expandWorkspacePattern(cwd, pattern);
|
|
21
|
+
for (const dir of dirs) {
|
|
22
|
+
roots.delete(dir);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
16
25
|
const existing = [];
|
|
17
26
|
for (const dir of roots) {
|
|
18
27
|
const packageJsonPath = path.join(dir, "package.json");
|
|
@@ -21,7 +30,7 @@ export async function discoverPackageDirs(cwd, workspaceMode) {
|
|
|
21
30
|
existing.push(dir);
|
|
22
31
|
}
|
|
23
32
|
catch {
|
|
24
|
-
// ignore
|
|
33
|
+
// ignore missing package.json
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
return existing.sort();
|
|
@@ -53,7 +62,7 @@ async function readPnpmWorkspacePatterns(cwd) {
|
|
|
53
62
|
const trimmed = line.trim();
|
|
54
63
|
if (!trimmed.startsWith("-"))
|
|
55
64
|
continue;
|
|
56
|
-
const value = trimmed.replace(/^-\s*/, "").replace(/^['
|
|
65
|
+
const value = trimmed.replace(/^-\s*/, "").replace(/^['"]|['"]$/g, "");
|
|
57
66
|
if (value.length > 0) {
|
|
58
67
|
patterns.push(value);
|
|
59
68
|
}
|
|
@@ -64,23 +73,52 @@ async function readPnpmWorkspacePatterns(cwd) {
|
|
|
64
73
|
return [];
|
|
65
74
|
}
|
|
66
75
|
}
|
|
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 !== "/") {
|
|
76
|
+
async function expandWorkspacePattern(cwd, pattern) {
|
|
77
|
+
const normalized = pattern.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
78
|
+
if (normalized.length === 0)
|
|
76
79
|
return [];
|
|
80
|
+
if (!normalized.includes("*")) {
|
|
81
|
+
return [path.resolve(cwd, normalized)];
|
|
82
|
+
}
|
|
83
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
84
|
+
const results = new Set();
|
|
85
|
+
await collectMatches(path.resolve(cwd), segments, 0, results);
|
|
86
|
+
return Array.from(results);
|
|
87
|
+
}
|
|
88
|
+
async function collectMatches(baseDir, segments, index, out) {
|
|
89
|
+
if (out.size > MAX_DISCOVERED_DIRS) {
|
|
90
|
+
throw new Error(`Workspace discovery exceeded ${MAX_DISCOVERED_DIRS} directories. Refine workspace patterns.`);
|
|
77
91
|
}
|
|
78
|
-
|
|
92
|
+
if (index >= segments.length) {
|
|
93
|
+
out.add(baseDir);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const segment = segments[index];
|
|
97
|
+
if (segment === "**") {
|
|
98
|
+
await collectMatches(baseDir, segments, index + 1, out);
|
|
99
|
+
const children = await readChildDirs(baseDir);
|
|
100
|
+
for (const child of children) {
|
|
101
|
+
await collectMatches(child, segments, index, out);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (segment === "*") {
|
|
106
|
+
const children = await readChildDirs(baseDir);
|
|
107
|
+
for (const child of children) {
|
|
108
|
+
await collectMatches(child, segments, index + 1, out);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await collectMatches(path.join(baseDir, segment), segments, index + 1, out);
|
|
113
|
+
}
|
|
114
|
+
async function readChildDirs(dir) {
|
|
79
115
|
try {
|
|
80
|
-
const entries = await fs.readdir(
|
|
116
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
81
117
|
return entries
|
|
82
118
|
.filter((entry) => entry.isDirectory())
|
|
83
|
-
.
|
|
119
|
+
.filter((entry) => !HARD_IGNORE_DIRS.has(entry.name))
|
|
120
|
+
.filter((entry) => !entry.name.startsWith("."))
|
|
121
|
+
.map((entry) => path.join(dir, entry.name));
|
|
84
122
|
}
|
|
85
123
|
catch {
|
|
86
124
|
return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainy-updates/cli",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Agentic CLI to check and upgrade npm/pnpm dependencies for CI workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"test": "bun test",
|
|
48
48
|
"lint": "bunx biome check src tests",
|
|
49
49
|
"check": "bun run typecheck && bun test",
|
|
50
|
+
"perf:smoke": "node scripts/perf-smoke.mjs",
|
|
50
51
|
"test:prod": "node dist/bin/cli.js --help && node dist/bin/cli.js --version",
|
|
51
52
|
"prepublishOnly": "bun run check && bun run build && bun run test:prod"
|
|
52
53
|
},
|