@rainy-updates/cli 0.4.0 → 0.5.0-rc.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 +53 -1
- package/CODE_OF_CONDUCT.md +25 -0
- package/README.md +150 -82
- package/SECURITY.md +18 -0
- package/dist/bin/cli.js +73 -10
- package/dist/cache/cache.d.ts +1 -1
- package/dist/cache/cache.js +60 -15
- package/dist/config/loader.d.ts +3 -1
- package/dist/core/baseline.d.ts +23 -0
- package/dist/core/baseline.js +72 -0
- package/dist/core/check.js +24 -12
- package/dist/core/init-ci.d.ts +7 -1
- package/dist/core/init-ci.js +46 -7
- package/dist/core/options.d.ts +11 -2
- package/dist/core/options.js +163 -2
- package/dist/core/warm-cache.js +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/output/sarif.js +21 -1
- package/dist/registry/npm.d.ts +13 -2
- package/dist/registry/npm.js +29 -12
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/semver.d.ts +1 -0
- package/dist/utils/semver.js +24 -0
- package/package.json +4 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BaselineOptions, DependencyKind } from "../types/index.js";
|
|
2
|
+
interface BaselineEntry {
|
|
3
|
+
packagePath: string;
|
|
4
|
+
kind: DependencyKind;
|
|
5
|
+
name: string;
|
|
6
|
+
range: string;
|
|
7
|
+
}
|
|
8
|
+
export interface BaselineSaveResult {
|
|
9
|
+
filePath: string;
|
|
10
|
+
entries: number;
|
|
11
|
+
}
|
|
12
|
+
export interface BaselineDiffResult {
|
|
13
|
+
filePath: string;
|
|
14
|
+
added: BaselineEntry[];
|
|
15
|
+
removed: BaselineEntry[];
|
|
16
|
+
changed: Array<{
|
|
17
|
+
before: BaselineEntry;
|
|
18
|
+
after: BaselineEntry;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare function saveBaseline(options: BaselineOptions): Promise<BaselineSaveResult>;
|
|
22
|
+
export declare function diffBaseline(options: BaselineOptions): Promise<BaselineDiffResult>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { collectDependencies, readManifest } from "../parsers/package-json.js";
|
|
4
|
+
import { discoverPackageDirs } from "../workspace/discover.js";
|
|
5
|
+
export async function saveBaseline(options) {
|
|
6
|
+
const entries = await collectBaselineEntries(options.cwd, options.workspace, options.includeKinds);
|
|
7
|
+
const payload = {
|
|
8
|
+
version: 1,
|
|
9
|
+
createdAt: new Date().toISOString(),
|
|
10
|
+
entries,
|
|
11
|
+
};
|
|
12
|
+
await fs.mkdir(path.dirname(options.filePath), { recursive: true });
|
|
13
|
+
await fs.writeFile(options.filePath, JSON.stringify(payload, null, 2) + "\n", "utf8");
|
|
14
|
+
return {
|
|
15
|
+
filePath: options.filePath,
|
|
16
|
+
entries: entries.length,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function diffBaseline(options) {
|
|
20
|
+
const content = await fs.readFile(options.filePath, "utf8");
|
|
21
|
+
const baseline = JSON.parse(content);
|
|
22
|
+
const currentEntries = await collectBaselineEntries(options.cwd, options.workspace, options.includeKinds);
|
|
23
|
+
const baselineMap = new Map(baseline.entries.map((entry) => [toKey(entry), entry]));
|
|
24
|
+
const currentMap = new Map(currentEntries.map((entry) => [toKey(entry), entry]));
|
|
25
|
+
const added = [];
|
|
26
|
+
const removed = [];
|
|
27
|
+
const changed = [];
|
|
28
|
+
for (const [key, current] of currentMap) {
|
|
29
|
+
const base = baselineMap.get(key);
|
|
30
|
+
if (!base) {
|
|
31
|
+
added.push(current);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (base.range !== current.range) {
|
|
35
|
+
changed.push({ before: base, after: current });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const [key, base] of baselineMap) {
|
|
39
|
+
if (!currentMap.has(key)) {
|
|
40
|
+
removed.push(base);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
filePath: options.filePath,
|
|
45
|
+
added: sortEntries(added),
|
|
46
|
+
removed: sortEntries(removed),
|
|
47
|
+
changed: changed.sort((a, b) => toKey(a.after).localeCompare(toKey(b.after))),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function collectBaselineEntries(cwd, workspace, includeKinds) {
|
|
51
|
+
const packageDirs = await discoverPackageDirs(cwd, workspace);
|
|
52
|
+
const entries = [];
|
|
53
|
+
for (const packageDir of packageDirs) {
|
|
54
|
+
const manifest = await readManifest(packageDir);
|
|
55
|
+
const deps = collectDependencies(manifest, includeKinds);
|
|
56
|
+
for (const dep of deps) {
|
|
57
|
+
entries.push({
|
|
58
|
+
packagePath: path.relative(cwd, packageDir) || ".",
|
|
59
|
+
kind: dep.kind,
|
|
60
|
+
name: dep.name,
|
|
61
|
+
range: dep.range,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return sortEntries(entries);
|
|
66
|
+
}
|
|
67
|
+
function toKey(entry) {
|
|
68
|
+
return `${entry.packagePath}::${entry.kind}::${entry.name}`;
|
|
69
|
+
}
|
|
70
|
+
function sortEntries(entries) {
|
|
71
|
+
return [...entries].sort((a, b) => toKey(a).localeCompare(toKey(b)));
|
|
72
|
+
}
|
package/dist/core/check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { collectDependencies, readManifest } from "../parsers/package-json.js";
|
|
3
3
|
import { matchesPattern } from "../utils/pattern.js";
|
|
4
|
-
import { applyRangeStyle, classifyDiff, clampTarget,
|
|
4
|
+
import { applyRangeStyle, classifyDiff, clampTarget, pickTargetVersionFromAvailable } from "../utils/semver.js";
|
|
5
5
|
import { VersionCache } from "../cache/cache.js";
|
|
6
6
|
import { NpmRegistryClient } from "../registry/npm.js";
|
|
7
7
|
import { detectPackageManager } from "../pm/detect.js";
|
|
@@ -53,7 +53,10 @@ export async function check(options) {
|
|
|
53
53
|
for (const packageName of uniquePackageNames) {
|
|
54
54
|
const cached = await cache.getValid(packageName, options.target);
|
|
55
55
|
if (cached) {
|
|
56
|
-
resolvedVersions.set(packageName,
|
|
56
|
+
resolvedVersions.set(packageName, {
|
|
57
|
+
latestVersion: cached.latestVersion,
|
|
58
|
+
availableVersions: cached.availableVersions,
|
|
59
|
+
});
|
|
57
60
|
}
|
|
58
61
|
else {
|
|
59
62
|
unresolvedPackages.push(packageName);
|
|
@@ -64,7 +67,10 @@ export async function check(options) {
|
|
|
64
67
|
for (const packageName of unresolvedPackages) {
|
|
65
68
|
const stale = await cache.getAny(packageName, options.target);
|
|
66
69
|
if (stale) {
|
|
67
|
-
resolvedVersions.set(packageName,
|
|
70
|
+
resolvedVersions.set(packageName, {
|
|
71
|
+
latestVersion: stale.latestVersion,
|
|
72
|
+
availableVersions: stale.availableVersions,
|
|
73
|
+
});
|
|
68
74
|
warnings.push(`Using stale cache for ${packageName} because --offline is enabled.`);
|
|
69
75
|
}
|
|
70
76
|
else {
|
|
@@ -73,19 +79,25 @@ export async function check(options) {
|
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
81
|
else {
|
|
76
|
-
const fetched = await registryClient.
|
|
82
|
+
const fetched = await registryClient.resolveManyPackageMetadata(unresolvedPackages, {
|
|
77
83
|
concurrency: options.concurrency,
|
|
78
84
|
});
|
|
79
|
-
for (const [packageName,
|
|
80
|
-
resolvedVersions.set(packageName,
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
for (const [packageName, metadata] of fetched.metadata) {
|
|
86
|
+
resolvedVersions.set(packageName, {
|
|
87
|
+
latestVersion: metadata.latestVersion,
|
|
88
|
+
availableVersions: metadata.versions,
|
|
89
|
+
});
|
|
90
|
+
if (metadata.latestVersion) {
|
|
91
|
+
await cache.set(packageName, options.target, metadata.latestVersion, metadata.versions, options.cacheTtlSeconds);
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
for (const [packageName, error] of fetched.errors) {
|
|
86
95
|
const stale = await cache.getAny(packageName, options.target);
|
|
87
96
|
if (stale) {
|
|
88
|
-
resolvedVersions.set(packageName,
|
|
97
|
+
resolvedVersions.set(packageName, {
|
|
98
|
+
latestVersion: stale.latestVersion,
|
|
99
|
+
availableVersions: stale.availableVersions,
|
|
100
|
+
});
|
|
89
101
|
warnings.push(`Using stale cache for ${packageName} due to registry error: ${error}`);
|
|
90
102
|
}
|
|
91
103
|
else {
|
|
@@ -95,12 +107,12 @@ export async function check(options) {
|
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
for (const task of tasks) {
|
|
98
|
-
const
|
|
99
|
-
if (!latestVersion)
|
|
110
|
+
const metadata = resolvedVersions.get(task.dependency.name);
|
|
111
|
+
if (!metadata?.latestVersion)
|
|
100
112
|
continue;
|
|
101
113
|
const rule = policy.packageRules.get(task.dependency.name);
|
|
102
114
|
const effectiveTarget = clampTarget(options.target, rule?.maxTarget);
|
|
103
|
-
const picked =
|
|
115
|
+
const picked = pickTargetVersionFromAvailable(task.dependency.range, metadata.availableVersions, metadata.latestVersion, effectiveTarget);
|
|
104
116
|
if (!picked)
|
|
105
117
|
continue;
|
|
106
118
|
const nextRange = applyRangeStyle(task.dependency.range, picked);
|
package/dist/core/init-ci.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type InitCiMode = "minimal" | "strict" | "enterprise";
|
|
2
|
+
export type InitCiSchedule = "weekly" | "daily" | "off";
|
|
3
|
+
export interface InitCiOptions {
|
|
4
|
+
mode: InitCiMode;
|
|
5
|
+
schedule: InitCiSchedule;
|
|
6
|
+
}
|
|
7
|
+
export declare function initCiWorkflow(cwd: string, force: boolean, options: InitCiOptions): Promise<{
|
|
2
8
|
path: string;
|
|
3
9
|
created: boolean;
|
|
4
10
|
}>;
|
package/dist/core/init-ci.js
CHANGED
|
@@ -1,20 +1,59 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { access, writeFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
export async function initCiWorkflow(cwd, force) {
|
|
3
|
+
export async function initCiWorkflow(cwd, force, options) {
|
|
4
4
|
const workflowPath = path.join(cwd, ".github", "workflows", "rainy-updates.yml");
|
|
5
5
|
try {
|
|
6
6
|
if (!force) {
|
|
7
|
-
await
|
|
7
|
+
await access(workflowPath);
|
|
8
8
|
return { path: workflowPath, created: false };
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
catch {
|
|
12
12
|
// missing file, continue create
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const packageManager = await detectPackageManager(cwd);
|
|
15
|
+
const scheduleBlock = renderScheduleBlock(options.schedule);
|
|
16
|
+
const workflow = options.mode === "minimal"
|
|
17
|
+
? minimalWorkflowTemplate(scheduleBlock, packageManager)
|
|
18
|
+
: options.mode === "strict"
|
|
19
|
+
? strictWorkflowTemplate(scheduleBlock, packageManager)
|
|
20
|
+
: enterpriseWorkflowTemplate(scheduleBlock, packageManager);
|
|
21
|
+
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
22
|
+
await writeFile(workflowPath, workflow, "utf8");
|
|
16
23
|
return { path: workflowPath, created: true };
|
|
17
24
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
25
|
+
async function detectPackageManager(cwd) {
|
|
26
|
+
const pnpmLock = path.join(cwd, "pnpm-lock.yaml");
|
|
27
|
+
try {
|
|
28
|
+
await access(pnpmLock);
|
|
29
|
+
return "pnpm";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "npm";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function renderScheduleBlock(schedule) {
|
|
36
|
+
if (schedule === "off") {
|
|
37
|
+
return " workflow_dispatch:";
|
|
38
|
+
}
|
|
39
|
+
const cron = schedule === "daily" ? "0 8 * * *" : "0 8 * * 1";
|
|
40
|
+
return ` schedule:\n - cron: '${cron}'\n workflow_dispatch:`;
|
|
41
|
+
}
|
|
42
|
+
function installStep(packageManager) {
|
|
43
|
+
if (packageManager === "pnpm") {
|
|
44
|
+
return ` - name: Setup pnpm\n uses: pnpm/action-setup@v4\n with:\n version: 9\n\n - name: Install dependencies\n run: pnpm install --frozen-lockfile`;
|
|
45
|
+
}
|
|
46
|
+
return ` - name: Install dependencies\n run: npm ci`;
|
|
47
|
+
}
|
|
48
|
+
function minimalWorkflowTemplate(scheduleBlock, packageManager) {
|
|
49
|
+
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: '20'\n\n${installStep(packageManager)}\n\n - name: Run dependency check\n run: |\n npx @rainy-updates/cli check \\\n --workspace \\\n --ci \\\n --format table\n`;
|
|
50
|
+
}
|
|
51
|
+
function strictWorkflowTemplate(scheduleBlock, packageManager) {
|
|
52
|
+
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\npermissions:\n contents: read\n security-events: write\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: '20'\n\n${installStep(packageManager)}\n\n - name: Warm cache\n run: npx @rainy-updates/cli warm-cache --workspace --concurrency 32\n\n - name: Run strict dependency check\n run: |\n npx @rainy-updates/cli check \\\n --workspace \\\n --offline \\\n --ci \\\n --concurrency 32 \\\n --format github \\\n --json-file .artifacts/deps-report.json \\\n --pr-report-file .artifacts/deps-report.md \\\n --sarif-file .artifacts/deps-report.sarif \\\n --github-output $GITHUB_OUTPUT\n\n - name: Upload report artifacts\n uses: actions/upload-artifact@v4\n with:\n name: rainy-updates-report\n path: .artifacts/\n\n - name: Upload SARIF\n uses: github/codeql-action/upload-sarif@v3\n with:\n sarif_file: .artifacts/deps-report.sarif\n`;
|
|
53
|
+
}
|
|
54
|
+
function enterpriseWorkflowTemplate(scheduleBlock, packageManager) {
|
|
55
|
+
const detectedPmInstall = packageManager === "pnpm"
|
|
56
|
+
? "corepack enable && corepack prepare pnpm@9 --activate && pnpm install --frozen-lockfile"
|
|
57
|
+
: "npm ci";
|
|
58
|
+
return `name: Rainy Updates Enterprise\n\non:\n${scheduleBlock}\n\npermissions:\n contents: read\n security-events: write\n actions: read\n\nconcurrency:\n group: rainy-updates-\${{ github.ref }}\n cancel-in-progress: false\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n node: [20, 22]\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: \${{ matrix.node }}\n\n - name: Install dependencies\n run: ${detectedPmInstall}\n\n - name: Warm cache\n run: npx @rainy-updates/cli warm-cache --workspace --concurrency 32\n\n - name: Check updates with rollout controls\n run: |\n npx @rainy-updates/cli check \\\n --workspace \\\n --offline \\\n --concurrency 32 \\\n --format github \\\n --fail-on minor \\\n --max-updates 50 \\\n --json-file .artifacts/deps-report-node-\${{ matrix.node }}.json \\\n --pr-report-file .artifacts/deps-report-node-\${{ matrix.node }}.md \\\n --sarif-file .artifacts/deps-report-node-\${{ matrix.node }}.sarif \\\n --github-output $GITHUB_OUTPUT\n\n - name: Upload report artifacts\n uses: actions/upload-artifact@v4\n with:\n name: rainy-updates-report-node-\${{ matrix.node }}\n path: .artifacts/\n retention-days: 14\n\n - name: Upload SARIF\n uses: github/codeql-action/upload-sarif@v3\n with:\n sarif_file: .artifacts/deps-report-node-\${{ matrix.node }}.sarif\n`;
|
|
20
59
|
}
|
package/dist/core/options.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { CheckOptions, UpgradeOptions } from "../types/index.js";
|
|
1
|
+
import type { BaselineOptions, CheckOptions, UpgradeOptions } from "../types/index.js";
|
|
2
|
+
import type { InitCiMode, InitCiSchedule } from "./init-ci.js";
|
|
2
3
|
export type ParsedCliArgs = {
|
|
3
4
|
command: "check";
|
|
4
5
|
options: CheckOptions;
|
|
@@ -10,8 +11,16 @@ export type ParsedCliArgs = {
|
|
|
10
11
|
options: CheckOptions;
|
|
11
12
|
} | {
|
|
12
13
|
command: "init-ci";
|
|
13
|
-
options:
|
|
14
|
+
options: {
|
|
15
|
+
cwd: string;
|
|
14
16
|
force: boolean;
|
|
17
|
+
mode: InitCiMode;
|
|
18
|
+
schedule: InitCiSchedule;
|
|
19
|
+
};
|
|
20
|
+
} | {
|
|
21
|
+
command: "baseline";
|
|
22
|
+
options: BaselineOptions & {
|
|
23
|
+
action: "save" | "check";
|
|
15
24
|
};
|
|
16
25
|
};
|
|
17
26
|
export declare function parseCliArgs(argv: string[]): Promise<ParsedCliArgs>;
|
package/dist/core/options.js
CHANGED
|
@@ -7,7 +7,7 @@ const DEFAULT_INCLUDE_KINDS = [
|
|
|
7
7
|
"optionalDependencies",
|
|
8
8
|
"peerDependencies",
|
|
9
9
|
];
|
|
10
|
-
const KNOWN_COMMANDS = ["check", "upgrade", "warm-cache", "init-ci"];
|
|
10
|
+
const KNOWN_COMMANDS = ["check", "upgrade", "warm-cache", "init-ci", "baseline"];
|
|
11
11
|
export async function parseCliArgs(argv) {
|
|
12
12
|
const firstArg = argv[0];
|
|
13
13
|
const isKnownCommand = KNOWN_COMMANDS.includes(firstArg);
|
|
@@ -34,8 +34,14 @@ export async function parseCliArgs(argv) {
|
|
|
34
34
|
offline: false,
|
|
35
35
|
policyFile: undefined,
|
|
36
36
|
prReportFile: undefined,
|
|
37
|
+
failOn: "none",
|
|
38
|
+
maxUpdates: undefined,
|
|
37
39
|
};
|
|
38
40
|
let force = false;
|
|
41
|
+
let initCiMode = "enterprise";
|
|
42
|
+
let initCiSchedule = "weekly";
|
|
43
|
+
let baselineAction = "check";
|
|
44
|
+
let baselineFilePath = path.resolve(base.cwd, ".rainy-updates-baseline.json");
|
|
39
45
|
let resolvedConfig = await loadConfig(base.cwd);
|
|
40
46
|
applyConfig(base, resolvedConfig);
|
|
41
47
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -46,23 +52,36 @@ export async function parseCliArgs(argv) {
|
|
|
46
52
|
index += 1;
|
|
47
53
|
continue;
|
|
48
54
|
}
|
|
55
|
+
if (current === "--target") {
|
|
56
|
+
throw new Error("Missing value for --target");
|
|
57
|
+
}
|
|
49
58
|
if (current === "--filter" && next) {
|
|
50
59
|
base.filter = next;
|
|
51
60
|
index += 1;
|
|
52
61
|
continue;
|
|
53
62
|
}
|
|
63
|
+
if (current === "--filter") {
|
|
64
|
+
throw new Error("Missing value for --filter");
|
|
65
|
+
}
|
|
54
66
|
if (current === "--reject" && next) {
|
|
55
67
|
base.reject = next;
|
|
56
68
|
index += 1;
|
|
57
69
|
continue;
|
|
58
70
|
}
|
|
71
|
+
if (current === "--reject") {
|
|
72
|
+
throw new Error("Missing value for --reject");
|
|
73
|
+
}
|
|
59
74
|
if (current === "--cwd" && next) {
|
|
60
75
|
base.cwd = path.resolve(next);
|
|
61
76
|
resolvedConfig = await loadConfig(base.cwd);
|
|
62
77
|
applyConfig(base, resolvedConfig);
|
|
78
|
+
baselineFilePath = path.resolve(base.cwd, ".rainy-updates-baseline.json");
|
|
63
79
|
index += 1;
|
|
64
80
|
continue;
|
|
65
81
|
}
|
|
82
|
+
if (current === "--cwd") {
|
|
83
|
+
throw new Error("Missing value for --cwd");
|
|
84
|
+
}
|
|
66
85
|
if (current === "--cache-ttl" && next) {
|
|
67
86
|
const parsed = Number(next);
|
|
68
87
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
@@ -72,11 +91,17 @@ export async function parseCliArgs(argv) {
|
|
|
72
91
|
index += 1;
|
|
73
92
|
continue;
|
|
74
93
|
}
|
|
94
|
+
if (current === "--cache-ttl") {
|
|
95
|
+
throw new Error("Missing value for --cache-ttl");
|
|
96
|
+
}
|
|
75
97
|
if (current === "--format" && next) {
|
|
76
98
|
base.format = ensureFormat(next);
|
|
77
99
|
index += 1;
|
|
78
100
|
continue;
|
|
79
101
|
}
|
|
102
|
+
if (current === "--format") {
|
|
103
|
+
throw new Error("Missing value for --format");
|
|
104
|
+
}
|
|
80
105
|
if (current === "--ci") {
|
|
81
106
|
base.ci = true;
|
|
82
107
|
continue;
|
|
@@ -90,16 +115,25 @@ export async function parseCliArgs(argv) {
|
|
|
90
115
|
index += 1;
|
|
91
116
|
continue;
|
|
92
117
|
}
|
|
118
|
+
if (current === "--json-file") {
|
|
119
|
+
throw new Error("Missing value for --json-file");
|
|
120
|
+
}
|
|
93
121
|
if (current === "--github-output" && next) {
|
|
94
122
|
base.githubOutputFile = path.resolve(next);
|
|
95
123
|
index += 1;
|
|
96
124
|
continue;
|
|
97
125
|
}
|
|
126
|
+
if (current === "--github-output") {
|
|
127
|
+
throw new Error("Missing value for --github-output");
|
|
128
|
+
}
|
|
98
129
|
if (current === "--sarif-file" && next) {
|
|
99
130
|
base.sarifFile = path.resolve(next);
|
|
100
131
|
index += 1;
|
|
101
132
|
continue;
|
|
102
133
|
}
|
|
134
|
+
if (current === "--sarif-file") {
|
|
135
|
+
throw new Error("Missing value for --sarif-file");
|
|
136
|
+
}
|
|
103
137
|
if (current === "--concurrency" && next) {
|
|
104
138
|
const parsed = Number(next);
|
|
105
139
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
@@ -109,6 +143,9 @@ export async function parseCliArgs(argv) {
|
|
|
109
143
|
index += 1;
|
|
110
144
|
continue;
|
|
111
145
|
}
|
|
146
|
+
if (current === "--concurrency") {
|
|
147
|
+
throw new Error("Missing value for --concurrency");
|
|
148
|
+
}
|
|
112
149
|
if (current === "--offline") {
|
|
113
150
|
base.offline = true;
|
|
114
151
|
continue;
|
|
@@ -118,20 +155,99 @@ export async function parseCliArgs(argv) {
|
|
|
118
155
|
index += 1;
|
|
119
156
|
continue;
|
|
120
157
|
}
|
|
158
|
+
if (current === "--policy-file") {
|
|
159
|
+
throw new Error("Missing value for --policy-file");
|
|
160
|
+
}
|
|
121
161
|
if (current === "--pr-report-file" && next) {
|
|
122
162
|
base.prReportFile = path.resolve(next);
|
|
123
163
|
index += 1;
|
|
124
164
|
continue;
|
|
125
165
|
}
|
|
166
|
+
if (current === "--pr-report-file") {
|
|
167
|
+
throw new Error("Missing value for --pr-report-file");
|
|
168
|
+
}
|
|
126
169
|
if (current === "--force") {
|
|
127
170
|
force = true;
|
|
128
171
|
continue;
|
|
129
172
|
}
|
|
173
|
+
if (current === "--install" && command === "upgrade") {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (current === "--sync" && command === "upgrade") {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (current === "--pm" && next && command === "upgrade") {
|
|
180
|
+
parsePackageManager(args);
|
|
181
|
+
index += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (current === "--pm" && command === "upgrade") {
|
|
185
|
+
throw new Error("Missing value for --pm");
|
|
186
|
+
}
|
|
187
|
+
if (current === "--mode" && next) {
|
|
188
|
+
initCiMode = ensureInitCiMode(next);
|
|
189
|
+
index += 1;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (current === "--mode") {
|
|
193
|
+
throw new Error("Missing value for --mode");
|
|
194
|
+
}
|
|
195
|
+
if (current === "--schedule" && next) {
|
|
196
|
+
initCiSchedule = ensureInitCiSchedule(next);
|
|
197
|
+
index += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (current === "--schedule") {
|
|
201
|
+
throw new Error("Missing value for --schedule");
|
|
202
|
+
}
|
|
130
203
|
if (current === "--dep-kinds" && next) {
|
|
131
204
|
base.includeKinds = parseDependencyKinds(next);
|
|
132
205
|
index += 1;
|
|
133
206
|
continue;
|
|
134
207
|
}
|
|
208
|
+
if (current === "--dep-kinds") {
|
|
209
|
+
throw new Error("Missing value for --dep-kinds");
|
|
210
|
+
}
|
|
211
|
+
if (current === "--fail-on" && next) {
|
|
212
|
+
base.failOn = ensureFailOn(next);
|
|
213
|
+
index += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (current === "--fail-on") {
|
|
217
|
+
throw new Error("Missing value for --fail-on");
|
|
218
|
+
}
|
|
219
|
+
if (current === "--max-updates" && next) {
|
|
220
|
+
const parsed = Number(next);
|
|
221
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
222
|
+
throw new Error("--max-updates must be a non-negative integer");
|
|
223
|
+
}
|
|
224
|
+
base.maxUpdates = parsed;
|
|
225
|
+
index += 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (current === "--max-updates") {
|
|
229
|
+
throw new Error("Missing value for --max-updates");
|
|
230
|
+
}
|
|
231
|
+
if (current === "--save") {
|
|
232
|
+
baselineAction = "save";
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (current === "--check") {
|
|
236
|
+
baselineAction = "check";
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (current === "--file" && next) {
|
|
240
|
+
baselineFilePath = path.resolve(base.cwd, next);
|
|
241
|
+
index += 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (current === "--file") {
|
|
245
|
+
throw new Error("Missing value for --file");
|
|
246
|
+
}
|
|
247
|
+
if (current.startsWith("-")) {
|
|
248
|
+
throw new Error(`Unknown option: ${current}`);
|
|
249
|
+
}
|
|
250
|
+
throw new Error(`Unexpected argument: ${current}`);
|
|
135
251
|
}
|
|
136
252
|
if (command === "upgrade") {
|
|
137
253
|
const configPm = resolvedConfig.packageManager;
|
|
@@ -148,7 +264,28 @@ export async function parseCliArgs(argv) {
|
|
|
148
264
|
return { command, options: base };
|
|
149
265
|
}
|
|
150
266
|
if (command === "init-ci") {
|
|
151
|
-
return {
|
|
267
|
+
return {
|
|
268
|
+
command,
|
|
269
|
+
options: {
|
|
270
|
+
cwd: base.cwd,
|
|
271
|
+
force,
|
|
272
|
+
mode: initCiMode,
|
|
273
|
+
schedule: initCiSchedule,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (command === "baseline") {
|
|
278
|
+
return {
|
|
279
|
+
command,
|
|
280
|
+
options: {
|
|
281
|
+
action: baselineAction,
|
|
282
|
+
cwd: base.cwd,
|
|
283
|
+
workspace: base.workspace,
|
|
284
|
+
includeKinds: base.includeKinds,
|
|
285
|
+
filePath: baselineFilePath,
|
|
286
|
+
ci: base.ci,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
152
289
|
}
|
|
153
290
|
return {
|
|
154
291
|
command: "check",
|
|
@@ -192,6 +329,12 @@ function applyConfig(base, config) {
|
|
|
192
329
|
if (typeof config.prReportFile === "string") {
|
|
193
330
|
base.prReportFile = path.resolve(base.cwd, config.prReportFile);
|
|
194
331
|
}
|
|
332
|
+
if (typeof config.failOn === "string") {
|
|
333
|
+
base.failOn = ensureFailOn(config.failOn);
|
|
334
|
+
}
|
|
335
|
+
if (typeof config.maxUpdates === "number" && Number.isInteger(config.maxUpdates) && config.maxUpdates >= 0) {
|
|
336
|
+
base.maxUpdates = config.maxUpdates;
|
|
337
|
+
}
|
|
195
338
|
}
|
|
196
339
|
function parsePackageManager(args) {
|
|
197
340
|
const index = args.indexOf("--pm");
|
|
@@ -236,3 +379,21 @@ function parseDependencyKinds(value) {
|
|
|
236
379
|
}
|
|
237
380
|
return Array.from(new Set(mapped));
|
|
238
381
|
}
|
|
382
|
+
function ensureInitCiMode(value) {
|
|
383
|
+
if (value === "minimal" || value === "strict" || value === "enterprise") {
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
throw new Error("--mode must be minimal, strict or enterprise");
|
|
387
|
+
}
|
|
388
|
+
function ensureInitCiSchedule(value) {
|
|
389
|
+
if (value === "weekly" || value === "daily" || value === "off") {
|
|
390
|
+
return value;
|
|
391
|
+
}
|
|
392
|
+
throw new Error("--schedule must be weekly, daily or off");
|
|
393
|
+
}
|
|
394
|
+
function ensureFailOn(value) {
|
|
395
|
+
if (value === "none" || value === "patch" || value === "minor" || value === "major" || value === "any") {
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
throw new Error("--fail-on must be none, patch, minor, major or any");
|
|
399
|
+
}
|
package/dist/core/warm-cache.js
CHANGED
|
@@ -52,12 +52,12 @@ export async function warmCache(options) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
|
-
const fetched = await registryClient.
|
|
55
|
+
const fetched = await registryClient.resolveManyPackageMetadata(needsFetch, {
|
|
56
56
|
concurrency: options.concurrency,
|
|
57
57
|
});
|
|
58
|
-
for (const [pkg,
|
|
59
|
-
if (
|
|
60
|
-
await cache.set(pkg, options.target,
|
|
58
|
+
for (const [pkg, metadata] of fetched.metadata) {
|
|
59
|
+
if (metadata.latestVersion) {
|
|
60
|
+
await cache.set(pkg, options.target, metadata.latestVersion, metadata.versions, options.cacheTtlSeconds);
|
|
61
61
|
warmed += 1;
|
|
62
62
|
}
|
|
63
63
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ export { check } from "./core/check.js";
|
|
|
2
2
|
export { upgrade } from "./core/upgrade.js";
|
|
3
3
|
export { warmCache } from "./core/warm-cache.js";
|
|
4
4
|
export { initCiWorkflow } from "./core/init-ci.js";
|
|
5
|
+
export { saveBaseline, diffBaseline } from "./core/baseline.js";
|
|
5
6
|
export { createSarifReport } from "./output/sarif.js";
|
|
6
7
|
export { writeGitHubOutput, renderGitHubAnnotations } from "./output/github.js";
|
|
7
8
|
export { renderPrReport } from "./output/pr-report.js";
|
|
8
|
-
export type { CheckOptions, CheckResult, DependencyKind, OutputFormat, PackageUpdate, RunOptions, TargetLevel, UpgradeOptions, UpgradeResult, } from "./types/index.js";
|
|
9
|
+
export type { CheckOptions, CheckResult, DependencyKind, FailOnLevel, OutputFormat, PackageUpdate, RunOptions, TargetLevel, UpgradeOptions, UpgradeResult, } from "./types/index.js";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { check } from "./core/check.js";
|
|
|
2
2
|
export { upgrade } from "./core/upgrade.js";
|
|
3
3
|
export { warmCache } from "./core/warm-cache.js";
|
|
4
4
|
export { initCiWorkflow } from "./core/init-ci.js";
|
|
5
|
+
export { saveBaseline, diffBaseline } from "./core/baseline.js";
|
|
5
6
|
export { createSarifReport } from "./output/sarif.js";
|
|
6
7
|
export { writeGitHubOutput, renderGitHubAnnotations } from "./output/github.js";
|
|
7
8
|
export { renderPrReport } from "./output/pr-report.js";
|
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
14
|
constructor();
|
|
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
|
}
|