@rainy-updates/cli 0.5.1-rc.2 → 0.5.1
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 +84 -1
- package/README.md +8 -1
- package/dist/bin/cli.js +62 -12
- package/dist/commands/audit/fetcher.d.ts +6 -0
- package/dist/commands/audit/fetcher.js +79 -0
- package/dist/commands/audit/mapper.d.ts +16 -0
- package/dist/commands/audit/mapper.js +61 -0
- package/dist/commands/audit/parser.d.ts +3 -0
- package/dist/commands/audit/parser.js +87 -0
- package/dist/commands/audit/runner.d.ts +7 -0
- package/dist/commands/audit/runner.js +64 -0
- package/dist/commands/bisect/engine.d.ts +12 -0
- package/dist/commands/bisect/engine.js +89 -0
- package/dist/commands/bisect/oracle.d.ts +7 -0
- package/dist/commands/bisect/oracle.js +36 -0
- package/dist/commands/bisect/parser.d.ts +2 -0
- package/dist/commands/bisect/parser.js +73 -0
- package/dist/commands/bisect/runner.d.ts +6 -0
- package/dist/commands/bisect/runner.js +27 -0
- package/dist/commands/health/parser.d.ts +2 -0
- package/dist/commands/health/parser.js +90 -0
- package/dist/commands/health/runner.d.ts +7 -0
- package/dist/commands/health/runner.js +130 -0
- package/dist/config/loader.d.ts +5 -1
- package/dist/config/policy.d.ts +4 -0
- package/dist/config/policy.js +2 -0
- package/dist/core/check.js +56 -3
- package/dist/core/fix-pr-batch.js +3 -2
- package/dist/core/fix-pr.js +19 -4
- package/dist/core/init-ci.js +3 -3
- package/dist/core/options.d.ts +10 -1
- package/dist/core/options.js +129 -13
- package/dist/core/summary.d.ts +1 -0
- package/dist/core/summary.js +11 -1
- package/dist/core/upgrade.js +10 -0
- package/dist/core/warm-cache.js +19 -1
- package/dist/output/format.js +4 -0
- package/dist/output/github.js +3 -0
- package/dist/registry/npm.d.ts +9 -2
- package/dist/registry/npm.js +87 -17
- package/dist/types/index.d.ts +83 -0
- package/dist/utils/lockfile.d.ts +5 -0
- package/dist/utils/lockfile.js +44 -0
- package/package.json +13 -4
package/dist/registry/npm.js
CHANGED
|
@@ -8,13 +8,17 @@ const USER_AGENT = "@rainy-updates/cli";
|
|
|
8
8
|
const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
9
9
|
export class NpmRegistryClient {
|
|
10
10
|
requesterPromise;
|
|
11
|
-
|
|
11
|
+
defaultTimeoutMs;
|
|
12
|
+
defaultRetries;
|
|
13
|
+
constructor(cwd, options) {
|
|
12
14
|
this.requesterPromise = createRequester(cwd);
|
|
15
|
+
this.defaultTimeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
16
|
+
this.defaultRetries = Math.max(1, options?.retries ?? 3);
|
|
13
17
|
}
|
|
14
|
-
async resolvePackageMetadata(packageName, timeoutMs =
|
|
18
|
+
async resolvePackageMetadata(packageName, timeoutMs = this.defaultTimeoutMs, retries = this.defaultRetries) {
|
|
15
19
|
const requester = await this.requesterPromise;
|
|
16
20
|
let lastError = null;
|
|
17
|
-
for (let attempt = 1; attempt <=
|
|
21
|
+
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
|
18
22
|
try {
|
|
19
23
|
const response = await requester(packageName, timeoutMs);
|
|
20
24
|
if (response.status === 404) {
|
|
@@ -35,7 +39,7 @@ export class NpmRegistryClient {
|
|
|
35
39
|
}
|
|
36
40
|
catch (error) {
|
|
37
41
|
lastError = String(error);
|
|
38
|
-
if (attempt <
|
|
42
|
+
if (attempt < retries) {
|
|
39
43
|
const backoffMs = error instanceof RetryableRegistryError ? error.waitMs : computeBackoffMs(attempt);
|
|
40
44
|
await sleep(backoffMs);
|
|
41
45
|
}
|
|
@@ -43,18 +47,19 @@ export class NpmRegistryClient {
|
|
|
43
47
|
}
|
|
44
48
|
throw new Error(`Unable to resolve ${packageName}: ${lastError ?? "unknown error"}`);
|
|
45
49
|
}
|
|
46
|
-
async resolveLatestVersion(packageName, timeoutMs =
|
|
47
|
-
const metadata = await this.resolvePackageMetadata(packageName, timeoutMs);
|
|
50
|
+
async resolveLatestVersion(packageName, timeoutMs = this.defaultTimeoutMs) {
|
|
51
|
+
const metadata = await this.resolvePackageMetadata(packageName, timeoutMs, this.defaultRetries);
|
|
48
52
|
return metadata.latestVersion;
|
|
49
53
|
}
|
|
50
54
|
async resolveManyPackageMetadata(packageNames, options) {
|
|
51
55
|
const unique = Array.from(new Set(packageNames));
|
|
52
56
|
const metadata = new Map();
|
|
53
57
|
const errors = new Map();
|
|
54
|
-
const timeoutMs = options.timeoutMs ??
|
|
58
|
+
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
59
|
+
const retries = options.retries ?? this.defaultRetries;
|
|
55
60
|
const results = await asyncPool(options.concurrency, unique.map((pkg) => async () => {
|
|
56
61
|
try {
|
|
57
|
-
const packageMetadata = await this.resolvePackageMetadata(pkg, timeoutMs);
|
|
62
|
+
const packageMetadata = await this.resolvePackageMetadata(pkg, timeoutMs, retries);
|
|
58
63
|
return { pkg, packageMetadata, error: null };
|
|
59
64
|
}
|
|
60
65
|
catch (error) {
|
|
@@ -111,12 +116,17 @@ async function createRequester(cwd) {
|
|
|
111
116
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
112
117
|
const registry = resolveRegistryForPackage(packageName, registryConfig);
|
|
113
118
|
const url = buildRegistryUrl(registry, packageName);
|
|
119
|
+
const authHeader = resolveAuthHeader(registry, registryConfig);
|
|
120
|
+
const headers = {
|
|
121
|
+
accept: "application/json",
|
|
122
|
+
"user-agent": USER_AGENT,
|
|
123
|
+
};
|
|
124
|
+
if (authHeader) {
|
|
125
|
+
headers.authorization = authHeader;
|
|
126
|
+
}
|
|
114
127
|
try {
|
|
115
128
|
const response = await fetch(url, {
|
|
116
|
-
headers
|
|
117
|
-
accept: "application/json",
|
|
118
|
-
"user-agent": USER_AGENT,
|
|
119
|
-
},
|
|
129
|
+
headers,
|
|
120
130
|
signal: controller.signal,
|
|
121
131
|
});
|
|
122
132
|
const data = (await response.json().catch(() => null));
|
|
@@ -140,13 +150,18 @@ async function tryCreateUndiciRequester(registryConfig) {
|
|
|
140
150
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
141
151
|
const registry = resolveRegistryForPackage(packageName, registryConfig);
|
|
142
152
|
const url = buildRegistryUrl(registry, packageName);
|
|
153
|
+
const authHeader = resolveAuthHeader(registry, registryConfig);
|
|
154
|
+
const headers = {
|
|
155
|
+
accept: "application/json",
|
|
156
|
+
"user-agent": USER_AGENT,
|
|
157
|
+
};
|
|
158
|
+
if (authHeader) {
|
|
159
|
+
headers.authorization = authHeader;
|
|
160
|
+
}
|
|
143
161
|
try {
|
|
144
162
|
const res = await undici.request(url, {
|
|
145
163
|
method: "GET",
|
|
146
|
-
headers
|
|
147
|
-
accept: "application/json",
|
|
148
|
-
"user-agent": USER_AGENT,
|
|
149
|
-
},
|
|
164
|
+
headers,
|
|
150
165
|
signal: controller.signal,
|
|
151
166
|
});
|
|
152
167
|
const bodyText = await res.body.text();
|
|
@@ -198,6 +213,7 @@ async function loadRegistryConfig(cwd) {
|
|
|
198
213
|
}
|
|
199
214
|
const defaultRegistry = normalizeRegistryUrl(merged.get("registry") ?? DEFAULT_REGISTRY);
|
|
200
215
|
const scopedRegistries = new Map();
|
|
216
|
+
const authByRegistry = new Map();
|
|
201
217
|
for (const [key, value] of merged) {
|
|
202
218
|
if (!key.startsWith("@") || !key.endsWith(":registry"))
|
|
203
219
|
continue;
|
|
@@ -206,7 +222,32 @@ async function loadRegistryConfig(cwd) {
|
|
|
206
222
|
scopedRegistries.set(scope, normalizeRegistryUrl(value));
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
|
-
|
|
225
|
+
for (const [key, value] of merged) {
|
|
226
|
+
if (!key.startsWith("//"))
|
|
227
|
+
continue;
|
|
228
|
+
const [registryKey, authKey] = key.split(/:(.+)/).filter(Boolean);
|
|
229
|
+
if (!registryKey || !authKey)
|
|
230
|
+
continue;
|
|
231
|
+
const registry = normalizeRegistryUrl(`https:${registryKey}`);
|
|
232
|
+
const current = authByRegistry.get(registry) ?? { alwaysAuth: false };
|
|
233
|
+
const resolvedValue = substituteEnvValue(value);
|
|
234
|
+
if (authKey === "_authToken") {
|
|
235
|
+
current.token = resolvedValue;
|
|
236
|
+
}
|
|
237
|
+
else if (authKey === "_auth") {
|
|
238
|
+
current.basicAuth = resolvedValue;
|
|
239
|
+
}
|
|
240
|
+
else if (authKey === "always-auth") {
|
|
241
|
+
current.alwaysAuth = resolvedValue === "true";
|
|
242
|
+
}
|
|
243
|
+
authByRegistry.set(registry, current);
|
|
244
|
+
}
|
|
245
|
+
if (merged.get("always-auth") === "true") {
|
|
246
|
+
const current = authByRegistry.get(defaultRegistry) ?? { alwaysAuth: false };
|
|
247
|
+
current.alwaysAuth = true;
|
|
248
|
+
authByRegistry.set(defaultRegistry, current);
|
|
249
|
+
}
|
|
250
|
+
return { defaultRegistry, scopedRegistries, authByRegistry };
|
|
210
251
|
}
|
|
211
252
|
function parseNpmrc(content) {
|
|
212
253
|
const values = new Map();
|
|
@@ -226,6 +267,9 @@ function parseNpmrc(content) {
|
|
|
226
267
|
}
|
|
227
268
|
return values;
|
|
228
269
|
}
|
|
270
|
+
function substituteEnvValue(value) {
|
|
271
|
+
return value.replace(/\$\{([^}]+)\}/g, (_match, name) => process.env[name] ?? "");
|
|
272
|
+
}
|
|
229
273
|
function normalizeRegistryUrl(value) {
|
|
230
274
|
const normalized = value.endsWith("/") ? value : `${value}/`;
|
|
231
275
|
return normalized;
|
|
@@ -251,6 +295,32 @@ function buildRegistryUrl(registry, packageName) {
|
|
|
251
295
|
const base = normalizeRegistryUrl(registry);
|
|
252
296
|
return new URL(encodeURIComponent(packageName), base).toString();
|
|
253
297
|
}
|
|
298
|
+
function resolveAuthHeader(registry, config) {
|
|
299
|
+
const registryUrl = normalizeRegistryUrl(registry);
|
|
300
|
+
const auth = findRegistryAuth(registryUrl, config.authByRegistry);
|
|
301
|
+
if (!auth)
|
|
302
|
+
return undefined;
|
|
303
|
+
if (!auth.alwaysAuth && !registryUrl.startsWith("https://"))
|
|
304
|
+
return undefined;
|
|
305
|
+
if (auth.token)
|
|
306
|
+
return `Bearer ${auth.token}`;
|
|
307
|
+
if (auth.basicAuth)
|
|
308
|
+
return `Basic ${auth.basicAuth}`;
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
function findRegistryAuth(registry, authByRegistry) {
|
|
312
|
+
let matched;
|
|
313
|
+
let longest = -1;
|
|
314
|
+
for (const [candidate, auth] of authByRegistry) {
|
|
315
|
+
if (!registry.startsWith(candidate))
|
|
316
|
+
continue;
|
|
317
|
+
if (candidate.length > longest) {
|
|
318
|
+
matched = auth;
|
|
319
|
+
longest = candidate.length;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return matched;
|
|
323
|
+
}
|
|
254
324
|
function parseRetryAfterHeader(value) {
|
|
255
325
|
if (!value)
|
|
256
326
|
return null;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type DependencyKind = "dependencies" | "devDependencies" | "optionalDepen
|
|
|
2
2
|
export type TargetLevel = "patch" | "minor" | "major" | "latest";
|
|
3
3
|
export type GroupBy = "none" | "name" | "scope" | "kind" | "risk";
|
|
4
4
|
export type CiProfile = "minimal" | "strict" | "enterprise";
|
|
5
|
+
export type LockfileMode = "preserve" | "update" | "error";
|
|
5
6
|
export type OutputFormat = "table" | "json" | "minimal" | "github" | "metrics";
|
|
6
7
|
export type FailOnLevel = "none" | "patch" | "minor" | "major" | "any";
|
|
7
8
|
export type LogLevel = "error" | "warn" | "info" | "debug";
|
|
@@ -20,7 +21,10 @@ export interface RunOptions {
|
|
|
20
21
|
githubOutputFile?: string;
|
|
21
22
|
sarifFile?: string;
|
|
22
23
|
concurrency: number;
|
|
24
|
+
registryTimeoutMs: number;
|
|
25
|
+
registryRetries: number;
|
|
23
26
|
offline: boolean;
|
|
27
|
+
stream: boolean;
|
|
24
28
|
policyFile?: string;
|
|
25
29
|
prReportFile?: string;
|
|
26
30
|
failOn?: FailOnLevel;
|
|
@@ -39,6 +43,7 @@ export interface RunOptions {
|
|
|
39
43
|
prLimit?: number;
|
|
40
44
|
onlyChanged: boolean;
|
|
41
45
|
ciProfile: CiProfile;
|
|
46
|
+
lockfileMode: LockfileMode;
|
|
42
47
|
}
|
|
43
48
|
export interface CheckOptions extends RunOptions {
|
|
44
49
|
}
|
|
@@ -68,6 +73,7 @@ export interface PackageUpdate {
|
|
|
68
73
|
toVersionResolved: string;
|
|
69
74
|
diffType: TargetLevel;
|
|
70
75
|
filtered: boolean;
|
|
76
|
+
autofix: boolean;
|
|
71
77
|
reason?: string;
|
|
72
78
|
}
|
|
73
79
|
export interface Summary {
|
|
@@ -84,6 +90,7 @@ export interface Summary {
|
|
|
84
90
|
total: number;
|
|
85
91
|
offlineCacheMiss: number;
|
|
86
92
|
registryFailure: number;
|
|
93
|
+
registryAuthFailure: number;
|
|
87
94
|
other: number;
|
|
88
95
|
};
|
|
89
96
|
warningCounts: {
|
|
@@ -106,6 +113,8 @@ export interface Summary {
|
|
|
106
113
|
cooldownSkipped: number;
|
|
107
114
|
ciProfile: CiProfile;
|
|
108
115
|
prLimitHit: boolean;
|
|
116
|
+
streamedEvents: number;
|
|
117
|
+
policyOverridesApplied: number;
|
|
109
118
|
}
|
|
110
119
|
export interface CheckResult {
|
|
111
120
|
projectPath: string;
|
|
@@ -141,3 +150,77 @@ export interface CachedVersion {
|
|
|
141
150
|
export interface VersionResolver {
|
|
142
151
|
resolveLatestVersion(packageName: string): Promise<string | null>;
|
|
143
152
|
}
|
|
153
|
+
export type AuditSeverity = "critical" | "high" | "medium" | "low";
|
|
154
|
+
export type AuditReportFormat = "table" | "json";
|
|
155
|
+
export interface AuditOptions {
|
|
156
|
+
cwd: string;
|
|
157
|
+
workspace: boolean;
|
|
158
|
+
severity?: AuditSeverity;
|
|
159
|
+
fix: boolean;
|
|
160
|
+
dryRun: boolean;
|
|
161
|
+
reportFormat: AuditReportFormat;
|
|
162
|
+
jsonFile?: string;
|
|
163
|
+
concurrency: number;
|
|
164
|
+
registryTimeoutMs: number;
|
|
165
|
+
}
|
|
166
|
+
export interface CveAdvisory {
|
|
167
|
+
cveId: string;
|
|
168
|
+
packageName: string;
|
|
169
|
+
severity: AuditSeverity;
|
|
170
|
+
vulnerableRange: string;
|
|
171
|
+
patchedVersion: string | null;
|
|
172
|
+
title: string;
|
|
173
|
+
url: string;
|
|
174
|
+
}
|
|
175
|
+
export interface AuditResult {
|
|
176
|
+
advisories: CveAdvisory[];
|
|
177
|
+
autoFixable: number;
|
|
178
|
+
errors: string[];
|
|
179
|
+
warnings: string[];
|
|
180
|
+
}
|
|
181
|
+
export interface BisectOptions {
|
|
182
|
+
cwd: string;
|
|
183
|
+
packageName: string;
|
|
184
|
+
versionRange?: string;
|
|
185
|
+
testCommand: string;
|
|
186
|
+
concurrency: number;
|
|
187
|
+
registryTimeoutMs: number;
|
|
188
|
+
cacheTtlSeconds: number;
|
|
189
|
+
dryRun: boolean;
|
|
190
|
+
}
|
|
191
|
+
export type BisectOutcome = "good" | "bad" | "skip";
|
|
192
|
+
export interface BisectResult {
|
|
193
|
+
packageName: string;
|
|
194
|
+
breakingVersion: string | null;
|
|
195
|
+
lastGoodVersion: string | null;
|
|
196
|
+
totalVersionsTested: number;
|
|
197
|
+
iterations: number;
|
|
198
|
+
}
|
|
199
|
+
export type HealthFlag = "stale" | "deprecated" | "archived" | "unmaintained";
|
|
200
|
+
export interface HealthOptions {
|
|
201
|
+
cwd: string;
|
|
202
|
+
workspace: boolean;
|
|
203
|
+
staleDays: number;
|
|
204
|
+
includeDeprecated: boolean;
|
|
205
|
+
includeAlternatives: boolean;
|
|
206
|
+
reportFormat: "table" | "json";
|
|
207
|
+
jsonFile?: string;
|
|
208
|
+
concurrency: number;
|
|
209
|
+
registryTimeoutMs: number;
|
|
210
|
+
}
|
|
211
|
+
export interface PackageHealthMetric {
|
|
212
|
+
name: string;
|
|
213
|
+
currentVersion: string;
|
|
214
|
+
lastPublished: string | null;
|
|
215
|
+
isDeprecated: boolean;
|
|
216
|
+
deprecatedMessage?: string;
|
|
217
|
+
isArchived: boolean;
|
|
218
|
+
daysSinceLastRelease: number | null;
|
|
219
|
+
flags: HealthFlag[];
|
|
220
|
+
}
|
|
221
|
+
export interface HealthResult {
|
|
222
|
+
metrics: PackageHealthMetric[];
|
|
223
|
+
totalFlagged: number;
|
|
224
|
+
errors: string[];
|
|
225
|
+
warnings: string[];
|
|
226
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LockfileMode } from "../types/index.js";
|
|
2
|
+
export type LockfileSnapshot = Map<string, string | null>;
|
|
3
|
+
export declare function captureLockfileSnapshot(cwd: string): Promise<LockfileSnapshot>;
|
|
4
|
+
export declare function changedLockfiles(cwd: string, before: LockfileSnapshot): Promise<string[]>;
|
|
5
|
+
export declare function validateLockfileMode(mode: LockfileMode, install: boolean): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
const LOCKFILE_NAMES = ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock"];
|
|
5
|
+
export async function captureLockfileSnapshot(cwd) {
|
|
6
|
+
const snapshot = new Map();
|
|
7
|
+
for (const name of LOCKFILE_NAMES) {
|
|
8
|
+
const filePath = path.join(cwd, name);
|
|
9
|
+
try {
|
|
10
|
+
const content = await fs.readFile(filePath);
|
|
11
|
+
snapshot.set(filePath, hashBuffer(content));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
snapshot.set(filePath, null);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return snapshot;
|
|
18
|
+
}
|
|
19
|
+
export async function changedLockfiles(cwd, before) {
|
|
20
|
+
const changed = [];
|
|
21
|
+
for (const name of LOCKFILE_NAMES) {
|
|
22
|
+
const filePath = path.join(cwd, name);
|
|
23
|
+
let current = null;
|
|
24
|
+
try {
|
|
25
|
+
const content = await fs.readFile(filePath);
|
|
26
|
+
current = hashBuffer(content);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
current = null;
|
|
30
|
+
}
|
|
31
|
+
if ((before.get(filePath) ?? null) !== current) {
|
|
32
|
+
changed.push(filePath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return changed.sort((a, b) => a.localeCompare(b));
|
|
36
|
+
}
|
|
37
|
+
export function validateLockfileMode(mode, install) {
|
|
38
|
+
if (mode === "update" && !install) {
|
|
39
|
+
throw new Error("--lockfile-mode update requires --install to update lockfiles deterministically.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function hashBuffer(value) {
|
|
43
|
+
return createHash("sha256").update(value).digest("hex");
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainy-updates/cli",
|
|
3
|
-
"version": "0.5.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "The fastest DevOps-first dependency CLI. Checks, audits, upgrades, bisects, and automates npm/pnpm dependencies in CI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"license": "MIT",
|
|
@@ -18,12 +18,21 @@
|
|
|
18
18
|
"updates",
|
|
19
19
|
"cli",
|
|
20
20
|
"ci",
|
|
21
|
+
"devops",
|
|
21
22
|
"npm",
|
|
22
23
|
"pnpm",
|
|
23
|
-
"monorepo"
|
|
24
|
+
"monorepo",
|
|
25
|
+
"audit",
|
|
26
|
+
"security",
|
|
27
|
+
"bisect",
|
|
28
|
+
"ncu",
|
|
29
|
+
"taze",
|
|
30
|
+
"renovate"
|
|
24
31
|
],
|
|
25
32
|
"bin": {
|
|
26
|
-
"rainy-updates": "./dist/bin/cli.js"
|
|
33
|
+
"rainy-updates": "./dist/bin/cli.js",
|
|
34
|
+
"rainy-up": "./dist/bin/cli.js",
|
|
35
|
+
"rup": "./dist/bin/cli.js"
|
|
27
36
|
},
|
|
28
37
|
"types": "./dist/index.d.ts",
|
|
29
38
|
"exports": {
|