relacher 0.0.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/README.md +153 -0
- package/lib-types/builder/cargo.d.ts +13 -0
- package/lib-types/builder/index.d.ts +3 -0
- package/lib-types/builder/pnpm.d.ts +13 -0
- package/lib-types/builder/shared.d.ts +12 -0
- package/lib-types/index.d.ts +6 -0
- package/lib-types/prepare.d.ts +14 -0
- package/lib-types/print.d.ts +2 -0
- package/lib-types/run.d.ts +7 -0
- package/lib-types/types.d.ts +87 -0
- package/lib-types/vcs/git.d.ts +14 -0
- package/lib-types/vcs/index.d.ts +8 -0
- package/lib-types/vcs/jj.d.ts +14 -0
- package/package.json +61 -0
- package/src/builder/cargo.ts +111 -0
- package/src/builder/index.ts +3 -0
- package/src/builder/pnpm.ts +162 -0
- package/src/builder/shared.ts +37 -0
- package/src/index.ts +6 -0
- package/src/prepare.ts +262 -0
- package/src/print.ts +109 -0
- package/src/run.ts +97 -0
- package/src/types.ts +114 -0
- package/src/vcs/git.ts +112 -0
- package/src/vcs/index.ts +10 -0
- package/src/vcs/jj.ts +127 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Relache
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
**Relache** is a scriptable, workspace-aware release orchestration library and CLI tool designed specifically for multi-package workspaces.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
📦 cli_tool 3.1.2 → 3.3.0 [minor]
|
|
11
|
+
flake.nix README.md CHANGELOG.md CHANGELOG.md
|
|
12
|
+
✦ 4a0ce62 feat(ui): add progress indicator to execution flow Author 2025-01-30
|
|
13
|
+
✦ Cascaded as [minor] from deps: plugin_api [minor]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
* 🤖 **Conventional Commits Engine:** Analyzes commit patterns to determine if modifications warrant a `patch`, `minor`, or `major` bump.
|
|
19
|
+
* 🔗 **Topological Cascading Rules:** Automatically tracks internal crate dependencies, propagating downstream version updates throughout your workspace.
|
|
20
|
+
* 🛠️ **TypeScript-First Configuration:** Declare, modify, and customize your release steps in code (via Bun, Deno, or Node) rather than static YAML or TOML configs.
|
|
21
|
+
* 📝 **Custom Changelog Rendering:** Flexible templating interfaces to format changelog files with precise controls.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### Why relache?
|
|
26
|
+
|
|
27
|
+
Most releases tools are configuration based, they often run as "all-or-nothing" CI black boxes. You execute the command, and it commits, tags, and publishes automatically.
|
|
28
|
+
|
|
29
|
+
Think of them as large functions that take your whole toml config and repo state as arguments.
|
|
30
|
+
|
|
31
|
+
Since relache is exposed as a typescript library you have full control of each step of the release lifecycle, you can replace or extends blocks as-needed without needing an extensive plugin system.
|
|
32
|
+
|
|
33
|
+
It's also meant to be used to update thoses annoying tasks of updating the version in deeply nested places. e.g. a frontend component, a readme...
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// cargoDeps will analyzer your workspace and form a graph of every crates
|
|
37
|
+
const CargoDeps = cargoDeps(tempDir).on('cli_tool', (c) => c
|
|
38
|
+
.update(
|
|
39
|
+
regexUpdate({
|
|
40
|
+
path: './flake.nix',
|
|
41
|
+
search: 'version = "[^"]+"',
|
|
42
|
+
replace: 'version = "{{version}}"',
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
.update(
|
|
46
|
+
regexUpdate({
|
|
47
|
+
path: './README.md',
|
|
48
|
+
search: 'CLI Tool v[^\\s]+',
|
|
49
|
+
replace: 'CLI Tool v{{version}}',
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
.update(
|
|
53
|
+
changelogUpdate({
|
|
54
|
+
path: './crates/cli_tool/CHANGELOG.md',
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
57
|
+
.update(
|
|
58
|
+
changelogUpdate({
|
|
59
|
+
path: './CHANGELOG.md',
|
|
60
|
+
global: true,
|
|
61
|
+
template: cliffTemplate,
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// this is how a crate is stored internally (simplified), notice how it's
|
|
67
|
+
// just a toml parser under the hood, no mention of rust, you can copy the
|
|
68
|
+
// source code of the cargo parser and pretty much adapt it to any setup of your
|
|
69
|
+
// liking:
|
|
70
|
+
//
|
|
71
|
+
// {
|
|
72
|
+
// "name": "cli_tool",
|
|
73
|
+
// "watch": ["crates/cli_tool"],
|
|
74
|
+
// "depends": ["plugin_api"],
|
|
75
|
+
// "updates": [
|
|
76
|
+
// {
|
|
77
|
+
// "kind": "toml",
|
|
78
|
+
// "path": "crates/cli_tool/Cargo.toml",
|
|
79
|
+
// "toml": "package.version"
|
|
80
|
+
// },
|
|
81
|
+
// // flake.nix, regex, changelog...
|
|
82
|
+
// ]
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
// ... You could prepare more actions here, like adding npm deps.
|
|
86
|
+
|
|
87
|
+
const vcs = new JjVcsProvider(tempDir);
|
|
88
|
+
|
|
89
|
+
const updates = await prepare(CargoDeps, vcs, {
|
|
90
|
+
cwd: tempDir,
|
|
91
|
+
sizes: {
|
|
92
|
+
major: { pattern: '^[a-z]+(?:\\([^)]+\\))?!:|BREAKING CHANGE' },
|
|
93
|
+
minor: { pattern: '^feat|^revert' },
|
|
94
|
+
patch: { pattern: '^fix|^build|^refactor|^nit|^style' },
|
|
95
|
+
skip: { pattern: '^release|^chore|^infra|^docs|^test|^ci|^build' },
|
|
96
|
+
},
|
|
97
|
+
cascade: {
|
|
98
|
+
patch: {
|
|
99
|
+
skip: 'patch',
|
|
100
|
+
patch: 'patch',
|
|
101
|
+
minor: 'minor',
|
|
102
|
+
major: 'minor',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
prettyPrint(updates);
|
|
108
|
+
|
|
109
|
+
const ans = prompt("Proceed with staging and committing release? [y/N]");
|
|
110
|
+
if (ans?.trim().toLowerCase() === "y") {
|
|
111
|
+
await run(updates, vcs, { cwd: root });
|
|
112
|
+
console.log("Release tags and files written successfully.");
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 📦 Installation
|
|
117
|
+
|
|
118
|
+
Add `relache` to your JavaScript or TypeScript workspace:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
bun add relache --dev
|
|
122
|
+
# or
|
|
123
|
+
pnpm install relache --save-dev
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 🛠️ Configuration & Customization
|
|
129
|
+
|
|
130
|
+
### Designing Custom Changelogs
|
|
131
|
+
You can supply your own renderer directly through the `template` option in `changelogUpdate`, bypassing rigid markdown configurations:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import type { ChangelogContext } from "relache";
|
|
135
|
+
|
|
136
|
+
function customTemplate({ version, date, commits }: ChangelogContext): string {
|
|
137
|
+
let lines = [`## [${version}] - ${date}\n`];
|
|
138
|
+
|
|
139
|
+
for (const commit of commits) {
|
|
140
|
+
const icon = commit.isBreaking ? "⚠️" : commit.type === "feat" ? "✨" : "🐛";
|
|
141
|
+
lines.push(`- ${icon} **${commit.scope || "general"}:** ${commit.description}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return lines.join("\n");
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🙏 Credits
|
|
151
|
+
|
|
152
|
+
* [git-cliff](https://github.com/orhun/git-cliff)
|
|
153
|
+
* [versio](https://github.com/chaaz/versio)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DependencyList } from './shared';
|
|
2
|
+
/**
|
|
3
|
+
* Loads a single Cargo project from a target Cargo.toml.
|
|
4
|
+
*/
|
|
5
|
+
export declare function cargoProject(cwd: string): DependencyList;
|
|
6
|
+
/**
|
|
7
|
+
* Loads a Cargo workspace, tracking member paths and dependency links.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cargoWorkspace(cwd: string): DependencyList;
|
|
10
|
+
/**
|
|
11
|
+
* Auto-detects whether the directory corresponds to a workspace or single-project setup.
|
|
12
|
+
*/
|
|
13
|
+
export declare function cargoDeps(cwd: string): DependencyList;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DependencyList } from './shared';
|
|
2
|
+
/**
|
|
3
|
+
* Loads a single project package configuration from package.json.
|
|
4
|
+
*/
|
|
5
|
+
export declare function pnpmProject(cwd: string): DependencyList;
|
|
6
|
+
/**
|
|
7
|
+
* Parses pnpm-workspace.yaml to identify target members and versioning topologies.
|
|
8
|
+
*/
|
|
9
|
+
export declare function pnpmWorkspace(cwd: string): DependencyList;
|
|
10
|
+
/**
|
|
11
|
+
* Auto-detects whether the workspace configuration exists, reverting to single packages.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pnpmDeps(cwd: string): DependencyList;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChangelogUpdate, RegexUpdate, DependencyConfig, UpdateAction } from '../types';
|
|
2
|
+
export declare function changelogUpdate(options: Omit<ChangelogUpdate, 'kind'>): ChangelogUpdate;
|
|
3
|
+
export declare function regexUpdate(options: Omit<RegexUpdate, 'kind'>): RegexUpdate;
|
|
4
|
+
export declare class SingleDependencyBuilder {
|
|
5
|
+
private config;
|
|
6
|
+
constructor(config: DependencyConfig);
|
|
7
|
+
update(action: UpdateAction): this;
|
|
8
|
+
}
|
|
9
|
+
export interface DependencyList extends Array<DependencyConfig> {
|
|
10
|
+
on(name: string, cb: (builder: SingleDependencyBuilder) => void): this;
|
|
11
|
+
}
|
|
12
|
+
export declare function decorateList(configs: DependencyConfig[]): DependencyList;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type BumpSize, type Commit, type VcsProvider, type DependencyConfig, type DependencyUpdateReport, type IntermediateReport, type PrepareOptions, type SizePatterns, type CascadeRules, type UpdateAction, type ChangelogContext } from './types';
|
|
2
|
+
export declare function getTomlValue(content: string): string | null;
|
|
3
|
+
export declare function bumpVersion(version: string, size: BumpSize): string;
|
|
4
|
+
export declare function getCurrentVersion(dep: DependencyConfig, cwd: string): string;
|
|
5
|
+
export declare function matchBumpSize(message: string, sizes: SizePatterns): BumpSize;
|
|
6
|
+
export declare function evaluateCommitsBump(commits: Commit[], sizes: SizePatterns): BumpSize;
|
|
7
|
+
export declare function topologicalSort(items: IntermediateReport[]): IntermediateReport[];
|
|
8
|
+
export declare function propagateBumps(sorted: IntermediateReport[], rules?: CascadeRules): void;
|
|
9
|
+
export declare function initReportItems(cargoDeps: DependencyConfig[], vcs: VcsProvider, sizes: SizePatterns, cwd: string): Promise<IntermediateReport[]>;
|
|
10
|
+
export declare function prepare(cargoDeps: DependencyConfig[], vcs: VcsProvider, options?: PrepareOptions): Promise<DependencyUpdateReport[]>;
|
|
11
|
+
export declare function buildChangelogBlock(version: string, commits: Commit[]): string;
|
|
12
|
+
export declare function finalizeReports(sorted: IntermediateReport[]): DependencyUpdateReport[];
|
|
13
|
+
export declare function defaultChangelogTemplate({ version, date, commits }: ChangelogContext): string;
|
|
14
|
+
export declare function mapResolvedUpdates(updates: UpdateAction[], newVersion: string, crateCommits: Commit[], globalCommits: Commit[]): UpdateAction[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DependencyUpdateReport, VcsProvider } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Applies calculated updates to files on disk, stages, commits, and tags using the provided VcsProvider.
|
|
4
|
+
*/
|
|
5
|
+
export declare function run(reports: DependencyUpdateReport[], vcs: VcsProvider, options: {
|
|
6
|
+
cwd: string;
|
|
7
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { VcsProvider } from './vcs';
|
|
2
|
+
export type BumpSize = "major" | "minor" | "patch" | "skip";
|
|
3
|
+
export interface Commit {
|
|
4
|
+
hash: string;
|
|
5
|
+
shortHash: string;
|
|
6
|
+
author: string;
|
|
7
|
+
date: string;
|
|
8
|
+
message: string;
|
|
9
|
+
type: string;
|
|
10
|
+
scope: string | null;
|
|
11
|
+
isBreaking: boolean;
|
|
12
|
+
description: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ChangelogContext {
|
|
15
|
+
version: string;
|
|
16
|
+
date: string;
|
|
17
|
+
commits: Commit[];
|
|
18
|
+
}
|
|
19
|
+
export interface ChangelogUpdate {
|
|
20
|
+
kind: "changelog";
|
|
21
|
+
path: string;
|
|
22
|
+
global?: boolean;
|
|
23
|
+
template?: (ctx: ChangelogContext) => string;
|
|
24
|
+
resolvedBlock?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface TomlUpdate {
|
|
27
|
+
kind: "toml";
|
|
28
|
+
path: string;
|
|
29
|
+
toml: string;
|
|
30
|
+
}
|
|
31
|
+
export interface RegexUpdate {
|
|
32
|
+
kind: "regex";
|
|
33
|
+
path: string;
|
|
34
|
+
search: string;
|
|
35
|
+
replace: string;
|
|
36
|
+
resolvedReplace?: string;
|
|
37
|
+
}
|
|
38
|
+
export type UpdateAction = TomlUpdate | RegexUpdate | ChangelogUpdate;
|
|
39
|
+
export interface DependencyConfig {
|
|
40
|
+
name: string;
|
|
41
|
+
watch?: string[];
|
|
42
|
+
updates?: UpdateAction[];
|
|
43
|
+
depends?: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface PatternConfig {
|
|
46
|
+
pattern: string;
|
|
47
|
+
}
|
|
48
|
+
export interface SizePatterns {
|
|
49
|
+
major: PatternConfig;
|
|
50
|
+
minor: PatternConfig;
|
|
51
|
+
patch: PatternConfig;
|
|
52
|
+
skip: PatternConfig;
|
|
53
|
+
}
|
|
54
|
+
export interface CascadeRules {
|
|
55
|
+
skip?: Record<BumpSize, BumpSize>;
|
|
56
|
+
patch?: Record<BumpSize, BumpSize>;
|
|
57
|
+
minor?: Record<BumpSize, BumpSize>;
|
|
58
|
+
major?: Record<BumpSize, BumpSize>;
|
|
59
|
+
}
|
|
60
|
+
export interface PrepareOptions {
|
|
61
|
+
sizes?: SizePatterns;
|
|
62
|
+
cascade?: CascadeRules;
|
|
63
|
+
cwd?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface DependencyUpdateReport {
|
|
66
|
+
name: string;
|
|
67
|
+
currentVersion: string;
|
|
68
|
+
newVersion: string;
|
|
69
|
+
bump: BumpSize;
|
|
70
|
+
originalBump?: BumpSize;
|
|
71
|
+
commits: Commit[];
|
|
72
|
+
updates: UpdateAction[];
|
|
73
|
+
depends?: string[];
|
|
74
|
+
}
|
|
75
|
+
export interface IntermediateReport {
|
|
76
|
+
name: string;
|
|
77
|
+
currentVersion: string;
|
|
78
|
+
newVersion: string;
|
|
79
|
+
bump: BumpSize;
|
|
80
|
+
originalBump?: BumpSize;
|
|
81
|
+
commits: Commit[];
|
|
82
|
+
updates: UpdateAction[];
|
|
83
|
+
depends: string[];
|
|
84
|
+
}
|
|
85
|
+
export declare const defaultSizes: SizePatterns;
|
|
86
|
+
export declare const defaultCascadeRules: Required<CascadeRules>;
|
|
87
|
+
export type { VcsProvider };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Commit, VcsProvider } from '../types';
|
|
2
|
+
export declare function runGit(cmd: string, cwd: string): string;
|
|
3
|
+
export declare function findLatestTag(tags: string[], prefix: string): string | null;
|
|
4
|
+
export declare function resolveGitTag(name: string, allTags: string[]): string | null;
|
|
5
|
+
export declare function getGitCommits(name: string, watch: string[], allTags: string[], cwd: string): Commit[];
|
|
6
|
+
export declare class GitVcsProvider implements VcsProvider {
|
|
7
|
+
private cwd;
|
|
8
|
+
private allTags;
|
|
9
|
+
constructor(cwd: string);
|
|
10
|
+
private getAllTags;
|
|
11
|
+
getCommits(name: string, watch: string[]): Promise<Commit[]>;
|
|
12
|
+
commit(message: string): Promise<void>;
|
|
13
|
+
tag(tagName: string): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Commit } from '../types';
|
|
2
|
+
export interface VcsProvider {
|
|
3
|
+
getCommits(name: string, watch: string[]): Promise<Commit[]>;
|
|
4
|
+
commit(message: string): Promise<void>;
|
|
5
|
+
tag(tagName: string): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export * from './git';
|
|
8
|
+
export * from './jj';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Commit, VcsProvider } from '../types';
|
|
2
|
+
export declare function runJj(cmd: string, cwd: string): string;
|
|
3
|
+
export declare function findLatestJjTag(tags: string[], prefix: string): string | null;
|
|
4
|
+
export declare function resolveJjTag(name: string, allTags: string[]): string | null;
|
|
5
|
+
export declare function getJjCommits(name: string, watch: string[], allTags: string[], cwd: string): Commit[];
|
|
6
|
+
export declare class JjVcsProvider implements VcsProvider {
|
|
7
|
+
private cwd;
|
|
8
|
+
private allTags;
|
|
9
|
+
constructor(cwd: string);
|
|
10
|
+
private getAllTags;
|
|
11
|
+
getCommits(name: string, watch: string[]): Promise<Commit[]>;
|
|
12
|
+
commit(message: string): Promise<void>;
|
|
13
|
+
tag(tagName: string): Promise<void>;
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "relacher",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Scriptable release orchestration library for monorepos",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"author": "Emilien Jegou",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://github.com/emilien-jegou/relache#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/emilien-jegou/relache.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/emilien-jegou/relache/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"bun",
|
|
19
|
+
"release",
|
|
20
|
+
"release-management",
|
|
21
|
+
"versioning",
|
|
22
|
+
"semver",
|
|
23
|
+
"monorepo",
|
|
24
|
+
"jujutsu",
|
|
25
|
+
"jj",
|
|
26
|
+
"git",
|
|
27
|
+
"pnpm",
|
|
28
|
+
"workspace",
|
|
29
|
+
"scriptable"
|
|
30
|
+
],
|
|
31
|
+
"main": "./src/index.ts",
|
|
32
|
+
"types": "./lib-types/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./lib-types/index.d.ts",
|
|
36
|
+
"import": "./src/index.ts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src",
|
|
41
|
+
"lib-types",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"ts:build": "tsc -p tsconfig.build.json",
|
|
47
|
+
"ts:watch": "tsc --noEmit --incremental --preserveWatchOutput --pretty --watch",
|
|
48
|
+
"lint": "oxlint",
|
|
49
|
+
"lint:fix": "oxlint --fix",
|
|
50
|
+
"lint:watch": "oxlint --watch",
|
|
51
|
+
"prepublishOnly": "bun run ts:build"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/bun": "latest",
|
|
55
|
+
"@types/node": "^24.12.4",
|
|
56
|
+
"oxfmt": "^0.19.0",
|
|
57
|
+
"oxlint": "^1.64.0",
|
|
58
|
+
"prettier": "^3.8.3",
|
|
59
|
+
"typescript": "^5.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { DependencyConfig } from '../types';
|
|
5
|
+
|
|
6
|
+
import { decorateList, type DependencyList } from './shared';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Loads a single Cargo project from a target Cargo.toml.
|
|
10
|
+
*/
|
|
11
|
+
export function cargoProject(cwd: string): DependencyList {
|
|
12
|
+
const configs: DependencyConfig[] = [];
|
|
13
|
+
const rootCargo = path.join(cwd, 'Cargo.toml');
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(rootCargo)) {
|
|
16
|
+
const fileContent = fs.readFileSync(rootCargo, 'utf8');
|
|
17
|
+
const nameMatch = fileContent.match(/^name\s*=\s*"([^"]+)"/m);
|
|
18
|
+
const parsedName = nameMatch && nameMatch[1] ? nameMatch[1] : '';
|
|
19
|
+
if (parsedName) {
|
|
20
|
+
configs.push({
|
|
21
|
+
name: parsedName,
|
|
22
|
+
watch: ['.'],
|
|
23
|
+
depends: [],
|
|
24
|
+
updates: [{ kind: 'toml', path: 'Cargo.toml', toml: 'package.version' }],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return decorateList(configs);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loads a Cargo workspace, tracking member paths and dependency links.
|
|
34
|
+
*/
|
|
35
|
+
export function cargoWorkspace(cwd: string): DependencyList {
|
|
36
|
+
const configs: DependencyConfig[] = [];
|
|
37
|
+
const rootCargo = path.join(cwd, 'Cargo.toml');
|
|
38
|
+
|
|
39
|
+
if (fs.existsSync(rootCargo)) {
|
|
40
|
+
const content = fs.readFileSync(rootCargo, 'utf8');
|
|
41
|
+
const membersMatch = content.match(/members\s*=\s*\[([^\]]+)\]/);
|
|
42
|
+
if (membersMatch && membersMatch[1]) {
|
|
43
|
+
const rawMembers = membersMatch[1].split(',').map((m) => m.replace(/["\s]/g, ''));
|
|
44
|
+
const crateInfos: { name: string; memberPath: string; content: string }[] = [];
|
|
45
|
+
|
|
46
|
+
for (const member of rawMembers) {
|
|
47
|
+
const globPath = path.join(cwd, member);
|
|
48
|
+
const cargoPaths = globPath.endsWith('*')
|
|
49
|
+
? fs
|
|
50
|
+
.readdirSync(path.dirname(globPath))
|
|
51
|
+
.map((dir) => path.join(path.dirname(globPath), dir, 'Cargo.toml'))
|
|
52
|
+
: [path.join(globPath, 'Cargo.toml')];
|
|
53
|
+
|
|
54
|
+
for (const p of cargoPaths) {
|
|
55
|
+
if (fs.existsSync(p)) {
|
|
56
|
+
const fileContent = fs.readFileSync(p, 'utf8');
|
|
57
|
+
const nameMatch = fileContent.match(/^name\s*=\s*"([^"]+)"/m);
|
|
58
|
+
const parsedName = nameMatch && nameMatch[1] ? nameMatch[1] : '';
|
|
59
|
+
if (parsedName) {
|
|
60
|
+
crateInfos.push({
|
|
61
|
+
name: parsedName,
|
|
62
|
+
memberPath: path.dirname(p),
|
|
63
|
+
content: fileContent,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const info of crateInfos) {
|
|
71
|
+
const relativePath = path.relative(cwd, info.memberPath);
|
|
72
|
+
const relativeCargo = path.join(relativePath, 'Cargo.toml');
|
|
73
|
+
|
|
74
|
+
const depends: string[] = [];
|
|
75
|
+
for (const other of crateInfos) {
|
|
76
|
+
if (other.name === info.name) continue;
|
|
77
|
+
const depPattern = new RegExp(`^${other.name}\\s*=`, 'm');
|
|
78
|
+
if (depPattern.test(info.content)) {
|
|
79
|
+
depends.push(other.name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
configs.push({
|
|
84
|
+
name: info.name,
|
|
85
|
+
watch: [relativePath],
|
|
86
|
+
depends,
|
|
87
|
+
updates: [{ kind: 'toml', path: relativeCargo, toml: 'package.version' }],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return decorateList(configs);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Auto-detects whether the directory corresponds to a workspace or single-project setup.
|
|
98
|
+
*/
|
|
99
|
+
export function cargoDeps(cwd: string): DependencyList {
|
|
100
|
+
const rootCargo = path.join(cwd, 'Cargo.toml');
|
|
101
|
+
if (fs.existsSync(rootCargo)) {
|
|
102
|
+
const content = fs.readFileSync(rootCargo, 'utf8');
|
|
103
|
+
if (content.includes('members =')) {
|
|
104
|
+
return cargoWorkspace(cwd);
|
|
105
|
+
}
|
|
106
|
+
if (content.includes('package =') || content.match(/^name\s*=/m)) {
|
|
107
|
+
return cargoProject(cwd);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return decorateList([]);
|
|
111
|
+
}
|