relacher 0.0.4-rc.0 → 0.0.4-rc.2
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/lib-types/builder/cargo.d.ts +0 -8
- package/lib-types/updater/builder.d.ts +3 -9
- package/lib-types/updater/changelog.d.ts +1 -9
- package/lib-types/updater/json.d.ts +1 -9
- package/lib-types/updater/regex.d.ts +1 -9
- package/lib-types/updater/toml.d.ts +1 -9
- package/package.json +4 -2
- package/src/builder/cargo.ts +228 -224
- package/src/builder/pnpm.ts +2 -2
- package/src/prepare.ts +3 -3
- package/src/updater/builder.ts +46 -28
- package/src/updater/index.ts +0 -1
|
@@ -2,12 +2,4 @@ import { type DependencyList } from './shared';
|
|
|
2
2
|
export interface CargoBuilderOptions {
|
|
3
3
|
syncWorkspaceDeps?: boolean;
|
|
4
4
|
}
|
|
5
|
-
/**
|
|
6
|
-
* Loads a single Cargo project from a target Cargo.toml.
|
|
7
|
-
*/
|
|
8
|
-
export declare function cargoProject(cwd: string): DependencyList;
|
|
9
|
-
/**
|
|
10
|
-
* Loads a Cargo workspace, tracking member paths, dependency links, and recursive nested paths.
|
|
11
|
-
*/
|
|
12
|
-
export declare function cargoWorkspace(cwd: string, options?: CargoBuilderOptions): DependencyList;
|
|
13
5
|
export declare function cargoDeps(cwd: string, options?: CargoBuilderOptions): DependencyList;
|
|
@@ -17,6 +17,8 @@ export interface UpdateAction {
|
|
|
17
17
|
required?: boolean;
|
|
18
18
|
params: any;
|
|
19
19
|
prepare(data: UpdateActionOptions): UpdateActionResolved;
|
|
20
|
+
skipIf(fn: (cwd: string) => boolean): this;
|
|
21
|
+
_skipIf(cwd: string): boolean;
|
|
20
22
|
}
|
|
21
23
|
export interface VersionFallback {
|
|
22
24
|
readFallback(cwd: string): string | null;
|
|
@@ -44,13 +46,5 @@ type UpdateBuilderParams<T, K = undefined> = {
|
|
|
44
46
|
};
|
|
45
47
|
export declare const updateBuilder: <T, K = undefined>({ kind, apply, prepare }: UpdateBuilderParams<T, K>) => (targetPath: string, params: T & {
|
|
46
48
|
required?: boolean;
|
|
47
|
-
}) =>
|
|
48
|
-
kind: string;
|
|
49
|
-
path: string;
|
|
50
|
-
params: T & {
|
|
51
|
-
required?: boolean;
|
|
52
|
-
};
|
|
53
|
-
required: boolean | undefined;
|
|
54
|
-
prepare(options: UpdateActionOptions): UpdateActionResolved;
|
|
55
|
-
};
|
|
49
|
+
}) => UpdateAction;
|
|
56
50
|
export {};
|
|
@@ -6,13 +6,5 @@ export type ChangelogUpdateParams = {
|
|
|
6
6
|
};
|
|
7
7
|
export declare const changelogUpdate: (targetPath: string, params: ChangelogUpdateParams & {
|
|
8
8
|
required?: boolean;
|
|
9
|
-
}) =>
|
|
10
|
-
kind: string;
|
|
11
|
-
path: string;
|
|
12
|
-
params: ChangelogUpdateParams & {
|
|
13
|
-
required?: boolean;
|
|
14
|
-
};
|
|
15
|
-
required: boolean | undefined;
|
|
16
|
-
prepare(options: import("./builder").UpdateActionOptions): import("./builder").UpdateActionResolved;
|
|
17
|
-
};
|
|
9
|
+
}) => import("./builder").UpdateAction;
|
|
18
10
|
export declare function defaultChangelogTemplate({ version, date, commits }: ChangelogContext): string;
|
|
@@ -8,12 +8,4 @@ export type JsonUpdateParams = (parsed: any, report: DependencyUpdateReport, rep
|
|
|
8
8
|
export declare const jsonFallback: (params: JsonFallbackParams) => VersionFallback;
|
|
9
9
|
export declare const jsonUpdate: (targetPath: string, params: JsonUpdateParams & {
|
|
10
10
|
required?: boolean;
|
|
11
|
-
}) =>
|
|
12
|
-
kind: string;
|
|
13
|
-
path: string;
|
|
14
|
-
params: JsonUpdateParams & {
|
|
15
|
-
required?: boolean;
|
|
16
|
-
};
|
|
17
|
-
required: boolean | undefined;
|
|
18
|
-
prepare(options: import("./builder").UpdateActionOptions): import("./builder").UpdateActionResolved;
|
|
19
|
-
};
|
|
11
|
+
}) => import("./builder").UpdateAction;
|
|
@@ -11,12 +11,4 @@ export type RegexUpdateParams = {
|
|
|
11
11
|
export declare const regexFallback: (params: RegexFallbackParams) => VersionFallback;
|
|
12
12
|
export declare const regexUpdate: (targetPath: string, params: RegexUpdateParams & {
|
|
13
13
|
required?: boolean;
|
|
14
|
-
}) =>
|
|
15
|
-
kind: string;
|
|
16
|
-
path: string;
|
|
17
|
-
params: RegexUpdateParams & {
|
|
18
|
-
required?: boolean;
|
|
19
|
-
};
|
|
20
|
-
required: boolean | undefined;
|
|
21
|
-
prepare(options: import("./builder").UpdateActionOptions): import("./builder").UpdateActionResolved;
|
|
22
|
-
};
|
|
14
|
+
}) => import("./builder").UpdateAction;
|
|
@@ -8,12 +8,4 @@ export type TomlUpdateParams = (parsed: any, report: DependencyUpdateReport, rep
|
|
|
8
8
|
export declare const tomlFallback: (params: TomlFallbackParams) => VersionFallback;
|
|
9
9
|
export declare const tomlUpdate: (targetPath: string, params: TomlUpdateParams & {
|
|
10
10
|
required?: boolean;
|
|
11
|
-
}) =>
|
|
12
|
-
kind: string;
|
|
13
|
-
path: string;
|
|
14
|
-
params: TomlUpdateParams & {
|
|
15
|
-
required?: boolean;
|
|
16
|
-
};
|
|
17
|
-
required: boolean | undefined;
|
|
18
|
-
prepare(options: import("./builder").UpdateActionOptions): import("./builder").UpdateActionResolved;
|
|
19
|
-
};
|
|
11
|
+
}) => import("./builder").UpdateAction;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relacher",
|
|
3
|
-
"version": "0.0.4-rc.
|
|
3
|
+
"version": "0.0.4-rc.2",
|
|
4
4
|
"description": "Scriptable release orchestration library for monorepos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -56,7 +56,9 @@
|
|
|
56
56
|
"oxfmt": "^0.19.0",
|
|
57
57
|
"oxlint": "^1.64.0",
|
|
58
58
|
"prettier": "^3.8.3",
|
|
59
|
-
"smol-toml": "^1.6.1",
|
|
60
59
|
"typescript": "^6.0.3"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"smol-toml": "^1.6.1"
|
|
61
63
|
}
|
|
62
64
|
}
|
package/src/builder/cargo.ts
CHANGED
|
@@ -12,244 +12,248 @@ export interface CargoBuilderOptions {
|
|
|
12
12
|
syncWorkspaceDeps?: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
depends: [],
|
|
55
|
-
versionFallback: tomlFallback({
|
|
56
|
-
path: 'Cargo.toml',
|
|
57
|
-
read: (d) => d.package?.version,
|
|
58
|
-
}),
|
|
59
|
-
updates,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
// Ignore parse error
|
|
15
|
+
interface PathDep {
|
|
16
|
+
name: string;
|
|
17
|
+
path: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isWorkspace(cargoPath: string): boolean {
|
|
21
|
+
try {
|
|
22
|
+
const doc = parse(fs.readFileSync(cargoPath, 'utf8')) as any;
|
|
23
|
+
return !!doc?.workspace;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function checkDir(dir: string): { isRoot: boolean; hasCargo: boolean } {
|
|
30
|
+
const cargoPath = path.join(dir, 'Cargo.toml');
|
|
31
|
+
const hasCargo = fs.existsSync(cargoPath);
|
|
32
|
+
return { isRoot: hasCargo && isWorkspace(cargoPath), hasCargo };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function findCargoWorkspaceRoot(cwd: string): string {
|
|
36
|
+
let current = path.resolve(cwd);
|
|
37
|
+
let bestRoot = current;
|
|
38
|
+
while (true) {
|
|
39
|
+
const { isRoot, hasCargo } = checkDir(current);
|
|
40
|
+
if (isRoot) return current;
|
|
41
|
+
if (hasCargo) bestRoot = current;
|
|
42
|
+
const parent = path.dirname(current);
|
|
43
|
+
if (parent === current) break;
|
|
44
|
+
current = parent;
|
|
45
|
+
}
|
|
46
|
+
return bestRoot;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getPathFromVal(name: string, val: any, wsDeps: Record<string, any>): string | undefined {
|
|
50
|
+
if (val && typeof val === 'object') {
|
|
51
|
+
if (val.path) return val.path;
|
|
52
|
+
if (val.workspace === true && wsDeps[name]?.path) {
|
|
53
|
+
return wsDeps[name].path;
|
|
64
54
|
}
|
|
65
55
|
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
66
58
|
|
|
67
|
-
|
|
59
|
+
function collectDepsFromBlock(block: any, wsDeps: Record<string, any>): PathDep[] {
|
|
60
|
+
if (!block || typeof block !== 'object') return [];
|
|
61
|
+
const list: PathDep[] = [];
|
|
62
|
+
for (const [name, val] of Object.entries(block)) {
|
|
63
|
+
const p = getPathFromVal(name, val, wsDeps);
|
|
64
|
+
if (p) list.push({ name, path: p });
|
|
65
|
+
}
|
|
66
|
+
return list;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
function collectTargetDeps(doc: any, wsDeps: Record<string, any>): PathDep[] {
|
|
70
|
+
if (!doc.target || typeof doc.target !== 'object') return [];
|
|
71
|
+
const list: PathDep[] = [];
|
|
72
|
+
for (const targetValue of Object.values(doc.target)) {
|
|
73
|
+
if (targetValue && typeof targetValue === 'object') {
|
|
74
|
+
list.push(...collectDepsFromBlock((targetValue as any).dependencies, wsDeps));
|
|
75
|
+
list.push(...collectDepsFromBlock((targetValue as any)['dev-dependencies'], wsDeps));
|
|
76
|
+
list.push(...collectDepsFromBlock((targetValue as any)['build-dependencies'], wsDeps));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return list;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function extractPathDependencies(doc: any, wsDeps: Record<string, any> = {}): PathDep[] {
|
|
83
|
+
return [
|
|
84
|
+
...collectDepsFromBlock(doc.dependencies, wsDeps),
|
|
85
|
+
...collectDepsFromBlock(doc['dev-dependencies'], wsDeps),
|
|
86
|
+
...collectDepsFromBlock(doc['build-dependencies'], wsDeps),
|
|
87
|
+
...collectTargetDeps(doc, wsDeps),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class CrateScanner {
|
|
92
|
+
workspaceRoot: string;
|
|
93
|
+
workspaceDeps: Record<string, any>;
|
|
94
|
+
discoveredPaths = new Set<string>();
|
|
95
|
+
crateInfos: { name: string; memberPath: string; doc: any }[] = [];
|
|
96
|
+
|
|
97
|
+
constructor(workspaceRoot: string, workspaceDeps: Record<string, any>) {
|
|
98
|
+
this.workspaceRoot = workspaceRoot;
|
|
99
|
+
this.workspaceDeps = workspaceDeps;
|
|
100
|
+
}
|
|
79
101
|
|
|
80
|
-
|
|
102
|
+
registerCrate(p: string) {
|
|
103
|
+
const absPath = path.resolve(this.workspaceRoot, p);
|
|
104
|
+
if (this.discoveredPaths.has(absPath)) return;
|
|
105
|
+
this.discoveredPaths.add(absPath);
|
|
106
|
+
const cargoPath = path.join(absPath, 'Cargo.toml');
|
|
107
|
+
if (!fs.existsSync(cargoPath)) return;
|
|
81
108
|
try {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (Array.isArray(members)) {
|
|
87
|
-
const crateInfos: { name: string; memberPath: string; doc: any }[] = [];
|
|
88
|
-
|
|
89
|
-
for (const member of members) {
|
|
90
|
-
const globPath = path.join(cwd, member);
|
|
91
|
-
const cargoPaths = globPath.endsWith('*')
|
|
92
|
-
? fs.existsSync(path.dirname(globPath))
|
|
93
|
-
? fs
|
|
94
|
-
.readdirSync(path.dirname(globPath))
|
|
95
|
-
.map((dir) => path.join(path.dirname(globPath), dir, 'Cargo.toml'))
|
|
96
|
-
: []
|
|
97
|
-
: [path.join(globPath, 'Cargo.toml')];
|
|
98
|
-
|
|
99
|
-
for (const p of cargoPaths) {
|
|
100
|
-
if (fs.existsSync(p)) {
|
|
101
|
-
const memberPath = path.dirname(p);
|
|
102
|
-
discoveredPaths.add(memberPath);
|
|
103
|
-
const fileContent = fs.readFileSync(p, 'utf8');
|
|
104
|
-
const doc = parse(fileContent) as any;
|
|
105
|
-
const parsedName = doc.package?.name;
|
|
106
|
-
if (parsedName) {
|
|
107
|
-
crateInfos.push({ name: parsedName, memberPath, doc });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Discover nested packages dynamically via path dependencies on the parsed objects
|
|
114
|
-
let i = 0;
|
|
115
|
-
while (i < crateInfos.length) {
|
|
116
|
-
const info = crateInfos[i]!;
|
|
117
|
-
const deps = [
|
|
118
|
-
...(Object.values(info.doc.dependencies || {}) as any[]),
|
|
119
|
-
...(Object.values(info.doc['dev-dependencies'] || {}) as any[]),
|
|
120
|
-
...(Object.values(info.doc['build-dependencies'] || {}) as any[]),
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
for (const dep of deps) {
|
|
124
|
-
if (dep && typeof dep === 'object' && dep.path) {
|
|
125
|
-
const fullDepPath = path.resolve(info.memberPath, dep.path);
|
|
126
|
-
if (!discoveredPaths.has(fullDepPath)) {
|
|
127
|
-
discoveredPaths.add(fullDepPath);
|
|
128
|
-
const cargoPath = path.join(fullDepPath, 'Cargo.toml');
|
|
129
|
-
|
|
130
|
-
if (fs.existsSync(cargoPath)) {
|
|
131
|
-
const fileContent = fs.readFileSync(cargoPath, 'utf8');
|
|
132
|
-
const doc = parse(fileContent) as any;
|
|
133
|
-
const parsedName = doc.package?.name;
|
|
134
|
-
if (parsedName) {
|
|
135
|
-
crateInfos.push({ name: parsedName, memberPath: fullDepPath, doc });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
i++;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
for (const info of crateInfos) {
|
|
145
|
-
const relativePath = path.relative(cwd, info.memberPath);
|
|
146
|
-
const posixRelativePath = relativePath.split(path.sep).join(path.posix.sep);
|
|
147
|
-
const relativeCargo = posixRelativePath
|
|
148
|
-
? path.posix.join(posixRelativePath, 'Cargo.toml')
|
|
149
|
-
: 'Cargo.toml';
|
|
150
|
-
|
|
151
|
-
const depends: string[] = [];
|
|
152
|
-
const allDeps = {
|
|
153
|
-
...(info.doc.dependencies || {}),
|
|
154
|
-
...(info.doc['dev-dependencies'] || {}),
|
|
155
|
-
...(info.doc['build-dependencies'] || {}),
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
for (const other of crateInfos) {
|
|
159
|
-
if (other.name === info.name) continue;
|
|
160
|
-
if (allDeps[other.name]) {
|
|
161
|
-
depends.push(other.name);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const updates = [
|
|
166
|
-
tomlUpdate(relativeCargo, (doc, report, reports) => {
|
|
167
|
-
// Update self
|
|
168
|
-
if (doc.package) {
|
|
169
|
-
doc.package.version = report.newVersion;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Keep local path workspace dependencies perfectly in sync internally
|
|
173
|
-
const depTypes = ['dependencies', 'dev-dependencies', 'build-dependencies'];
|
|
174
|
-
for (const depType of depTypes) {
|
|
175
|
-
if (!doc[depType]) continue;
|
|
176
|
-
for (const depReport of reports) {
|
|
177
|
-
if (depReport.name === report.name) continue;
|
|
178
|
-
const dep = doc[depType][depReport.name];
|
|
179
|
-
if (dep && typeof dep === 'object' && dep.version) {
|
|
180
|
-
dep.version = depReport.newVersion;
|
|
181
|
-
} else if (typeof dep === 'string') {
|
|
182
|
-
doc[depType][depReport.name] = depReport.newVersion;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}),
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
if (hasLock) {
|
|
190
|
-
updates.push(
|
|
191
|
-
tomlUpdate('Cargo.lock', (doc, report) => {
|
|
192
|
-
if (Array.isArray(doc.package)) {
|
|
193
|
-
// Only update local packages (missing source/checksum attributes)
|
|
194
|
-
const pkg = doc.package.find(
|
|
195
|
-
(p: any) => p.name === info.name && !p.source && !p.checksum,
|
|
196
|
-
);
|
|
197
|
-
if (pkg) pkg.version = report.newVersion;
|
|
198
|
-
}
|
|
199
|
-
}),
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (options?.syncWorkspaceDeps) {
|
|
204
|
-
updates.push(
|
|
205
|
-
tomlUpdate('Cargo.toml', (doc, report) => {
|
|
206
|
-
if (doc.workspace?.dependencies?.[info.name]) {
|
|
207
|
-
const dep = doc.workspace.dependencies[info.name];
|
|
208
|
-
if (typeof dep === 'object' && dep.version) {
|
|
209
|
-
dep.version = report.newVersion;
|
|
210
|
-
} else if (typeof dep === 'string') {
|
|
211
|
-
doc.workspace.dependencies[info.name] = report.newVersion;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}),
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
configs.push({
|
|
219
|
-
name: info.name,
|
|
220
|
-
watch: [posixRelativePath || '.'],
|
|
221
|
-
depends,
|
|
222
|
-
versionFallback: tomlFallback({
|
|
223
|
-
path: relativeCargo,
|
|
224
|
-
read: (doc) => doc.package?.version,
|
|
225
|
-
}),
|
|
226
|
-
updates,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
109
|
+
const doc = parse(fs.readFileSync(cargoPath, 'utf8')) as any;
|
|
110
|
+
if (doc.package?.name) {
|
|
111
|
+
this.crateInfos.push({ name: doc.package.name, memberPath: absPath, doc });
|
|
229
112
|
}
|
|
230
|
-
} catch {
|
|
231
|
-
|
|
113
|
+
} catch { }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function expandGlob(workspaceRoot: string, member: string): string[] {
|
|
118
|
+
const globPath = path.join(workspaceRoot, member);
|
|
119
|
+
if (!globPath.endsWith('*')) return [path.join(globPath, 'Cargo.toml')];
|
|
120
|
+
const dir = path.dirname(globPath);
|
|
121
|
+
if (!fs.existsSync(dir)) return [];
|
|
122
|
+
return fs.readdirSync(dir).map((d) => path.join(dir, d, 'Cargo.toml'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function registerWorkspaceMembers(scanner: CrateScanner, rootDoc: any) {
|
|
126
|
+
if (rootDoc.package?.name) {
|
|
127
|
+
scanner.registerCrate(scanner.workspaceRoot);
|
|
128
|
+
}
|
|
129
|
+
const members = rootDoc.workspace?.members;
|
|
130
|
+
if (!Array.isArray(members)) return;
|
|
131
|
+
for (const member of members) {
|
|
132
|
+
const paths = expandGlob(scanner.workspaceRoot, member).filter(fs.existsSync);
|
|
133
|
+
paths.forEach((p) => scanner.registerCrate(path.dirname(p)));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function runRecursiveDiscovery(scanner: CrateScanner) {
|
|
138
|
+
let i = 0;
|
|
139
|
+
while (i < scanner.crateInfos.length) {
|
|
140
|
+
const info = scanner.crateInfos[i]!;
|
|
141
|
+
const foundDeps = extractPathDependencies(info.doc, scanner.workspaceDeps);
|
|
142
|
+
for (const dep of foundDeps) {
|
|
143
|
+
const fullDepPath = path.resolve(info.memberPath, dep.path);
|
|
144
|
+
scanner.registerCrate(fullDepPath);
|
|
232
145
|
}
|
|
146
|
+
i++;
|
|
233
147
|
}
|
|
148
|
+
}
|
|
234
149
|
|
|
235
|
-
|
|
150
|
+
function updateBlockEntry(block: any, depName: string, newVersion: string) {
|
|
151
|
+
const dep = block[depName];
|
|
152
|
+
if (dep && typeof dep === 'object' && dep.version) {
|
|
153
|
+
dep.version = newVersion;
|
|
154
|
+
} else if (typeof dep === 'string') {
|
|
155
|
+
block[depName] = newVersion;
|
|
156
|
+
}
|
|
236
157
|
}
|
|
237
158
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
159
|
+
function syncDeps(block: any, reports: any[], selfName: string) {
|
|
160
|
+
if (!block || typeof block !== 'object') return;
|
|
161
|
+
for (const r of reports) {
|
|
162
|
+
if (r.name !== selfName && block[r.name]) {
|
|
163
|
+
updateBlockEntry(block, r.name, r.newVersion);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function syncAllBlocks(doc: any, reports: any[], selfName: string) {
|
|
169
|
+
syncDeps(doc.dependencies, reports, selfName);
|
|
170
|
+
syncDeps(doc['dev-dependencies'], reports, selfName);
|
|
171
|
+
syncDeps(doc['build-dependencies'], reports, selfName);
|
|
172
|
+
if (doc.target && typeof doc.target === 'object') {
|
|
173
|
+
for (const targetVal of Object.values(doc.target)) {
|
|
174
|
+
if (targetVal && typeof targetVal === 'object') {
|
|
175
|
+
syncDeps((targetVal as any).dependencies, reports, selfName);
|
|
176
|
+
syncDeps((targetVal as any)['dev-dependencies'], reports, selfName);
|
|
177
|
+
syncDeps((targetVal as any)['build-dependencies'], reports, selfName);
|
|
249
178
|
}
|
|
250
|
-
} catch {
|
|
251
|
-
// Fallback below
|
|
252
179
|
}
|
|
253
180
|
}
|
|
254
|
-
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createSelfUpdate(relativeCargo: string, selfName: string) {
|
|
184
|
+
return tomlUpdate(relativeCargo, (doc, report, reports) => {
|
|
185
|
+
if (doc.package) {
|
|
186
|
+
doc.package.version = report.newVersion;
|
|
187
|
+
}
|
|
188
|
+
syncAllBlocks(doc, reports, selfName);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function createLockUpdate(selfName: string, hasLock: boolean) {
|
|
193
|
+
return tomlUpdate('Cargo.lock', (doc, report) => {
|
|
194
|
+
if (Array.isArray(doc.package)) {
|
|
195
|
+
const pkg = doc.package.find((p: any) => p.name === selfName && !p.source && !p.checksum);
|
|
196
|
+
if (pkg) pkg.version = report.newVersion;
|
|
197
|
+
}
|
|
198
|
+
}).skipIf(() => !hasLock);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createWorkspaceDepUpdate(selfName: string, options?: CargoBuilderOptions) {
|
|
202
|
+
return tomlUpdate('Cargo.toml', (doc, report) => {
|
|
203
|
+
const dep = doc.workspace?.dependencies?.[selfName];
|
|
204
|
+
if (typeof dep === 'object' && dep.version) {
|
|
205
|
+
dep.version = report.newVersion;
|
|
206
|
+
} else if (typeof dep === 'string') {
|
|
207
|
+
doc.workspace.dependencies[selfName] = report.newVersion;
|
|
208
|
+
}
|
|
209
|
+
}).skipIf(() => !options?.syncWorkspaceDeps);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function buildCrateConfig(
|
|
213
|
+
info: any,
|
|
214
|
+
workspaceRoot: string,
|
|
215
|
+
localNames: Set<string>,
|
|
216
|
+
hasLock: boolean,
|
|
217
|
+
wsDeps: Record<string, any>,
|
|
218
|
+
options?: CargoBuilderOptions,
|
|
219
|
+
): DependencyConfig {
|
|
220
|
+
const relPath = path.relative(workspaceRoot, info.memberPath);
|
|
221
|
+
const posixPath = relPath.split(path.sep).join(path.posix.sep);
|
|
222
|
+
const relCargo = posixPath ? path.posix.join(posixPath, 'Cargo.toml') : 'Cargo.toml';
|
|
223
|
+
const depends = extractPathDependencies(info.doc, wsDeps)
|
|
224
|
+
.map((fd) => fd.name)
|
|
225
|
+
.filter((name) => localNames.has(name) && name !== info.name);
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
name: info.name,
|
|
229
|
+
watch: [posixPath || '.'],
|
|
230
|
+
depends,
|
|
231
|
+
versionFallback: tomlFallback({ path: relCargo, read: (doc) => doc.package?.version }),
|
|
232
|
+
updates: [
|
|
233
|
+
createSelfUpdate(relCargo, info.name),
|
|
234
|
+
createLockUpdate(info.name, hasLock),
|
|
235
|
+
createWorkspaceDepUpdate(info.name, options),
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function cargoDeps(cwd: string, options?: CargoBuilderOptions): DependencyList {
|
|
241
|
+
const workspaceRoot = findCargoWorkspaceRoot(cwd);
|
|
242
|
+
const rootCargo = path.join(workspaceRoot, 'Cargo.toml');
|
|
243
|
+
if (!fs.existsSync(rootCargo)) return decorateList([]);
|
|
244
|
+
try {
|
|
245
|
+
const rootDoc = parse(fs.readFileSync(rootCargo, 'utf8')) as any;
|
|
246
|
+
const scanner = new CrateScanner(workspaceRoot, rootDoc.workspace?.dependencies || {});
|
|
247
|
+
if (rootDoc.workspace) registerWorkspaceMembers(scanner, rootDoc);
|
|
248
|
+
else if (rootDoc.package?.name) scanner.registerCrate(workspaceRoot);
|
|
249
|
+
runRecursiveDiscovery(scanner);
|
|
250
|
+
const hasLock = fs.existsSync(path.join(workspaceRoot, 'Cargo.lock'));
|
|
251
|
+
const localNames = new Set(scanner.crateInfos.map((c) => c.name));
|
|
252
|
+
const configs = scanner.crateInfos.map((info) =>
|
|
253
|
+
buildCrateConfig(info, workspaceRoot, localNames, hasLock, scanner.workspaceDeps, options),
|
|
254
|
+
);
|
|
255
|
+
return decorateList(configs);
|
|
256
|
+
} catch {
|
|
257
|
+
return decorateList([]);
|
|
258
|
+
}
|
|
255
259
|
}
|
package/src/builder/pnpm.ts
CHANGED
|
@@ -29,7 +29,7 @@ export function pnpmProject(cwd: string): DependencyList {
|
|
|
29
29
|
updates: [
|
|
30
30
|
jsonUpdate('package.json', (parsed, report) => {
|
|
31
31
|
parsed.version = report.newVersion;
|
|
32
|
-
}),
|
|
32
|
+
}).skipIf((cwd) => !fs.existsSync(path.join(cwd, 'package.json'))),
|
|
33
33
|
],
|
|
34
34
|
});
|
|
35
35
|
}
|
|
@@ -173,7 +173,7 @@ export function pnpmWorkspace(cwd: string): DependencyList {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
-
}),
|
|
176
|
+
}).skipIf((cwd) => !fs.existsSync(path.join(cwd, relativePJson))),
|
|
177
177
|
],
|
|
178
178
|
});
|
|
179
179
|
}
|
package/src/prepare.ts
CHANGED
|
@@ -33,12 +33,10 @@ export async function getCurrentVersion(
|
|
|
33
33
|
vcs: VcsProvider,
|
|
34
34
|
cwd: string,
|
|
35
35
|
): Promise<{ version: string | null; isFallback: boolean }> {
|
|
36
|
-
// 1. Look for releases in git history first
|
|
37
36
|
const latestTag = await vcs.getLatestTag(dep.name);
|
|
38
37
|
if (latestTag) {
|
|
39
38
|
let version = latestTag;
|
|
40
39
|
|
|
41
|
-
// Strip package-specific prefixes (e.g., "api-v1.0.0" or "api/v1.0.0" -> "v1.0.0")
|
|
42
40
|
if (version.startsWith(`${dep.name}-`)) {
|
|
43
41
|
version = version.slice(dep.name.length + 1);
|
|
44
42
|
} else if (version.startsWith(`${dep.name}/`)) {
|
|
@@ -48,7 +46,6 @@ export async function getCurrentVersion(
|
|
|
48
46
|
return { version: version.replace(/^v/, ''), isFallback: false };
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
// 2. Resolve via user-defined fallback
|
|
52
49
|
if (dep.versionFallback) {
|
|
53
50
|
return { version: dep.versionFallback.readFallback(cwd), isFallback: true };
|
|
54
51
|
}
|
|
@@ -145,6 +142,9 @@ export async function initReportItems(
|
|
|
145
142
|
|
|
146
143
|
let isErroneous = isMissingVersion;
|
|
147
144
|
for (const u of dep.updates || []) {
|
|
145
|
+
if (u._skipIf?.(cwd)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
148
|
if (u.required) {
|
|
149
149
|
const filePath = path.resolve(cwd, u.path);
|
|
150
150
|
if (!fs.existsSync(filePath)) {
|
package/src/updater/builder.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface UpdateAction {
|
|
|
20
20
|
required?: boolean;
|
|
21
21
|
params: any;
|
|
22
22
|
prepare(data: UpdateActionOptions): UpdateActionResolved;
|
|
23
|
+
skipIf(fn: (cwd: string) => boolean): this;
|
|
24
|
+
_skipIf(cwd: string): boolean;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface VersionFallback {
|
|
@@ -33,7 +35,7 @@ export type ApplyActionFnArgs<T, K = undefined> = {
|
|
|
33
35
|
report: DependencyUpdateReport;
|
|
34
36
|
reports: DependencyUpdateReport[];
|
|
35
37
|
cwd: string;
|
|
36
|
-
preparedData: K;
|
|
38
|
+
preparedData: K;
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
type ApplyActionFn<T, K = undefined> = (args: ApplyActionFnArgs<T, K>) => void;
|
|
@@ -48,36 +50,52 @@ type PrepareActionFn<T, K> = (args: PrepareActionFnArgs<T>) => K;
|
|
|
48
50
|
|
|
49
51
|
type UpdateBuilderParams<T, K = undefined> = {
|
|
50
52
|
kind: string;
|
|
51
|
-
apply: ApplyActionFn<T, K>;
|
|
53
|
+
apply: ApplyActionFn<T, K>;
|
|
52
54
|
prepare?: PrepareActionFn<T, K>;
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
export const updateBuilder =
|
|
56
58
|
<T, K = undefined>({ kind, apply, prepare }: UpdateBuilderParams<T, K>) =>
|
|
57
|
-
(targetPath: string, params: T & { required?: boolean }) =>
|
|
58
|
-
|
|
59
|
-
path: targetPath,
|
|
60
|
-
params,
|
|
61
|
-
required: params.required,
|
|
62
|
-
prepare(options: UpdateActionOptions): UpdateActionResolved {
|
|
63
|
-
const preparedData = prepare?.({ params, targetPath, options }) as K;
|
|
59
|
+
(targetPath: string, params: T & { required?: boolean }) => {
|
|
60
|
+
let skipIfCallback: ((cwd: string) => boolean) | undefined;
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
62
|
+
const action: UpdateAction = {
|
|
63
|
+
kind,
|
|
64
|
+
path: targetPath,
|
|
65
|
+
params,
|
|
66
|
+
required: params.required,
|
|
67
|
+
skipIf(fn: (cwd: string) => boolean) {
|
|
68
|
+
skipIfCallback = fn;
|
|
69
|
+
return this;
|
|
70
|
+
},
|
|
71
|
+
_skipIf(cwd: string) {
|
|
72
|
+
return skipIfCallback ? skipIfCallback(cwd) : false;
|
|
73
|
+
},
|
|
74
|
+
prepare(options: UpdateActionOptions): UpdateActionResolved {
|
|
75
|
+
const preparedData = prepare?.({ params, targetPath, options }) as K;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
kind,
|
|
79
|
+
targetPath,
|
|
80
|
+
params,
|
|
81
|
+
preparedData,
|
|
82
|
+
apply(report: DependencyUpdateReport, reports: DependencyUpdateReport[], cwd: string) {
|
|
83
|
+
if (skipIfCallback?.(cwd)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
apply({
|
|
87
|
+
preparedData,
|
|
88
|
+
targetPath,
|
|
89
|
+
params,
|
|
90
|
+
options,
|
|
91
|
+
report,
|
|
92
|
+
reports,
|
|
93
|
+
cwd,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return action;
|
|
101
|
+
};
|
package/src/updater/index.ts
CHANGED