@rainy-updates/cli 0.4.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 +158 -0
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +146 -0
- package/dist/cache/cache.d.ts +9 -0
- package/dist/cache/cache.js +125 -0
- package/dist/config/loader.d.ts +22 -0
- package/dist/config/loader.js +35 -0
- package/dist/config/policy.d.ts +16 -0
- package/dist/config/policy.js +32 -0
- package/dist/core/check.d.ts +2 -0
- package/dist/core/check.js +141 -0
- package/dist/core/init-ci.d.ts +4 -0
- package/dist/core/init-ci.js +20 -0
- package/dist/core/options.d.ts +17 -0
- package/dist/core/options.js +238 -0
- package/dist/core/upgrade.d.ts +2 -0
- package/dist/core/upgrade.js +88 -0
- package/dist/core/warm-cache.d.ts +2 -0
- package/dist/core/warm-cache.js +89 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/output/format.d.ts +2 -0
- package/dist/output/format.js +56 -0
- package/dist/output/github.d.ts +3 -0
- package/dist/output/github.js +30 -0
- package/dist/output/pr-report.d.ts +2 -0
- package/dist/output/pr-report.js +38 -0
- package/dist/output/sarif.d.ts +2 -0
- package/dist/output/sarif.js +60 -0
- package/dist/parsers/package-json.d.ts +5 -0
- package/dist/parsers/package-json.js +35 -0
- package/dist/pm/detect.d.ts +1 -0
- package/dist/pm/detect.js +20 -0
- package/dist/pm/install.d.ts +1 -0
- package/dist/pm/install.js +21 -0
- package/dist/registry/npm.d.ts +14 -0
- package/dist/registry/npm.js +128 -0
- package/dist/types/index.d.ts +86 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/async-pool.d.ts +1 -0
- package/dist/utils/async-pool.js +21 -0
- package/dist/utils/pattern.d.ts +1 -0
- package/dist/utils/pattern.js +11 -0
- package/dist/utils/semver.d.ts +13 -0
- package/dist/utils/semver.js +80 -0
- package/dist/workspace/discover.d.ts +1 -0
- package/dist/workspace/discover.js +88 -0
- package/dist/workspace/graph.d.ts +13 -0
- package/dist/workspace/graph.js +86 -0
- package/package.json +66 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { renderGitHubAnnotations } from "./github.js";
|
|
2
|
+
export function renderResult(result, format) {
|
|
3
|
+
if (format === "json") {
|
|
4
|
+
return JSON.stringify(result, null, 2);
|
|
5
|
+
}
|
|
6
|
+
if (format === "minimal") {
|
|
7
|
+
if (result.updates.length === 0 && result.summary.warmedPackages > 0) {
|
|
8
|
+
return `Cache warmed for ${result.summary.warmedPackages} package(s).`;
|
|
9
|
+
}
|
|
10
|
+
if (result.updates.length === 0)
|
|
11
|
+
return "No updates found.";
|
|
12
|
+
return result.updates
|
|
13
|
+
.map((item) => `${item.packagePath} :: ${item.name}: ${item.fromRange} -> ${item.toRange}`)
|
|
14
|
+
.join("\n");
|
|
15
|
+
}
|
|
16
|
+
if (format === "github") {
|
|
17
|
+
return renderGitHubAnnotations(result);
|
|
18
|
+
}
|
|
19
|
+
const lines = [];
|
|
20
|
+
lines.push(`Project: ${result.projectPath}`);
|
|
21
|
+
lines.push(`Scanned packages: ${result.summary.scannedPackages}`);
|
|
22
|
+
lines.push(`Package manager: ${result.packageManager}`);
|
|
23
|
+
lines.push(`Target: ${result.target}`);
|
|
24
|
+
lines.push("");
|
|
25
|
+
if (result.updates.length === 0) {
|
|
26
|
+
if (result.summary.warmedPackages > 0) {
|
|
27
|
+
lines.push(`Cache warmed for ${result.summary.warmedPackages} package(s).`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
lines.push("No updates found.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
lines.push("Updates:");
|
|
35
|
+
for (const update of result.updates) {
|
|
36
|
+
lines.push(`- ${update.packagePath} :: ${update.name} [${update.kind}] ${update.fromRange} -> ${update.toRange} (${update.diffType})`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (result.errors.length > 0) {
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push("Errors:");
|
|
42
|
+
for (const error of result.errors) {
|
|
43
|
+
lines.push(`- ${error}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (result.warnings.length > 0) {
|
|
47
|
+
lines.push("");
|
|
48
|
+
lines.push("Warnings:");
|
|
49
|
+
for (const warning of result.warnings) {
|
|
50
|
+
lines.push(`- ${warning}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
lines.push("");
|
|
54
|
+
lines.push(`Summary: ${result.summary.updatesFound} updates, ${result.summary.checkedDependencies}/${result.summary.totalDependencies} checked, ${result.summary.warmedPackages} warmed`);
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function writeGitHubOutput(filePath, result) {
|
|
4
|
+
const lines = [
|
|
5
|
+
`updates_found=${result.summary.updatesFound}`,
|
|
6
|
+
`errors_count=${result.errors.length}`,
|
|
7
|
+
`warnings_count=${result.warnings.length}`,
|
|
8
|
+
`checked_dependencies=${result.summary.checkedDependencies}`,
|
|
9
|
+
`scanned_packages=${result.summary.scannedPackages}`,
|
|
10
|
+
`warmed_packages=${result.summary.warmedPackages}`,
|
|
11
|
+
];
|
|
12
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
13
|
+
await fs.writeFile(filePath, lines.join("\n") + "\n", "utf8");
|
|
14
|
+
}
|
|
15
|
+
export function renderGitHubAnnotations(result) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
for (const update of result.updates) {
|
|
18
|
+
lines.push(`::notice title=Dependency Update::${update.name} ${update.fromRange} -> ${update.toRange} (${update.packagePath})`);
|
|
19
|
+
}
|
|
20
|
+
for (const warning of result.warnings) {
|
|
21
|
+
lines.push(`::warning title=Rainy Updates::${warning}`);
|
|
22
|
+
}
|
|
23
|
+
for (const error of result.errors) {
|
|
24
|
+
lines.push(`::error title=Rainy Updates::${error}`);
|
|
25
|
+
}
|
|
26
|
+
if (lines.length === 0) {
|
|
27
|
+
lines.push("::notice title=Rainy Updates::No updates found");
|
|
28
|
+
}
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function renderPrReport(result) {
|
|
2
|
+
const lines = [];
|
|
3
|
+
lines.push("# Dependency Update Report");
|
|
4
|
+
lines.push("");
|
|
5
|
+
lines.push(`- Scanned packages: ${result.summary.scannedPackages}`);
|
|
6
|
+
lines.push(`- Checked dependencies: ${result.summary.checkedDependencies}`);
|
|
7
|
+
lines.push(`- Updates found: ${result.summary.updatesFound}`);
|
|
8
|
+
lines.push(`- Errors: ${result.errors.length}`);
|
|
9
|
+
lines.push(`- Warnings: ${result.warnings.length}`);
|
|
10
|
+
lines.push("");
|
|
11
|
+
if (result.updates.length > 0) {
|
|
12
|
+
lines.push("## Proposed Updates");
|
|
13
|
+
lines.push("");
|
|
14
|
+
lines.push("| Package | From | To | Type | Path |");
|
|
15
|
+
lines.push("|---|---|---|---|---|");
|
|
16
|
+
for (const update of result.updates) {
|
|
17
|
+
lines.push(`| ${update.name} | ${update.fromRange} | ${update.toRange} | ${update.diffType} | ${update.packagePath} |`);
|
|
18
|
+
}
|
|
19
|
+
lines.push("");
|
|
20
|
+
}
|
|
21
|
+
if (result.errors.length > 0) {
|
|
22
|
+
lines.push("## Errors");
|
|
23
|
+
lines.push("");
|
|
24
|
+
for (const error of result.errors) {
|
|
25
|
+
lines.push(`- ${error}`);
|
|
26
|
+
}
|
|
27
|
+
lines.push("");
|
|
28
|
+
}
|
|
29
|
+
if (result.warnings.length > 0) {
|
|
30
|
+
lines.push("## Warnings");
|
|
31
|
+
lines.push("");
|
|
32
|
+
for (const warning of result.warnings) {
|
|
33
|
+
lines.push(`- ${warning}`);
|
|
34
|
+
}
|
|
35
|
+
lines.push("");
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function createSarifReport(result) {
|
|
2
|
+
const dependencyRuleId = "rainy-updates/dependency-update";
|
|
3
|
+
const runtimeRuleId = "rainy-updates/runtime-error";
|
|
4
|
+
const updateResults = result.updates.map((update) => ({
|
|
5
|
+
ruleId: dependencyRuleId,
|
|
6
|
+
level: "warning",
|
|
7
|
+
message: {
|
|
8
|
+
text: `${update.name} can be updated from ${update.fromRange} to ${update.toRange}`,
|
|
9
|
+
},
|
|
10
|
+
locations: [
|
|
11
|
+
{
|
|
12
|
+
physicalLocation: {
|
|
13
|
+
artifactLocation: {
|
|
14
|
+
uri: `${update.packagePath}/package.json`,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
properties: {
|
|
20
|
+
dependency: update.name,
|
|
21
|
+
kind: update.kind,
|
|
22
|
+
diffType: update.diffType,
|
|
23
|
+
resolvedVersion: update.toVersionResolved,
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
const errorResults = result.errors.map((error) => ({
|
|
27
|
+
ruleId: runtimeRuleId,
|
|
28
|
+
level: "error",
|
|
29
|
+
message: {
|
|
30
|
+
text: error,
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
return {
|
|
34
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
35
|
+
version: "2.1.0",
|
|
36
|
+
runs: [
|
|
37
|
+
{
|
|
38
|
+
tool: {
|
|
39
|
+
driver: {
|
|
40
|
+
name: "@rainy-updates/cli",
|
|
41
|
+
version: "0.1.0",
|
|
42
|
+
rules: [
|
|
43
|
+
{
|
|
44
|
+
id: dependencyRuleId,
|
|
45
|
+
shortDescription: { text: "Dependency update available" },
|
|
46
|
+
fullDescription: { text: "A dependency has a newer version according to configured target." },
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: runtimeRuleId,
|
|
50
|
+
shortDescription: { text: "Dependency resolution error" },
|
|
51
|
+
fullDescription: { text: "The resolver could not fetch or parse package metadata." },
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
results: [...updateResults, ...errorResults],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DependencyKind, PackageDependency, PackageManifest } from "../types/index.js";
|
|
2
|
+
export declare function getPackageJsonPath(cwd: string): string;
|
|
3
|
+
export declare function readManifest(cwd: string): Promise<PackageManifest>;
|
|
4
|
+
export declare function writeManifest(cwd: string, manifest: PackageManifest): Promise<void>;
|
|
5
|
+
export declare function collectDependencies(manifest: PackageManifest, includeKinds: DependencyKind[]): PackageDependency[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const DEPENDENCY_KINDS = [
|
|
4
|
+
"dependencies",
|
|
5
|
+
"devDependencies",
|
|
6
|
+
"optionalDependencies",
|
|
7
|
+
"peerDependencies",
|
|
8
|
+
];
|
|
9
|
+
export function getPackageJsonPath(cwd) {
|
|
10
|
+
return path.join(cwd, "package.json");
|
|
11
|
+
}
|
|
12
|
+
export async function readManifest(cwd) {
|
|
13
|
+
const filePath = getPackageJsonPath(cwd);
|
|
14
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
15
|
+
return JSON.parse(content);
|
|
16
|
+
}
|
|
17
|
+
export async function writeManifest(cwd, manifest) {
|
|
18
|
+
const filePath = getPackageJsonPath(cwd);
|
|
19
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
20
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
21
|
+
}
|
|
22
|
+
export function collectDependencies(manifest, includeKinds) {
|
|
23
|
+
const deps = [];
|
|
24
|
+
for (const kind of DEPENDENCY_KINDS) {
|
|
25
|
+
if (!includeKinds.includes(kind))
|
|
26
|
+
continue;
|
|
27
|
+
const section = manifest[kind];
|
|
28
|
+
if (!section || typeof section !== "object")
|
|
29
|
+
continue;
|
|
30
|
+
for (const [name, range] of Object.entries(section)) {
|
|
31
|
+
deps.push({ name, range, kind });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return deps;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function detectPackageManager(cwd: string): Promise<"npm" | "pnpm" | "unknown">;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function detectPackageManager(cwd) {
|
|
4
|
+
const pnpmLock = path.join(cwd, "pnpm-lock.yaml");
|
|
5
|
+
const npmLock = path.join(cwd, "package-lock.json");
|
|
6
|
+
try {
|
|
7
|
+
await access(pnpmLock);
|
|
8
|
+
return "pnpm";
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// noop
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
await access(npmLock);
|
|
15
|
+
return "npm";
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return "unknown";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installDependencies(cwd: string, packageManager: "auto" | "npm" | "pnpm", detected: "npm" | "pnpm" | "unknown"): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
export async function installDependencies(cwd, packageManager, detected) {
|
|
3
|
+
const selected = packageManager === "auto" ? (detected === "unknown" ? "npm" : detected) : packageManager;
|
|
4
|
+
const command = selected;
|
|
5
|
+
const args = ["install"];
|
|
6
|
+
await new Promise((resolve, reject) => {
|
|
7
|
+
const child = spawn(command, args, {
|
|
8
|
+
cwd,
|
|
9
|
+
stdio: "inherit",
|
|
10
|
+
shell: process.platform === "win32",
|
|
11
|
+
});
|
|
12
|
+
child.on("exit", (code) => {
|
|
13
|
+
if (code === 0) {
|
|
14
|
+
resolve();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code ?? "unknown"}`));
|
|
18
|
+
});
|
|
19
|
+
child.on("error", reject);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ResolveManyOptions {
|
|
2
|
+
concurrency: number;
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface ResolveManyResult {
|
|
6
|
+
versions: Map<string, string | null>;
|
|
7
|
+
errors: Map<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export declare class NpmRegistryClient {
|
|
10
|
+
private readonly requesterPromise;
|
|
11
|
+
constructor();
|
|
12
|
+
resolveLatestVersion(packageName: string, timeoutMs?: number): Promise<string | null>;
|
|
13
|
+
resolveManyLatestVersions(packageNames: string[], options: ResolveManyOptions): Promise<ResolveManyResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { asyncPool } from "../utils/async-pool.js";
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 8000;
|
|
3
|
+
const USER_AGENT = "@rainy-updates/cli";
|
|
4
|
+
export class NpmRegistryClient {
|
|
5
|
+
requesterPromise;
|
|
6
|
+
constructor() {
|
|
7
|
+
this.requesterPromise = createRequester();
|
|
8
|
+
}
|
|
9
|
+
async resolveLatestVersion(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
10
|
+
const requester = await this.requesterPromise;
|
|
11
|
+
let lastError = null;
|
|
12
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
13
|
+
try {
|
|
14
|
+
const response = await requester(packageName, timeoutMs);
|
|
15
|
+
if (response.status === 404)
|
|
16
|
+
return null;
|
|
17
|
+
if (response.status === 429 || response.status >= 500) {
|
|
18
|
+
throw new Error(`Registry temporary error: ${response.status}`);
|
|
19
|
+
}
|
|
20
|
+
if (response.status < 200 || response.status >= 300) {
|
|
21
|
+
throw new Error(`Registry request failed: ${response.status}`);
|
|
22
|
+
}
|
|
23
|
+
return response.data?.["dist-tags"]?.latest ?? null;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
lastError = String(error);
|
|
27
|
+
if (attempt < 3) {
|
|
28
|
+
await sleep(120 * attempt);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Unable to resolve ${packageName}: ${lastError ?? "unknown error"}`);
|
|
33
|
+
}
|
|
34
|
+
async resolveManyLatestVersions(packageNames, options) {
|
|
35
|
+
const unique = Array.from(new Set(packageNames));
|
|
36
|
+
const versions = new Map();
|
|
37
|
+
const errors = new Map();
|
|
38
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
39
|
+
const results = await asyncPool(options.concurrency, unique.map((pkg) => async () => {
|
|
40
|
+
try {
|
|
41
|
+
const latest = await this.resolveLatestVersion(pkg, timeoutMs);
|
|
42
|
+
return { pkg, latest, error: null };
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return { pkg, latest: null, error: String(error) };
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
48
|
+
for (const result of results) {
|
|
49
|
+
if (result instanceof Error) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (result.error) {
|
|
53
|
+
errors.set(result.pkg, result.error);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
versions.set(result.pkg, result.latest);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { versions, errors };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function sleep(ms) {
|
|
63
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
64
|
+
}
|
|
65
|
+
async function createRequester() {
|
|
66
|
+
const undiciRequester = await tryCreateUndiciRequester();
|
|
67
|
+
if (undiciRequester)
|
|
68
|
+
return undiciRequester;
|
|
69
|
+
return async (packageName, timeoutMs) => {
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, {
|
|
74
|
+
headers: {
|
|
75
|
+
accept: "application/json",
|
|
76
|
+
"user-agent": USER_AGENT,
|
|
77
|
+
},
|
|
78
|
+
signal: controller.signal,
|
|
79
|
+
});
|
|
80
|
+
const data = (await response.json().catch(() => null));
|
|
81
|
+
return { status: response.status, data };
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async function tryCreateUndiciRequester() {
|
|
89
|
+
try {
|
|
90
|
+
const dynamicImport = Function("specifier", "return import(specifier)");
|
|
91
|
+
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
|
+
return async (packageName, timeoutMs) => {
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
100
|
+
try {
|
|
101
|
+
const res = await pool.request({
|
|
102
|
+
path: `/${encodeURIComponent(packageName)}`,
|
|
103
|
+
method: "GET",
|
|
104
|
+
headers: {
|
|
105
|
+
accept: "application/json",
|
|
106
|
+
"user-agent": USER_AGENT,
|
|
107
|
+
},
|
|
108
|
+
signal: controller.signal,
|
|
109
|
+
});
|
|
110
|
+
const bodyText = await res.body.text();
|
|
111
|
+
let data = null;
|
|
112
|
+
try {
|
|
113
|
+
data = JSON.parse(bodyText);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
data = null;
|
|
117
|
+
}
|
|
118
|
+
return { status: res.statusCode, data };
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type DependencyKind = "dependencies" | "devDependencies" | "optionalDependencies" | "peerDependencies";
|
|
2
|
+
export type TargetLevel = "patch" | "minor" | "major" | "latest";
|
|
3
|
+
export type OutputFormat = "table" | "json" | "minimal" | "github";
|
|
4
|
+
export interface RunOptions {
|
|
5
|
+
cwd: string;
|
|
6
|
+
target: TargetLevel;
|
|
7
|
+
filter?: string;
|
|
8
|
+
reject?: string;
|
|
9
|
+
cacheTtlSeconds: number;
|
|
10
|
+
includeKinds: DependencyKind[];
|
|
11
|
+
ci: boolean;
|
|
12
|
+
format: OutputFormat;
|
|
13
|
+
workspace: boolean;
|
|
14
|
+
jsonFile?: string;
|
|
15
|
+
githubOutputFile?: string;
|
|
16
|
+
sarifFile?: string;
|
|
17
|
+
concurrency: number;
|
|
18
|
+
offline: boolean;
|
|
19
|
+
policyFile?: string;
|
|
20
|
+
prReportFile?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CheckOptions extends RunOptions {
|
|
23
|
+
}
|
|
24
|
+
export interface UpgradeOptions extends RunOptions {
|
|
25
|
+
install: boolean;
|
|
26
|
+
packageManager: "auto" | "npm" | "pnpm";
|
|
27
|
+
sync: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface PackageDependency {
|
|
30
|
+
name: string;
|
|
31
|
+
range: string;
|
|
32
|
+
kind: DependencyKind;
|
|
33
|
+
}
|
|
34
|
+
export interface PackageUpdate {
|
|
35
|
+
packagePath: string;
|
|
36
|
+
name: string;
|
|
37
|
+
kind: DependencyKind;
|
|
38
|
+
fromRange: string;
|
|
39
|
+
toRange: string;
|
|
40
|
+
toVersionResolved: string;
|
|
41
|
+
diffType: TargetLevel;
|
|
42
|
+
filtered: boolean;
|
|
43
|
+
reason?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface Summary {
|
|
46
|
+
scannedPackages: number;
|
|
47
|
+
totalDependencies: number;
|
|
48
|
+
checkedDependencies: number;
|
|
49
|
+
updatesFound: number;
|
|
50
|
+
upgraded: number;
|
|
51
|
+
skipped: number;
|
|
52
|
+
warmedPackages: number;
|
|
53
|
+
}
|
|
54
|
+
export interface CheckResult {
|
|
55
|
+
projectPath: string;
|
|
56
|
+
packagePaths: string[];
|
|
57
|
+
packageManager: "npm" | "pnpm" | "unknown";
|
|
58
|
+
target: TargetLevel;
|
|
59
|
+
timestamp: string;
|
|
60
|
+
summary: Summary;
|
|
61
|
+
updates: PackageUpdate[];
|
|
62
|
+
errors: string[];
|
|
63
|
+
warnings: string[];
|
|
64
|
+
}
|
|
65
|
+
export interface UpgradeResult extends CheckResult {
|
|
66
|
+
changed: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface PackageManifest {
|
|
69
|
+
name?: string;
|
|
70
|
+
version?: string;
|
|
71
|
+
dependencies?: Record<string, string>;
|
|
72
|
+
devDependencies?: Record<string, string>;
|
|
73
|
+
optionalDependencies?: Record<string, string>;
|
|
74
|
+
peerDependencies?: Record<string, string>;
|
|
75
|
+
[key: string]: unknown;
|
|
76
|
+
}
|
|
77
|
+
export interface CachedVersion {
|
|
78
|
+
packageName: string;
|
|
79
|
+
target: TargetLevel;
|
|
80
|
+
latestVersion: string;
|
|
81
|
+
fetchedAt: number;
|
|
82
|
+
ttlSeconds: number;
|
|
83
|
+
}
|
|
84
|
+
export interface VersionResolver {
|
|
85
|
+
resolveLatestVersion(packageName: string): Promise<string | null>;
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function asyncPool<T>(concurrency: number, tasks: Array<() => Promise<T>>): Promise<Array<T | Error>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export async function asyncPool(concurrency, tasks) {
|
|
2
|
+
const limit = Math.max(1, concurrency);
|
|
3
|
+
const results = new Array(tasks.length);
|
|
4
|
+
let nextIndex = 0;
|
|
5
|
+
const workers = Array.from({ length: Math.min(limit, tasks.length) }, async () => {
|
|
6
|
+
while (true) {
|
|
7
|
+
const current = nextIndex;
|
|
8
|
+
nextIndex += 1;
|
|
9
|
+
if (current >= tasks.length)
|
|
10
|
+
return;
|
|
11
|
+
try {
|
|
12
|
+
results[current] = await tasks[current]();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
results[current] = error instanceof Error ? error : new Error(String(error));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
await Promise.all(workers);
|
|
20
|
+
return results;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function matchesPattern(value: string, pattern?: string): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function matchesPattern(value, pattern) {
|
|
2
|
+
if (!pattern || pattern.trim() === "") {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
const escaped = pattern
|
|
6
|
+
.split("*")
|
|
7
|
+
.map((part) => part.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&"))
|
|
8
|
+
.join(".*");
|
|
9
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
10
|
+
return regex.test(value);
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TargetLevel } from "../types/index.js";
|
|
2
|
+
export interface ParsedVersion {
|
|
3
|
+
major: number;
|
|
4
|
+
minor: number;
|
|
5
|
+
patch: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function normalizeRangePrefix(range: string): string;
|
|
8
|
+
export declare function parseVersion(raw: string): ParsedVersion | null;
|
|
9
|
+
export declare function compareVersions(a: ParsedVersion, b: ParsedVersion): number;
|
|
10
|
+
export declare function classifyDiff(currentRange: string, nextVersion: string): TargetLevel;
|
|
11
|
+
export declare function pickTargetVersion(currentRange: string, latestVersion: string, target: TargetLevel): string | null;
|
|
12
|
+
export declare function applyRangeStyle(previousRange: string, version: string): string;
|
|
13
|
+
export declare function clampTarget(requested: TargetLevel, maxAllowed?: TargetLevel): TargetLevel;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function normalizeRangePrefix(range) {
|
|
2
|
+
const trimmed = range.trim();
|
|
3
|
+
if (!trimmed)
|
|
4
|
+
return "";
|
|
5
|
+
const prefixes = ["^", "~", ">=", "<=", ">", "<", "="];
|
|
6
|
+
const prefix = prefixes.find((item) => trimmed.startsWith(item));
|
|
7
|
+
return prefix ?? "";
|
|
8
|
+
}
|
|
9
|
+
export function parseVersion(raw) {
|
|
10
|
+
const clean = raw.trim().replace(/^[~^]/, "").split("-")[0];
|
|
11
|
+
const match = clean.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
12
|
+
if (!match)
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
major: Number(match[1]),
|
|
16
|
+
minor: Number(match[2]),
|
|
17
|
+
patch: Number(match[3]),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function compareVersions(a, b) {
|
|
21
|
+
if (a.major !== b.major)
|
|
22
|
+
return a.major - b.major;
|
|
23
|
+
if (a.minor !== b.minor)
|
|
24
|
+
return a.minor - b.minor;
|
|
25
|
+
return a.patch - b.patch;
|
|
26
|
+
}
|
|
27
|
+
export function classifyDiff(currentRange, nextVersion) {
|
|
28
|
+
const current = parseVersion(currentRange);
|
|
29
|
+
const next = parseVersion(nextVersion);
|
|
30
|
+
if (!current || !next)
|
|
31
|
+
return "latest";
|
|
32
|
+
if (next.major > current.major)
|
|
33
|
+
return "major";
|
|
34
|
+
if (next.minor > current.minor)
|
|
35
|
+
return "minor";
|
|
36
|
+
if (next.patch > current.patch)
|
|
37
|
+
return "patch";
|
|
38
|
+
return "latest";
|
|
39
|
+
}
|
|
40
|
+
export function pickTargetVersion(currentRange, latestVersion, target) {
|
|
41
|
+
const current = parseVersion(currentRange);
|
|
42
|
+
const latest = parseVersion(latestVersion);
|
|
43
|
+
if (!latest)
|
|
44
|
+
return null;
|
|
45
|
+
if (!current || target === "latest")
|
|
46
|
+
return latestVersion;
|
|
47
|
+
if (target === "patch") {
|
|
48
|
+
if (current.major === latest.major && current.minor === latest.minor && latest.patch > current.patch) {
|
|
49
|
+
return latestVersion;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (target === "minor") {
|
|
54
|
+
if (current.major === latest.major && compareVersions(latest, current) > 0) {
|
|
55
|
+
return latestVersion;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (target === "major") {
|
|
60
|
+
if (compareVersions(latest, current) > 0) {
|
|
61
|
+
return latestVersion;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return latestVersion;
|
|
66
|
+
}
|
|
67
|
+
export function applyRangeStyle(previousRange, version) {
|
|
68
|
+
const prefix = normalizeRangePrefix(previousRange);
|
|
69
|
+
return `${prefix}${version}`;
|
|
70
|
+
}
|
|
71
|
+
const TARGET_ORDER = ["patch", "minor", "major", "latest"];
|
|
72
|
+
export function clampTarget(requested, maxAllowed) {
|
|
73
|
+
if (!maxAllowed)
|
|
74
|
+
return requested;
|
|
75
|
+
const requestedIndex = TARGET_ORDER.indexOf(requested);
|
|
76
|
+
const allowedIndex = TARGET_ORDER.indexOf(maxAllowed);
|
|
77
|
+
if (requestedIndex === -1 || allowedIndex === -1)
|
|
78
|
+
return requested;
|
|
79
|
+
return TARGET_ORDER[Math.min(requestedIndex, allowedIndex)];
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function discoverPackageDirs(cwd: string, workspaceMode: boolean): Promise<string[]>;
|