@rainy-updates/cli 0.6.0 → 0.6.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 -0
- package/dist/bin/cli.js +12 -127
- package/dist/bin/dispatch.js +6 -0
- package/dist/bin/help.js +47 -0
- package/dist/bin/main.d.ts +1 -0
- package/dist/bin/main.js +126 -0
- package/dist/commands/audit/parser.js +36 -0
- package/dist/commands/audit/runner.js +17 -18
- package/dist/commands/bisect/oracle.js +18 -15
- package/dist/commands/bisect/runner.js +4 -3
- package/dist/commands/dashboard/parser.js +41 -0
- package/dist/commands/doctor/parser.js +44 -0
- package/dist/commands/ga/parser.js +39 -0
- package/dist/commands/ga/runner.js +10 -7
- package/dist/commands/health/parser.js +36 -0
- package/dist/commands/health/runner.js +5 -1
- package/dist/commands/hook/parser.d.ts +2 -0
- package/dist/commands/hook/parser.js +40 -0
- package/dist/commands/hook/runner.d.ts +2 -0
- package/dist/commands/hook/runner.js +174 -0
- package/dist/commands/licenses/parser.js +39 -0
- package/dist/commands/licenses/runner.js +5 -1
- package/dist/commands/resolve/graph/builder.js +5 -1
- package/dist/commands/resolve/parser.js +39 -0
- package/dist/commands/resolve/runner.js +5 -0
- package/dist/commands/review/parser.js +44 -0
- package/dist/commands/snapshot/parser.js +39 -0
- package/dist/commands/snapshot/runner.js +4 -1
- package/dist/commands/unused/parser.js +39 -0
- package/dist/commands/unused/runner.js +4 -1
- package/dist/commands/unused/scanner.d.ts +2 -1
- package/dist/commands/unused/scanner.js +60 -44
- package/dist/core/check.js +5 -1
- package/dist/core/init-ci.js +28 -26
- package/dist/core/options.d.ts +4 -1
- package/dist/core/options.js +57 -0
- package/dist/core/verification.js +8 -6
- package/dist/core/warm-cache.js +5 -1
- package/dist/generated/version.d.ts +1 -0
- package/dist/generated/version.js +2 -0
- package/dist/git/scope.d.ts +19 -0
- package/dist/git/scope.js +167 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/output/sarif.js +2 -8
- package/dist/pm/detect.d.ts +37 -0
- package/dist/pm/detect.js +133 -2
- package/dist/pm/install.d.ts +2 -1
- package/dist/pm/install.js +7 -5
- package/dist/rup +0 -0
- package/dist/types/index.d.ts +58 -0
- package/dist/ui/tui.js +152 -64
- package/dist/workspace/discover.d.ts +7 -1
- package/dist/workspace/discover.js +12 -3
- package/package.json +10 -5
|
@@ -37,6 +37,11 @@ export function parseReviewArgs(args) {
|
|
|
37
37
|
cooldownDays: undefined,
|
|
38
38
|
prLimit: undefined,
|
|
39
39
|
onlyChanged: false,
|
|
40
|
+
affected: false,
|
|
41
|
+
staged: false,
|
|
42
|
+
baseRef: undefined,
|
|
43
|
+
headRef: undefined,
|
|
44
|
+
sinceRef: undefined,
|
|
40
45
|
ciProfile: "minimal",
|
|
41
46
|
lockfileMode: "preserve",
|
|
42
47
|
interactive: false,
|
|
@@ -68,6 +73,39 @@ export function parseReviewArgs(args) {
|
|
|
68
73
|
options.workspace = true;
|
|
69
74
|
continue;
|
|
70
75
|
}
|
|
76
|
+
if (current === "--only-changed") {
|
|
77
|
+
options.onlyChanged = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (current === "--affected") {
|
|
81
|
+
options.affected = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (current === "--staged") {
|
|
85
|
+
options.staged = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (current === "--base" && next) {
|
|
89
|
+
options.baseRef = next;
|
|
90
|
+
i += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (current === "--base")
|
|
94
|
+
throw new Error("Missing value for --base");
|
|
95
|
+
if (current === "--head" && next) {
|
|
96
|
+
options.headRef = next;
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (current === "--head")
|
|
101
|
+
throw new Error("Missing value for --head");
|
|
102
|
+
if (current === "--since" && next) {
|
|
103
|
+
options.sinceRef = next;
|
|
104
|
+
i += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (current === "--since")
|
|
108
|
+
throw new Error("Missing value for --since");
|
|
71
109
|
if (current === "--interactive") {
|
|
72
110
|
options.interactive = true;
|
|
73
111
|
continue;
|
|
@@ -224,6 +262,12 @@ Options:
|
|
|
224
262
|
--test-command <cmd> Override the command used for test verification
|
|
225
263
|
--show-changelog Fetch release notes summaries for review output
|
|
226
264
|
--workspace Scan all workspace packages
|
|
265
|
+
--only-changed Limit analysis to changed packages
|
|
266
|
+
--affected Include changed packages and their dependents
|
|
267
|
+
--staged Limit analysis to staged changes
|
|
268
|
+
--base <ref> Compare changes against a base git ref
|
|
269
|
+
--head <ref> Compare changes against a head git ref
|
|
270
|
+
--since <ref> Compare changes since a git ref
|
|
227
271
|
--policy-file <path> Load policy overrides
|
|
228
272
|
--json-file <path> Write JSON review report to file
|
|
229
273
|
--registry-timeout-ms <n>
|
|
@@ -3,6 +3,11 @@ export function parseSnapshotArgs(args) {
|
|
|
3
3
|
const options = {
|
|
4
4
|
cwd: process.cwd(),
|
|
5
5
|
workspace: false,
|
|
6
|
+
affected: false,
|
|
7
|
+
staged: false,
|
|
8
|
+
baseRef: undefined,
|
|
9
|
+
headRef: undefined,
|
|
10
|
+
sinceRef: undefined,
|
|
6
11
|
action: "list",
|
|
7
12
|
label: undefined,
|
|
8
13
|
snapshotId: undefined,
|
|
@@ -25,6 +30,35 @@ export function parseSnapshotArgs(args) {
|
|
|
25
30
|
options.workspace = true;
|
|
26
31
|
continue;
|
|
27
32
|
}
|
|
33
|
+
if (current === "--affected") {
|
|
34
|
+
options.affected = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (current === "--staged") {
|
|
38
|
+
options.staged = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (current === "--base" && next) {
|
|
42
|
+
options.baseRef = next;
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (current === "--base")
|
|
47
|
+
throw new Error("Missing value for --base");
|
|
48
|
+
if (current === "--head" && next) {
|
|
49
|
+
options.headRef = next;
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (current === "--head")
|
|
54
|
+
throw new Error("Missing value for --head");
|
|
55
|
+
if (current === "--since" && next) {
|
|
56
|
+
options.sinceRef = next;
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (current === "--since")
|
|
61
|
+
throw new Error("Missing value for --since");
|
|
28
62
|
if (current === "--label" && next) {
|
|
29
63
|
options.label = next;
|
|
30
64
|
i++;
|
|
@@ -75,6 +109,11 @@ Options:
|
|
|
75
109
|
--label <name> Human-readable label for the snapshot
|
|
76
110
|
--store <path> Custom snapshot store file (default: .rup-snapshots.json)
|
|
77
111
|
--workspace Include all workspace packages
|
|
112
|
+
--affected Include changed workspace packages and dependents
|
|
113
|
+
--staged Limit snapshot scope to staged changes
|
|
114
|
+
--base <ref> Compare changes against a base git ref
|
|
115
|
+
--head <ref> Compare changes against a head git ref
|
|
116
|
+
--since <ref> Compare changes since a git ref
|
|
78
117
|
--cwd <path> Working directory (default: cwd)
|
|
79
118
|
--help Show this help
|
|
80
119
|
`.trimStart();
|
|
@@ -16,7 +16,10 @@ export async function runSnapshot(options) {
|
|
|
16
16
|
errors: [],
|
|
17
17
|
warnings: [],
|
|
18
18
|
};
|
|
19
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
19
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
20
|
+
git: options,
|
|
21
|
+
includeDependents: options.affected === true,
|
|
22
|
+
});
|
|
20
23
|
const store = new SnapshotStore(options.cwd, options.storeFile);
|
|
21
24
|
switch (options.action) {
|
|
22
25
|
// ─ save ──────────────────────────────────────────────────────────────────
|
|
@@ -3,6 +3,11 @@ export function parseUnusedArgs(args) {
|
|
|
3
3
|
const options = {
|
|
4
4
|
cwd: process.cwd(),
|
|
5
5
|
workspace: false,
|
|
6
|
+
affected: false,
|
|
7
|
+
staged: false,
|
|
8
|
+
baseRef: undefined,
|
|
9
|
+
headRef: undefined,
|
|
10
|
+
sinceRef: undefined,
|
|
6
11
|
srcDirs: DEFAULT_SRC_DIRS,
|
|
7
12
|
includeDevDependencies: true,
|
|
8
13
|
fix: false,
|
|
@@ -24,6 +29,35 @@ export function parseUnusedArgs(args) {
|
|
|
24
29
|
options.workspace = true;
|
|
25
30
|
continue;
|
|
26
31
|
}
|
|
32
|
+
if (current === "--affected") {
|
|
33
|
+
options.affected = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (current === "--staged") {
|
|
37
|
+
options.staged = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (current === "--base" && next) {
|
|
41
|
+
options.baseRef = next;
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (current === "--base")
|
|
46
|
+
throw new Error("Missing value for --base");
|
|
47
|
+
if (current === "--head" && next) {
|
|
48
|
+
options.headRef = next;
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (current === "--head")
|
|
53
|
+
throw new Error("Missing value for --head");
|
|
54
|
+
if (current === "--since" && next) {
|
|
55
|
+
options.sinceRef = next;
|
|
56
|
+
i++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (current === "--since")
|
|
60
|
+
throw new Error("Missing value for --since");
|
|
27
61
|
if (current === "--src" && next) {
|
|
28
62
|
options.srcDirs = next
|
|
29
63
|
.split(",")
|
|
@@ -81,6 +115,11 @@ Usage:
|
|
|
81
115
|
Options:
|
|
82
116
|
--src <dirs> Comma-separated source directories to scan (default: src)
|
|
83
117
|
--workspace Scan all workspace packages
|
|
118
|
+
--affected Scan changed workspace packages and their dependents
|
|
119
|
+
--staged Limit scanning to staged changes
|
|
120
|
+
--base <ref> Compare changes against a base git ref
|
|
121
|
+
--head <ref> Compare changes against a head git ref
|
|
122
|
+
--since <ref> Compare changes since a git ref
|
|
84
123
|
--no-dev Exclude devDependencies from unused detection
|
|
85
124
|
--fix Remove unused dependencies from package.json
|
|
86
125
|
--dry-run Preview changes without writing
|
|
@@ -24,7 +24,10 @@ export async function runUnused(options) {
|
|
|
24
24
|
errors: [],
|
|
25
25
|
warnings: [],
|
|
26
26
|
};
|
|
27
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
27
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
28
|
+
git: options,
|
|
29
|
+
includeDependents: false,
|
|
30
|
+
});
|
|
28
31
|
for (const packageDir of packageDirs) {
|
|
29
32
|
// ─ Read manifest ─────────────────────────────────────────────────────────
|
|
30
33
|
let manifest;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extracts all imported package names from a single source file.
|
|
2
|
+
* Extracts all imported package names from a single source file using AST.
|
|
3
3
|
*
|
|
4
4
|
* Handles:
|
|
5
5
|
* - ESM static: import ... from "pkg"
|
|
6
6
|
* - ESM dynamic: import("pkg")
|
|
7
7
|
* - CJS: require("pkg")
|
|
8
|
+
* - ESM re-export: export ... from "pkg"
|
|
8
9
|
*
|
|
9
10
|
* Strips subpath imports (e.g. "lodash/merge" → "lodash"),
|
|
10
11
|
* skips relative imports and node: builtins.
|
|
@@ -1,37 +1,63 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { parseSync } from "oxc-parser";
|
|
3
3
|
/**
|
|
4
|
-
* Extracts all imported package names from a single source file.
|
|
4
|
+
* Extracts all imported package names from a single source file using AST.
|
|
5
5
|
*
|
|
6
6
|
* Handles:
|
|
7
7
|
* - ESM static: import ... from "pkg"
|
|
8
8
|
* - ESM dynamic: import("pkg")
|
|
9
9
|
* - CJS: require("pkg")
|
|
10
|
+
* - ESM re-export: export ... from "pkg"
|
|
10
11
|
*
|
|
11
12
|
* Strips subpath imports (e.g. "lodash/merge" → "lodash"),
|
|
12
13
|
* skips relative imports and node: builtins.
|
|
13
14
|
*/
|
|
14
15
|
export function extractImportsFromSource(source) {
|
|
15
16
|
const names = new Set();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
try {
|
|
18
|
+
const parseResult = parseSync("unknown.ts", source, {
|
|
19
|
+
sourceType: "module",
|
|
20
|
+
});
|
|
21
|
+
const walk = (node) => {
|
|
22
|
+
if (!node)
|
|
23
|
+
return;
|
|
24
|
+
if (node.type === "ImportDeclaration" && node.source?.value) {
|
|
25
|
+
addPackageName(names, node.source.value);
|
|
26
|
+
}
|
|
27
|
+
else if (node.type === "ExportNamedDeclaration" && node.source?.value) {
|
|
28
|
+
addPackageName(names, node.source.value);
|
|
29
|
+
}
|
|
30
|
+
else if (node.type === "ExportAllDeclaration" && node.source?.value) {
|
|
31
|
+
addPackageName(names, node.source.value);
|
|
32
|
+
}
|
|
33
|
+
else if (node.type === "ImportExpression" && node.source?.value) {
|
|
34
|
+
addPackageName(names, node.source.value);
|
|
35
|
+
}
|
|
36
|
+
else if (node.type === "CallExpression") {
|
|
37
|
+
if (node.callee?.type === "Identifier" &&
|
|
38
|
+
node.callee.name === "require" &&
|
|
39
|
+
node.arguments?.[0]?.type === "StringLiteral") {
|
|
40
|
+
addPackageName(names, node.arguments[0].value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Traverse children
|
|
44
|
+
for (const key in node) {
|
|
45
|
+
if (node[key] && typeof node[key] === "object") {
|
|
46
|
+
if (Array.isArray(node[key])) {
|
|
47
|
+
for (const child of node[key]) {
|
|
48
|
+
walk(child);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
walk(node[key]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
walk(parseResult.program);
|
|
30
58
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
for (const match of source.matchAll(reExport)) {
|
|
34
|
-
addPackageName(names, match[1]);
|
|
59
|
+
catch (err) {
|
|
60
|
+
// Fallback or ignore parse errors
|
|
35
61
|
}
|
|
36
62
|
return names;
|
|
37
63
|
}
|
|
@@ -87,40 +113,30 @@ const IGNORED_DIRS = new Set([
|
|
|
87
113
|
*/
|
|
88
114
|
export async function scanDirectory(dir) {
|
|
89
115
|
const allImports = new Set();
|
|
90
|
-
|
|
91
|
-
return allImports;
|
|
92
|
-
}
|
|
93
|
-
async function walkDirectory(dir, collector) {
|
|
94
|
-
let entries;
|
|
95
|
-
try {
|
|
96
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
116
|
+
const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}");
|
|
101
117
|
const tasks = [];
|
|
102
|
-
for (const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
for await (const file of glob.scan(dir)) {
|
|
119
|
+
// Bun.Glob returns relative paths
|
|
120
|
+
const fullPath = path.join(dir, file);
|
|
121
|
+
// Quick check to ignore certain directories in the path
|
|
122
|
+
if (fullPath.includes("/node_modules/") ||
|
|
123
|
+
fullPath.includes("/.git/") ||
|
|
124
|
+
fullPath.includes("/dist/") ||
|
|
125
|
+
fullPath.includes("/build/") ||
|
|
126
|
+
fullPath.includes("/out/") ||
|
|
127
|
+
fullPath.includes("/.next/") ||
|
|
128
|
+
fullPath.includes("/.nuxt/")) {
|
|
109
129
|
continue;
|
|
110
130
|
}
|
|
111
|
-
if (!entry.isFile())
|
|
112
|
-
continue;
|
|
113
|
-
const ext = path.extname(entryName).toLowerCase();
|
|
114
|
-
if (!SOURCE_EXTENSIONS.has(ext))
|
|
115
|
-
continue;
|
|
116
131
|
tasks.push(Bun.file(fullPath)
|
|
117
132
|
.text()
|
|
118
133
|
.then((source) => {
|
|
119
134
|
for (const name of extractImportsFromSource(source)) {
|
|
120
|
-
|
|
135
|
+
allImports.add(name);
|
|
121
136
|
}
|
|
122
137
|
})
|
|
123
138
|
.catch(() => undefined));
|
|
124
139
|
}
|
|
125
140
|
await Promise.all(tasks);
|
|
141
|
+
return allImports;
|
|
126
142
|
}
|
package/dist/core/check.js
CHANGED
|
@@ -18,7 +18,11 @@ export async function check(options) {
|
|
|
18
18
|
let registryMs = 0;
|
|
19
19
|
const discoveryStartedAt = Date.now();
|
|
20
20
|
const packageManager = await detectPackageManager(options.cwd);
|
|
21
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
21
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
22
|
+
git: options,
|
|
23
|
+
includeKinds: options.includeKinds,
|
|
24
|
+
includeDependents: options.affected === true,
|
|
25
|
+
});
|
|
22
26
|
discoveryMs += Date.now() - discoveryStartedAt;
|
|
23
27
|
const cache = await VersionCache.create();
|
|
24
28
|
const registryClient = new NpmRegistryClient(options.cwd, {
|
package/dist/core/init-ci.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { buildInstallInvocation, buildTestCommand, createPackageManagerProfile, detectPackageManagerDetails, } from "../pm/detect.js";
|
|
4
4
|
export async function initCiWorkflow(cwd, force, options) {
|
|
5
5
|
const workflowPath = path.join(cwd, ".github", "workflows", "rainy-updates.yml");
|
|
6
6
|
try {
|
|
@@ -13,8 +13,8 @@ export async function initCiWorkflow(cwd, force, options) {
|
|
|
13
13
|
catch {
|
|
14
14
|
// missing file, continue create
|
|
15
15
|
}
|
|
16
|
-
const detected = await
|
|
17
|
-
const packageManager =
|
|
16
|
+
const detected = await detectPackageManagerDetails(cwd);
|
|
17
|
+
const packageManager = createPackageManagerProfile("auto", detected);
|
|
18
18
|
const scheduleBlock = renderScheduleBlock(options.schedule);
|
|
19
19
|
const workflow = options.mode === "minimal"
|
|
20
20
|
? minimalWorkflowTemplate(scheduleBlock, packageManager)
|
|
@@ -32,32 +32,34 @@ function renderScheduleBlock(schedule) {
|
|
|
32
32
|
const cron = schedule === "daily" ? "0 8 * * *" : "0 8 * * 1";
|
|
33
33
|
return ` schedule:\n - cron: '${cron}'\n workflow_dispatch:`;
|
|
34
34
|
}
|
|
35
|
-
function installStep(
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
function installStep(profile) {
|
|
36
|
+
const install = buildInstallInvocation(profile, { frozen: true, ci: true });
|
|
37
|
+
return ` - name: Install dependencies\n run: ${install.display}`;
|
|
38
|
+
}
|
|
39
|
+
function runtimeSetupSteps(profile) {
|
|
40
|
+
const lines = [
|
|
41
|
+
` - name: Checkout\n uses: actions/checkout@v4`,
|
|
42
|
+
];
|
|
43
|
+
if (profile.manager !== "bun") {
|
|
44
|
+
lines.push(` - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: 22`);
|
|
45
|
+
}
|
|
46
|
+
lines.push(` - name: Setup Bun\n uses: oven-sh/setup-bun@v1`);
|
|
47
|
+
if (profile.manager === "pnpm" || profile.manager === "yarn") {
|
|
48
|
+
lines.push(` - name: Enable Corepack\n run: corepack enable`);
|
|
38
49
|
}
|
|
39
|
-
if (
|
|
40
|
-
|
|
50
|
+
if (profile.manager === "pnpm") {
|
|
51
|
+
lines.push(` - name: Prepare pnpm\n run: corepack prepare pnpm@9 --activate`);
|
|
41
52
|
}
|
|
42
|
-
return
|
|
53
|
+
return lines.join("\n\n");
|
|
43
54
|
}
|
|
44
|
-
function minimalWorkflowTemplate(scheduleBlock,
|
|
45
|
-
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n
|
|
55
|
+
function minimalWorkflowTemplate(scheduleBlock, profile) {
|
|
56
|
+
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n${runtimeSetupSteps(profile)}\n\n${installStep(profile)}\n\n - name: Run dependency check\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode minimal \\\n --gate check \\\n --ci \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --format table\n`;
|
|
46
57
|
}
|
|
47
|
-
function strictWorkflowTemplate(scheduleBlock,
|
|
48
|
-
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
|
|
58
|
+
function strictWorkflowTemplate(scheduleBlock, profile) {
|
|
59
|
+
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${runtimeSetupSteps(profile)}\n\n${installStep(profile)}\n\n - name: Warm cache\n run: bunx --bun @rainy-updates/cli warm-cache --workspace --concurrency 32 --registry-timeout-ms 12000 --registry-retries 4\n\n - name: Generate reviewed decision plan\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode strict \\\n --gate review \\\n --plan-file .artifacts/decision-plan.json \\\n --ci \\\n --concurrency 32 \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\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`;
|
|
49
60
|
}
|
|
50
|
-
function enterpriseWorkflowTemplate(scheduleBlock,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
detectedPmInstall =
|
|
55
|
-
"corepack enable && corepack prepare pnpm@9 --activate && pnpm install --frozen-lockfile";
|
|
56
|
-
testCmd = "pnpm test";
|
|
57
|
-
}
|
|
58
|
-
else if (packageManager === "bun") {
|
|
59
|
-
detectedPmInstall = "bun install";
|
|
60
|
-
testCmd = "bun test";
|
|
61
|
-
}
|
|
62
|
-
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: Setup Bun\n uses: oven-sh/setup-bun@v1\n\n - name: Install dependencies\n run: ${detectedPmInstall}\n\n - name: Warm cache\n run: bunx --bun @rainy-updates/cli warm-cache --workspace --concurrency 32 --registry-timeout-ms 12000 --registry-retries 4\n\n - name: Generate reviewed decision plan\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode enterprise \\\n --gate review \\\n --plan-file .artifacts/decision-plan.json \\\n --concurrency 32 \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --lockfile-mode preserve \\\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: Replay approved plan with verification\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode enterprise \\\n --gate upgrade \\\n --from-plan .artifacts/decision-plan.json \\\n --verify test \\\n --test-command "${testCmd}" \\\n --verification-report-file .artifacts/verification-node-\${{ matrix.node }}.json\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`;
|
|
61
|
+
function enterpriseWorkflowTemplate(scheduleBlock, profile) {
|
|
62
|
+
const install = buildInstallInvocation(profile, { frozen: true, ci: true });
|
|
63
|
+
const testCmd = buildTestCommand(profile);
|
|
64
|
+
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: Setup Bun\n uses: oven-sh/setup-bun@v1\n\n${profile.manager === "pnpm" || profile.manager === "yarn" ? ' - name: Enable Corepack\n run: corepack enable\n\n' : ""}${profile.manager === "pnpm" ? ' - name: Prepare pnpm\n run: corepack prepare pnpm@9 --activate\n\n' : ""} - name: Install dependencies\n run: ${install.display}\n\n - name: Warm cache\n run: bunx --bun @rainy-updates/cli warm-cache --workspace --concurrency 32 --registry-timeout-ms 12000 --registry-retries 4\n\n - name: Generate reviewed decision plan\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode enterprise \\\n --gate review \\\n --plan-file .artifacts/decision-plan.json \\\n --concurrency 32 \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --lockfile-mode preserve \\\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: Replay approved plan with verification\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode enterprise \\\n --gate upgrade \\\n --from-plan .artifacts/decision-plan.json \\\n --verify test \\\n --test-command "${testCmd}" \\\n --verification-report-file .artifacts/verification-node-\${{ matrix.node }}.json\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`;
|
|
63
65
|
}
|
package/dist/core/options.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions, ReviewOptions, DoctorOptions, RiskLevel, DashboardOptions, GaOptions } from "../types/index.js";
|
|
1
|
+
import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions, ReviewOptions, DoctorOptions, RiskLevel, DashboardOptions, GaOptions, HookOptions } from "../types/index.js";
|
|
2
2
|
import type { InitCiMode, InitCiSchedule } from "./init-ci.js";
|
|
3
3
|
export type ParsedCliArgs = {
|
|
4
4
|
command: "check";
|
|
@@ -58,6 +58,9 @@ export type ParsedCliArgs = {
|
|
|
58
58
|
} | {
|
|
59
59
|
command: "ga";
|
|
60
60
|
options: GaOptions;
|
|
61
|
+
} | {
|
|
62
|
+
command: "hook";
|
|
63
|
+
options: HookOptions;
|
|
61
64
|
};
|
|
62
65
|
export declare function parseCliArgs(argv: string[]): Promise<ParsedCliArgs>;
|
|
63
66
|
export declare function ensureRiskLevel(value: string): RiskLevel;
|
package/dist/core/options.js
CHANGED
|
@@ -25,6 +25,7 @@ const KNOWN_COMMANDS = [
|
|
|
25
25
|
"doctor",
|
|
26
26
|
"dashboard",
|
|
27
27
|
"ga",
|
|
28
|
+
"hook",
|
|
28
29
|
];
|
|
29
30
|
export async function parseCliArgs(argv) {
|
|
30
31
|
const firstArg = argv[0];
|
|
@@ -82,6 +83,10 @@ export async function parseCliArgs(argv) {
|
|
|
82
83
|
const { parseGaArgs } = await import("../commands/ga/parser.js");
|
|
83
84
|
return { command, options: parseGaArgs(args) };
|
|
84
85
|
}
|
|
86
|
+
if (command === "hook") {
|
|
87
|
+
const { parseHookArgs } = await import("../commands/hook/parser.js");
|
|
88
|
+
return { command, options: parseHookArgs(args) };
|
|
89
|
+
}
|
|
85
90
|
const base = {
|
|
86
91
|
cwd: getRuntimeCwd(),
|
|
87
92
|
target: "latest",
|
|
@@ -117,6 +122,11 @@ export async function parseCliArgs(argv) {
|
|
|
117
122
|
cooldownDays: undefined,
|
|
118
123
|
prLimit: undefined,
|
|
119
124
|
onlyChanged: false,
|
|
125
|
+
affected: false,
|
|
126
|
+
staged: false,
|
|
127
|
+
baseRef: undefined,
|
|
128
|
+
headRef: undefined,
|
|
129
|
+
sinceRef: undefined,
|
|
120
130
|
ciProfile: "minimal",
|
|
121
131
|
lockfileMode: "preserve",
|
|
122
132
|
interactive: false,
|
|
@@ -457,6 +467,38 @@ export async function parseCliArgs(argv) {
|
|
|
457
467
|
base.onlyChanged = true;
|
|
458
468
|
continue;
|
|
459
469
|
}
|
|
470
|
+
if (current === "--affected") {
|
|
471
|
+
base.affected = true;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (current === "--staged") {
|
|
475
|
+
base.staged = true;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (current === "--base" && next) {
|
|
479
|
+
base.baseRef = next;
|
|
480
|
+
index += 1;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (current === "--base") {
|
|
484
|
+
throw new Error("Missing value for --base");
|
|
485
|
+
}
|
|
486
|
+
if (current === "--head" && next) {
|
|
487
|
+
base.headRef = next;
|
|
488
|
+
index += 1;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (current === "--head") {
|
|
492
|
+
throw new Error("Missing value for --head");
|
|
493
|
+
}
|
|
494
|
+
if (current === "--since" && next) {
|
|
495
|
+
base.sinceRef = next;
|
|
496
|
+
index += 1;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (current === "--since") {
|
|
500
|
+
throw new Error("Missing value for --since");
|
|
501
|
+
}
|
|
460
502
|
if (current === "--interactive") {
|
|
461
503
|
base.interactive = true;
|
|
462
504
|
continue;
|
|
@@ -726,6 +768,21 @@ function applyConfig(base, config) {
|
|
|
726
768
|
if (typeof config.onlyChanged === "boolean") {
|
|
727
769
|
base.onlyChanged = config.onlyChanged;
|
|
728
770
|
}
|
|
771
|
+
if (typeof config.affected === "boolean") {
|
|
772
|
+
base.affected = config.affected;
|
|
773
|
+
}
|
|
774
|
+
if (typeof config.staged === "boolean") {
|
|
775
|
+
base.staged = config.staged;
|
|
776
|
+
}
|
|
777
|
+
if (typeof config.baseRef === "string" && config.baseRef.length > 0) {
|
|
778
|
+
base.baseRef = config.baseRef;
|
|
779
|
+
}
|
|
780
|
+
if (typeof config.headRef === "string" && config.headRef.length > 0) {
|
|
781
|
+
base.headRef = config.headRef;
|
|
782
|
+
}
|
|
783
|
+
if (typeof config.sinceRef === "string" && config.sinceRef.length > 0) {
|
|
784
|
+
base.sinceRef = config.sinceRef;
|
|
785
|
+
}
|
|
729
786
|
if (typeof config.ciProfile === "string") {
|
|
730
787
|
base.ciProfile = ensureCiProfile(config.ciProfile);
|
|
731
788
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildInstallInvocation, buildTestCommand, createPackageManagerProfile, detectPackageManagerDetails, } from "../pm/detect.js";
|
|
2
2
|
import { installDependencies } from "../pm/install.js";
|
|
3
3
|
import { stableStringify } from "../utils/stable-json.js";
|
|
4
4
|
import { writeFileAtomic } from "../utils/io.js";
|
|
@@ -15,15 +15,17 @@ export async function runVerification(options) {
|
|
|
15
15
|
return result;
|
|
16
16
|
}
|
|
17
17
|
const checks = [];
|
|
18
|
-
const detected = await
|
|
18
|
+
const detected = await detectPackageManagerDetails(options.cwd);
|
|
19
|
+
const profile = createPackageManagerProfile(options.packageManager, detected, "bun");
|
|
19
20
|
if (includesInstall(mode)) {
|
|
20
|
-
|
|
21
|
+
const installInvocation = buildInstallInvocation(profile);
|
|
22
|
+
checks.push(await runCheck("install", installInvocation.display, async () => {
|
|
21
23
|
await installDependencies(options.cwd, options.packageManager, detected);
|
|
22
24
|
}));
|
|
23
25
|
}
|
|
24
26
|
if (includesTest(mode)) {
|
|
25
27
|
const command = options.testCommand ??
|
|
26
|
-
defaultTestCommand(
|
|
28
|
+
defaultTestCommand(profile);
|
|
27
29
|
checks.push(await runShellCheck(options.cwd, command));
|
|
28
30
|
}
|
|
29
31
|
const result = {
|
|
@@ -40,8 +42,8 @@ function includesInstall(mode) {
|
|
|
40
42
|
function includesTest(mode) {
|
|
41
43
|
return mode === "test" || mode === "install,test";
|
|
42
44
|
}
|
|
43
|
-
function defaultTestCommand(
|
|
44
|
-
return
|
|
45
|
+
function defaultTestCommand(profile) {
|
|
46
|
+
return buildTestCommand(profile);
|
|
45
47
|
}
|
|
46
48
|
async function runShellCheck(cwd, command) {
|
|
47
49
|
const startedAt = Date.now();
|
package/dist/core/warm-cache.js
CHANGED
|
@@ -14,7 +14,11 @@ export async function warmCache(options) {
|
|
|
14
14
|
let registryMs = 0;
|
|
15
15
|
const discoveryStartedAt = Date.now();
|
|
16
16
|
const packageManager = await detectPackageManager(options.cwd);
|
|
17
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
17
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
18
|
+
git: options,
|
|
19
|
+
includeKinds: options.includeKinds,
|
|
20
|
+
includeDependents: options.affected === true,
|
|
21
|
+
});
|
|
18
22
|
discoveryMs += Date.now() - discoveryStartedAt;
|
|
19
23
|
const cache = await VersionCache.create();
|
|
20
24
|
const registryClient = new NpmRegistryClient(options.cwd, {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CLI_VERSION = "0.6.1";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DependencyKind } from "../types/index.js";
|
|
2
|
+
export interface GitScopeOptions {
|
|
3
|
+
onlyChanged?: boolean;
|
|
4
|
+
affected?: boolean;
|
|
5
|
+
staged?: boolean;
|
|
6
|
+
baseRef?: string;
|
|
7
|
+
headRef?: string;
|
|
8
|
+
sinceRef?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ScopedPackageDirsResult {
|
|
11
|
+
packageDirs: string[];
|
|
12
|
+
warnings: string[];
|
|
13
|
+
changedFiles: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function hasGitScope(options: GitScopeOptions): boolean;
|
|
16
|
+
export declare function scopePackageDirsByGit(cwd: string, packageDirs: string[], options: GitScopeOptions, config?: {
|
|
17
|
+
includeKinds?: DependencyKind[];
|
|
18
|
+
includeDependents?: boolean;
|
|
19
|
+
}): Promise<ScopedPackageDirsResult>;
|