@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -0
  4. package/dist/bin/cli.d.ts +2 -0
  5. package/dist/bin/cli.js +146 -0
  6. package/dist/cache/cache.d.ts +9 -0
  7. package/dist/cache/cache.js +125 -0
  8. package/dist/config/loader.d.ts +22 -0
  9. package/dist/config/loader.js +35 -0
  10. package/dist/config/policy.d.ts +16 -0
  11. package/dist/config/policy.js +32 -0
  12. package/dist/core/check.d.ts +2 -0
  13. package/dist/core/check.js +141 -0
  14. package/dist/core/init-ci.d.ts +4 -0
  15. package/dist/core/init-ci.js +20 -0
  16. package/dist/core/options.d.ts +17 -0
  17. package/dist/core/options.js +238 -0
  18. package/dist/core/upgrade.d.ts +2 -0
  19. package/dist/core/upgrade.js +88 -0
  20. package/dist/core/warm-cache.d.ts +2 -0
  21. package/dist/core/warm-cache.js +89 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.js +7 -0
  24. package/dist/output/format.d.ts +2 -0
  25. package/dist/output/format.js +56 -0
  26. package/dist/output/github.d.ts +3 -0
  27. package/dist/output/github.js +30 -0
  28. package/dist/output/pr-report.d.ts +2 -0
  29. package/dist/output/pr-report.js +38 -0
  30. package/dist/output/sarif.d.ts +2 -0
  31. package/dist/output/sarif.js +60 -0
  32. package/dist/parsers/package-json.d.ts +5 -0
  33. package/dist/parsers/package-json.js +35 -0
  34. package/dist/pm/detect.d.ts +1 -0
  35. package/dist/pm/detect.js +20 -0
  36. package/dist/pm/install.d.ts +1 -0
  37. package/dist/pm/install.js +21 -0
  38. package/dist/registry/npm.d.ts +14 -0
  39. package/dist/registry/npm.js +128 -0
  40. package/dist/types/index.d.ts +86 -0
  41. package/dist/types/index.js +1 -0
  42. package/dist/utils/async-pool.d.ts +1 -0
  43. package/dist/utils/async-pool.js +21 -0
  44. package/dist/utils/pattern.d.ts +1 -0
  45. package/dist/utils/pattern.js +11 -0
  46. package/dist/utils/semver.d.ts +13 -0
  47. package/dist/utils/semver.js +80 -0
  48. package/dist/workspace/discover.d.ts +1 -0
  49. package/dist/workspace/discover.js +88 -0
  50. package/dist/workspace/graph.d.ts +13 -0
  51. package/dist/workspace/graph.js +86 -0
  52. package/package.json +66 -0
@@ -0,0 +1,88 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ export async function discoverPackageDirs(cwd, workspaceMode) {
4
+ if (!workspaceMode) {
5
+ return [cwd];
6
+ }
7
+ const roots = new Set([cwd]);
8
+ const packagePatterns = await readPackageJsonWorkspacePatterns(cwd);
9
+ const pnpmPatterns = await readPnpmWorkspacePatterns(cwd);
10
+ for (const pattern of [...packagePatterns, ...pnpmPatterns]) {
11
+ const dirs = await expandSingleLevelPattern(cwd, pattern);
12
+ for (const dir of dirs) {
13
+ roots.add(dir);
14
+ }
15
+ }
16
+ const existing = [];
17
+ for (const dir of roots) {
18
+ const packageJsonPath = path.join(dir, "package.json");
19
+ try {
20
+ await fs.access(packageJsonPath);
21
+ existing.push(dir);
22
+ }
23
+ catch {
24
+ // ignore
25
+ }
26
+ }
27
+ return existing.sort();
28
+ }
29
+ async function readPackageJsonWorkspacePatterns(cwd) {
30
+ const packagePath = path.join(cwd, "package.json");
31
+ try {
32
+ const content = await fs.readFile(packagePath, "utf8");
33
+ const parsed = JSON.parse(content);
34
+ if (Array.isArray(parsed.workspaces)) {
35
+ return parsed.workspaces;
36
+ }
37
+ if (parsed.workspaces && Array.isArray(parsed.workspaces.packages)) {
38
+ return parsed.workspaces.packages;
39
+ }
40
+ return [];
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ }
46
+ async function readPnpmWorkspacePatterns(cwd) {
47
+ const workspacePath = path.join(cwd, "pnpm-workspace.yaml");
48
+ try {
49
+ const content = await fs.readFile(workspacePath, "utf8");
50
+ const lines = content.split(/\r?\n/);
51
+ const patterns = [];
52
+ for (const line of lines) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed.startsWith("-"))
55
+ continue;
56
+ const value = trimmed.replace(/^-\s*/, "").replace(/^['\"]|['\"]$/g, "");
57
+ if (value.length > 0) {
58
+ patterns.push(value);
59
+ }
60
+ }
61
+ return patterns;
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }
67
+ async function expandSingleLevelPattern(cwd, pattern) {
68
+ if (!pattern.includes("*")) {
69
+ return [path.resolve(cwd, pattern)];
70
+ }
71
+ const normalized = pattern.replace(/\\/g, "/");
72
+ const starIndex = normalized.indexOf("*");
73
+ const basePart = normalized.slice(0, starIndex).replace(/\/$/, "");
74
+ const suffix = normalized.slice(starIndex + 1);
75
+ if (suffix.length > 0 && suffix !== "/") {
76
+ return [];
77
+ }
78
+ const baseDir = path.resolve(cwd, basePart);
79
+ try {
80
+ const entries = await fs.readdir(baseDir, { withFileTypes: true });
81
+ return entries
82
+ .filter((entry) => entry.isDirectory())
83
+ .map((entry) => path.join(baseDir, entry.name));
84
+ }
85
+ catch {
86
+ return [];
87
+ }
88
+ }
@@ -0,0 +1,13 @@
1
+ import type { DependencyKind, PackageManifest } from "../types/index.js";
2
+ export interface WorkspaceNode {
3
+ packagePath: string;
4
+ packageName: string;
5
+ dependsOn: string[];
6
+ }
7
+ export interface WorkspaceGraphResult {
8
+ nodes: WorkspaceNode[];
9
+ orderedPaths: string[];
10
+ localPackageNames: Set<string>;
11
+ cycles: string[][];
12
+ }
13
+ export declare function buildWorkspaceGraph(manifestsByPath: Map<string, PackageManifest>, includeKinds: DependencyKind[]): WorkspaceGraphResult;
@@ -0,0 +1,86 @@
1
+ export function buildWorkspaceGraph(manifestsByPath, includeKinds) {
2
+ const nodes = [];
3
+ const localPackageNames = new Set();
4
+ for (const manifest of manifestsByPath.values()) {
5
+ if (typeof manifest.name === "string" && manifest.name.length > 0) {
6
+ localPackageNames.add(manifest.name);
7
+ }
8
+ }
9
+ for (const [packagePath, manifest] of manifestsByPath) {
10
+ const packageName = typeof manifest.name === "string" ? manifest.name : packagePath;
11
+ const dependsOn = new Set();
12
+ for (const kind of includeKinds) {
13
+ const section = manifest[kind];
14
+ if (!section)
15
+ continue;
16
+ for (const [depName, depRange] of Object.entries(section)) {
17
+ if (!localPackageNames.has(depName))
18
+ continue;
19
+ if (depRange.startsWith("workspace:"))
20
+ continue;
21
+ dependsOn.add(depName);
22
+ }
23
+ }
24
+ nodes.push({
25
+ packagePath,
26
+ packageName,
27
+ dependsOn: Array.from(dependsOn),
28
+ });
29
+ }
30
+ const byName = new Map(nodes.map((node) => [node.packageName, node]));
31
+ const inDegree = new Map();
32
+ const edges = new Map();
33
+ for (const node of nodes) {
34
+ inDegree.set(node.packageName, 0);
35
+ edges.set(node.packageName, []);
36
+ }
37
+ for (const node of nodes) {
38
+ for (const dep of node.dependsOn) {
39
+ if (!byName.has(dep))
40
+ continue;
41
+ const list = edges.get(dep);
42
+ if (list)
43
+ list.push(node.packageName);
44
+ inDegree.set(node.packageName, (inDegree.get(node.packageName) ?? 0) + 1);
45
+ }
46
+ }
47
+ const queue = [];
48
+ for (const [name, degree] of inDegree) {
49
+ if (degree === 0)
50
+ queue.push(name);
51
+ }
52
+ const orderedNames = [];
53
+ while (queue.length > 0) {
54
+ const current = queue.shift();
55
+ orderedNames.push(current);
56
+ for (const next of edges.get(current) ?? []) {
57
+ const degree = (inDegree.get(next) ?? 0) - 1;
58
+ inDegree.set(next, degree);
59
+ if (degree === 0)
60
+ queue.push(next);
61
+ }
62
+ }
63
+ const cycles = [];
64
+ if (orderedNames.length !== nodes.length) {
65
+ const remaining = nodes
66
+ .map((node) => node.packageName)
67
+ .filter((name) => !orderedNames.includes(name));
68
+ if (remaining.length > 0)
69
+ cycles.push(remaining);
70
+ }
71
+ const orderedPaths = orderedNames
72
+ .map((name) => byName.get(name))
73
+ .filter((node) => Boolean(node))
74
+ .map((node) => node.packagePath);
75
+ for (const node of nodes) {
76
+ if (!orderedPaths.includes(node.packagePath)) {
77
+ orderedPaths.push(node.packagePath);
78
+ }
79
+ }
80
+ return {
81
+ nodes,
82
+ orderedPaths,
83
+ localPackageNames,
84
+ cycles,
85
+ };
86
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@rainy-updates/cli",
3
+ "version": "0.4.0",
4
+ "description": "Agentic CLI to check and upgrade npm/pnpm dependencies for CI workflows",
5
+ "type": "module",
6
+ "private": false,
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/ferxalbs/rainy-updates.git"
11
+ },
12
+ "homepage": "https://github.com/ferxalbs/rainy-updates#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ferxalbs/rainy-updates/issues"
15
+ },
16
+ "keywords": [
17
+ "dependencies",
18
+ "updates",
19
+ "cli",
20
+ "ci",
21
+ "npm",
22
+ "pnpm",
23
+ "monorepo"
24
+ ],
25
+ "bin": {
26
+ "rainy-updates": "./dist/bin/cli.js"
27
+ },
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md",
38
+ "CHANGELOG.md",
39
+ "LICENSE"
40
+ ],
41
+ "scripts": {
42
+ "clean": "rm -rf dist",
43
+ "build": "tsc -p tsconfig.build.json",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "bun test",
46
+ "lint": "bunx biome check src tests",
47
+ "check": "bun run typecheck && bun test",
48
+ "test:prod": "node dist/bin/cli.js --help && node dist/bin/cli.js --version",
49
+ "prepublishOnly": "bun run check && bun run build && bun run test:prod"
50
+ },
51
+ "engines": {
52
+ "node": ">=20"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true
57
+ },
58
+ "devDependencies": {
59
+ "@types/bun": "latest",
60
+ "typescript": "^5.9.3"
61
+ },
62
+ "optionalDependencies": {
63
+ "better-sqlite3": "^12.6.2",
64
+ "undici": "^7.22.0"
65
+ }
66
+ }