relacher 0.0.2 → 0.0.4-rc.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.
- package/LICENSE +7 -0
- package/README.md +1 -6
- package/lib-types/index.d.ts +1 -0
- package/lib-types/updater/builder.d.ts +56 -0
- package/lib-types/updater/changelog.d.ts +12 -4
- package/lib-types/updater/index.d.ts +1 -22
- package/lib-types/updater/json.d.ts +14 -7
- package/lib-types/updater/regex.d.ts +12 -3
- package/lib-types/updater/toml.d.ts +14 -7
- package/package.json +2 -1
- package/src/builder/cargo.ts +193 -112
- package/src/builder/pnpm.ts +37 -8
- package/src/index.ts +1 -0
- package/src/print.ts +1 -1
- package/src/run.ts +1 -1
- package/src/updater/builder.ts +83 -0
- package/src/updater/changelog.ts +35 -37
- package/src/updater/index.ts +1 -26
- package/src/updater/json.ts +37 -71
- package/src/updater/regex.ts +32 -23
- package/src/updater/toml.ts +34 -42
- package/src/vcs/jj.ts +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Emilien Jegou
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -6,12 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**Relacher** is a scriptable, workspace-aware release orchestration library designed for monorepos.
|
|
8
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
|
-
```
|
|
9
|
+

|
|
15
10
|
|
|
16
11
|
## Features
|
|
17
12
|
|
package/lib-types/index.d.ts
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Commit, DependencyUpdateReport } from '../types';
|
|
2
|
+
export type UpdateActionOptions = {
|
|
3
|
+
newVersion: string;
|
|
4
|
+
globalCommits: Commit[];
|
|
5
|
+
crateCommits: Commit[];
|
|
6
|
+
};
|
|
7
|
+
export interface UpdateActionResolved {
|
|
8
|
+
kind: string;
|
|
9
|
+
targetPath: string;
|
|
10
|
+
apply(report: DependencyUpdateReport, reports: DependencyUpdateReport[], cwd: string): void;
|
|
11
|
+
params: any;
|
|
12
|
+
preparedData: any;
|
|
13
|
+
}
|
|
14
|
+
export interface UpdateAction {
|
|
15
|
+
kind: string;
|
|
16
|
+
path: string;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
params: any;
|
|
19
|
+
prepare(data: UpdateActionOptions): UpdateActionResolved;
|
|
20
|
+
}
|
|
21
|
+
export interface VersionFallback {
|
|
22
|
+
readFallback(cwd: string): string | null;
|
|
23
|
+
}
|
|
24
|
+
export type ApplyActionFnArgs<T, K = undefined> = {
|
|
25
|
+
params: T;
|
|
26
|
+
targetPath: string;
|
|
27
|
+
options: UpdateActionOptions;
|
|
28
|
+
report: DependencyUpdateReport;
|
|
29
|
+
reports: DependencyUpdateReport[];
|
|
30
|
+
cwd: string;
|
|
31
|
+
preparedData: K;
|
|
32
|
+
};
|
|
33
|
+
type ApplyActionFn<T, K = undefined> = (args: ApplyActionFnArgs<T, K>) => void;
|
|
34
|
+
export type PrepareActionFnArgs<T> = {
|
|
35
|
+
params: T;
|
|
36
|
+
targetPath: string;
|
|
37
|
+
options: UpdateActionOptions;
|
|
38
|
+
};
|
|
39
|
+
type PrepareActionFn<T, K> = (args: PrepareActionFnArgs<T>) => K;
|
|
40
|
+
type UpdateBuilderParams<T, K = undefined> = {
|
|
41
|
+
kind: string;
|
|
42
|
+
apply: ApplyActionFn<T, K>;
|
|
43
|
+
prepare?: PrepareActionFn<T, K>;
|
|
44
|
+
};
|
|
45
|
+
export declare const updateBuilder: <T, K = undefined>({ kind, apply, prepare }: UpdateBuilderParams<T, K>) => (targetPath: string, params: T & {
|
|
46
|
+
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
|
+
};
|
|
56
|
+
export {};
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { UpdateAction } from '.';
|
|
1
|
+
import { type ChangelogContext } from '../types';
|
|
3
2
|
export type ChangelogUpdateParams = {
|
|
4
|
-
path: string;
|
|
5
3
|
global?: boolean;
|
|
6
4
|
template?: (ctx: ChangelogContext) => string;
|
|
7
5
|
required?: boolean;
|
|
8
6
|
};
|
|
9
|
-
export declare const changelogUpdate: (params: ChangelogUpdateParams
|
|
7
|
+
export declare const changelogUpdate: (targetPath: string, params: ChangelogUpdateParams & {
|
|
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
|
+
};
|
|
10
18
|
export declare function defaultChangelogTemplate({ version, date, commits }: ChangelogContext): string;
|
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
export * from './builder';
|
|
2
2
|
export * from './changelog';
|
|
3
3
|
export * from './json';
|
|
4
4
|
export * from './regex';
|
|
5
5
|
export * from './toml';
|
|
6
|
-
export type UpdateActionOptions = {
|
|
7
|
-
newVersion: string;
|
|
8
|
-
globalCommits: Commit[];
|
|
9
|
-
crateCommits: Commit[];
|
|
10
|
-
};
|
|
11
|
-
export interface UpdateActionResolved {
|
|
12
|
-
kind: string;
|
|
13
|
-
path: string;
|
|
14
|
-
apply(report: DependencyUpdateReport, reports: DependencyUpdateReport[], cwd: string): void;
|
|
15
|
-
params: any;
|
|
16
|
-
}
|
|
17
|
-
export interface UpdateAction {
|
|
18
|
-
kind: string;
|
|
19
|
-
path: string;
|
|
20
|
-
required?: boolean;
|
|
21
|
-
params: any;
|
|
22
|
-
prepare(data: UpdateActionOptions): UpdateActionResolved;
|
|
23
|
-
}
|
|
24
|
-
export interface VersionFallback {
|
|
25
|
-
readFallback(cwd: string): string | null;
|
|
26
|
-
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DependencyUpdateReport } from '../types';
|
|
2
|
+
import { type VersionFallback } from '.';
|
|
2
3
|
export type JsonFallbackParams = {
|
|
3
4
|
path: string;
|
|
4
|
-
|
|
5
|
+
read: (parsed: any) => string | null | undefined;
|
|
5
6
|
};
|
|
6
|
-
export type JsonUpdateParams =
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export type JsonUpdateParams = (parsed: any, report: DependencyUpdateReport, reports: DependencyUpdateReport[]) => void;
|
|
8
|
+
export declare const jsonFallback: (params: JsonFallbackParams) => VersionFallback;
|
|
9
|
+
export declare const jsonUpdate: (targetPath: string, params: JsonUpdateParams & {
|
|
9
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;
|
|
10
19
|
};
|
|
11
|
-
export declare const jsonFallback: (params: JsonFallbackParams) => VersionFallback;
|
|
12
|
-
export declare const jsonUpdate: (params: JsonUpdateParams) => UpdateAction;
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type VersionFallback } from '.';
|
|
2
2
|
export type RegexFallbackParams = {
|
|
3
3
|
path: string;
|
|
4
4
|
search: string;
|
|
5
5
|
};
|
|
6
6
|
export type RegexUpdateParams = {
|
|
7
|
-
path: string;
|
|
8
7
|
search: string;
|
|
9
8
|
replace: string;
|
|
10
9
|
required?: boolean;
|
|
11
10
|
};
|
|
12
11
|
export declare const regexFallback: (params: RegexFallbackParams) => VersionFallback;
|
|
13
|
-
export declare const regexUpdate: (params: RegexUpdateParams
|
|
12
|
+
export declare const regexUpdate: (targetPath: string, params: RegexUpdateParams & {
|
|
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
|
+
};
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DependencyUpdateReport } from '../types';
|
|
2
|
+
import { type VersionFallback } from '.';
|
|
2
3
|
export type TomlFallbackParams = {
|
|
3
4
|
path: string;
|
|
4
|
-
|
|
5
|
+
read: (parsed: any) => string | null | undefined;
|
|
5
6
|
};
|
|
6
|
-
export type TomlUpdateParams =
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export type TomlUpdateParams = (parsed: any, report: DependencyUpdateReport, reports: DependencyUpdateReport[]) => void;
|
|
8
|
+
export declare const tomlFallback: (params: TomlFallbackParams) => VersionFallback;
|
|
9
|
+
export declare const tomlUpdate: (targetPath: string, params: TomlUpdateParams & {
|
|
9
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;
|
|
10
19
|
};
|
|
11
|
-
export declare const tomlFallback: (params: TomlFallbackParams) => VersionFallback;
|
|
12
|
-
export declare const tomlUpdate: (params: TomlUpdateParams) => UpdateAction;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relacher",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4-rc.0",
|
|
4
4
|
"description": "Scriptable release orchestration library for monorepos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"oxfmt": "^0.19.0",
|
|
57
57
|
"oxlint": "^1.64.0",
|
|
58
58
|
"prettier": "^3.8.3",
|
|
59
|
+
"smol-toml": "^1.6.1",
|
|
59
60
|
"typescript": "^6.0.3"
|
|
60
61
|
}
|
|
61
62
|
}
|
package/src/builder/cargo.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { parse } from 'smol-toml';
|
|
5
|
+
|
|
4
6
|
import type { DependencyConfig } from '../types';
|
|
5
|
-
import {
|
|
7
|
+
import { tomlUpdate, tomlFallback } from '../updater';
|
|
6
8
|
|
|
7
9
|
import { decorateList, type DependencyList } from './shared';
|
|
8
10
|
|
|
@@ -16,19 +18,49 @@ export interface CargoBuilderOptions {
|
|
|
16
18
|
export function cargoProject(cwd: string): DependencyList {
|
|
17
19
|
const configs: DependencyConfig[] = [];
|
|
18
20
|
const rootCargo = path.join(cwd, 'Cargo.toml');
|
|
21
|
+
const lockCargo = path.join(cwd, 'Cargo.lock');
|
|
22
|
+
const hasLock = fs.existsSync(lockCargo);
|
|
19
23
|
|
|
20
24
|
if (fs.existsSync(rootCargo)) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
try {
|
|
26
|
+
const fileContent = fs.readFileSync(rootCargo, 'utf8');
|
|
27
|
+
const doc = parse(fileContent) as any;
|
|
28
|
+
const parsedName = doc.package?.name;
|
|
29
|
+
|
|
30
|
+
if (parsedName) {
|
|
31
|
+
const updates = [
|
|
32
|
+
tomlUpdate('Cargo.toml', (d, r) => {
|
|
33
|
+
if (d.package) d.package.version = r.newVersion;
|
|
34
|
+
}),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
if (hasLock) {
|
|
38
|
+
updates.push(
|
|
39
|
+
tomlUpdate('Cargo.lock', (d, r) => {
|
|
40
|
+
if (Array.isArray(d.package)) {
|
|
41
|
+
// Only update local packages (missing source/checksum attributes)
|
|
42
|
+
const pkg = d.package.find(
|
|
43
|
+
(p: any) => p.name === parsedName && !p.source && !p.checksum,
|
|
44
|
+
);
|
|
45
|
+
if (pkg) pkg.version = r.newVersion;
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
configs.push({
|
|
52
|
+
name: parsedName,
|
|
53
|
+
watch: ['.'],
|
|
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
|
|
32
64
|
}
|
|
33
65
|
}
|
|
34
66
|
|
|
@@ -41,118 +73,162 @@ export function cargoProject(cwd: string): DependencyList {
|
|
|
41
73
|
export function cargoWorkspace(cwd: string, options?: CargoBuilderOptions): DependencyList {
|
|
42
74
|
const configs: DependencyConfig[] = [];
|
|
43
75
|
const rootCargo = path.join(cwd, 'Cargo.toml');
|
|
76
|
+
const lockCargo = path.join(cwd, 'Cargo.lock');
|
|
77
|
+
const hasLock = fs.existsSync(lockCargo);
|
|
44
78
|
const discoveredPaths = new Set<string>();
|
|
45
79
|
|
|
46
80
|
if (fs.existsSync(rootCargo)) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
? fs
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
name: parsedName,
|
|
74
|
-
|
|
75
|
-
content: fileContent,
|
|
76
|
-
});
|
|
81
|
+
try {
|
|
82
|
+
const content = fs.readFileSync(rootCargo, 'utf8');
|
|
83
|
+
const rootDoc = parse(content) as any;
|
|
84
|
+
const members = rootDoc.workspace?.members;
|
|
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
|
+
}
|
|
77
109
|
}
|
|
78
110
|
}
|
|
79
111
|
}
|
|
80
|
-
}
|
|
81
112
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
}
|
|
96
140
|
}
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
97
143
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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);
|
|
114
162
|
}
|
|
115
163
|
}
|
|
116
|
-
}
|
|
117
|
-
i++;
|
|
118
|
-
}
|
|
119
164
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
);
|
|
133
201
|
}
|
|
134
|
-
}
|
|
135
202
|
|
|
136
|
-
|
|
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
|
+
}
|
|
137
217
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
218
|
+
configs.push({
|
|
219
|
+
name: info.name,
|
|
220
|
+
watch: [posixRelativePath || '.'],
|
|
221
|
+
depends,
|
|
222
|
+
versionFallback: tomlFallback({
|
|
223
|
+
path: relativeCargo,
|
|
224
|
+
read: (doc) => doc.package?.version,
|
|
144
225
|
}),
|
|
145
|
-
|
|
226
|
+
updates,
|
|
227
|
+
});
|
|
146
228
|
}
|
|
147
|
-
|
|
148
|
-
configs.push({
|
|
149
|
-
name: info.name,
|
|
150
|
-
watch: [posixRelativePath || '.'],
|
|
151
|
-
depends,
|
|
152
|
-
versionFallback: tomlFallback({ path: relativeCargo, toml: 'package.version' }),
|
|
153
|
-
updates,
|
|
154
|
-
});
|
|
155
229
|
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Ignore parse error
|
|
156
232
|
}
|
|
157
233
|
}
|
|
158
234
|
|
|
@@ -162,12 +238,17 @@ export function cargoWorkspace(cwd: string, options?: CargoBuilderOptions): Depe
|
|
|
162
238
|
export function cargoDeps(cwd: string, options?: CargoBuilderOptions): DependencyList {
|
|
163
239
|
const rootCargo = path.join(cwd, 'Cargo.toml');
|
|
164
240
|
if (fs.existsSync(rootCargo)) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
241
|
+
try {
|
|
242
|
+
const content = fs.readFileSync(rootCargo, 'utf8');
|
|
243
|
+
const doc = parse(content) as any;
|
|
244
|
+
if (doc.workspace?.members) {
|
|
245
|
+
return cargoWorkspace(cwd, options);
|
|
246
|
+
}
|
|
247
|
+
if (doc.package?.name) {
|
|
248
|
+
return cargoProject(cwd);
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
// Fallback below
|
|
171
252
|
}
|
|
172
253
|
}
|
|
173
254
|
return decorateList([]);
|
package/src/builder/pnpm.ts
CHANGED
|
@@ -22,11 +22,13 @@ export function pnpmProject(cwd: string): DependencyList {
|
|
|
22
22
|
name: parsed.name,
|
|
23
23
|
watch: ['.'],
|
|
24
24
|
depends: [],
|
|
25
|
-
versionFallback: jsonFallback({
|
|
25
|
+
versionFallback: jsonFallback({
|
|
26
|
+
path: 'package.json',
|
|
27
|
+
read: (parsed) => parsed.version,
|
|
28
|
+
}),
|
|
26
29
|
updates: [
|
|
27
|
-
jsonUpdate({
|
|
28
|
-
|
|
29
|
-
json: 'version',
|
|
30
|
+
jsonUpdate('package.json', (parsed, report) => {
|
|
31
|
+
parsed.version = report.newVersion;
|
|
30
32
|
}),
|
|
31
33
|
],
|
|
32
34
|
});
|
|
@@ -139,11 +141,38 @@ export function pnpmWorkspace(cwd: string): DependencyList {
|
|
|
139
141
|
name: info.name,
|
|
140
142
|
watch: [posixRelativePath || '.'],
|
|
141
143
|
depends,
|
|
142
|
-
versionFallback: jsonFallback({
|
|
144
|
+
versionFallback: jsonFallback({
|
|
145
|
+
path: relativePJson,
|
|
146
|
+
read: (parsed) => parsed.version,
|
|
147
|
+
}),
|
|
143
148
|
updates: [
|
|
144
|
-
jsonUpdate({
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
jsonUpdate(relativePJson, (parsed, report, reports) => {
|
|
150
|
+
// Update local package version
|
|
151
|
+
parsed.version = report.newVersion;
|
|
152
|
+
|
|
153
|
+
// Sync internal workspace dependencies and preserve prefixing if present
|
|
154
|
+
const depSections = [
|
|
155
|
+
'dependencies',
|
|
156
|
+
'devDependencies',
|
|
157
|
+
'peerDependencies',
|
|
158
|
+
'optionalDependencies',
|
|
159
|
+
] as const;
|
|
160
|
+
|
|
161
|
+
for (const depReport of reports) {
|
|
162
|
+
if (depReport.name === report.name) continue;
|
|
163
|
+
|
|
164
|
+
for (const section of depSections) {
|
|
165
|
+
if (parsed[section] && parsed[section][depReport.name]) {
|
|
166
|
+
const currentVal = parsed[section][depReport.name];
|
|
167
|
+
if (typeof currentVal === 'string') {
|
|
168
|
+
// Match prefix structures such as '^', '~', 'workspace:^', or 'workspace:'
|
|
169
|
+
const match = currentVal.match(/^((?:workspace:)?([~^]?))/);
|
|
170
|
+
const prefix = match ? match[1] : '';
|
|
171
|
+
parsed[section][depReport.name] = `${prefix}${depReport.newVersion}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
147
176
|
}),
|
|
148
177
|
],
|
|
149
178
|
});
|
package/src/index.ts
CHANGED
package/src/print.ts
CHANGED
|
@@ -74,7 +74,7 @@ export function prettyPrint(reports: DependencyUpdateReport[]): void {
|
|
|
74
74
|
|
|
75
75
|
const files = report.updates.map(
|
|
76
76
|
(u) =>
|
|
77
|
-
`${getIconForFile(u.
|
|
77
|
+
`${getIconForFile(u.targetPath.split('/').pop() || '')} ${c.dim}${u.targetPath.split('/').pop()}${c.reset}`,
|
|
78
78
|
);
|
|
79
79
|
console.log(` ${files.join(' ')}`);
|
|
80
80
|
|
package/src/run.ts
CHANGED
|
@@ -63,7 +63,7 @@ export async function run(
|
|
|
63
63
|
// 1. Apply file updates
|
|
64
64
|
for (const report of reports) {
|
|
65
65
|
for (const u of report.updates) {
|
|
66
|
-
const filePath = path.resolve(cwd, u.
|
|
66
|
+
const filePath = path.resolve(cwd, u.targetPath);
|
|
67
67
|
const dirPath = path.dirname(filePath);
|
|
68
68
|
if (!fs.existsSync(dirPath)) {
|
|
69
69
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Commit, DependencyUpdateReport } from '../types';
|
|
2
|
+
|
|
3
|
+
export type UpdateActionOptions = {
|
|
4
|
+
newVersion: string;
|
|
5
|
+
globalCommits: Commit[];
|
|
6
|
+
crateCommits: Commit[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export interface UpdateActionResolved {
|
|
10
|
+
kind: string;
|
|
11
|
+
targetPath: string;
|
|
12
|
+
apply(report: DependencyUpdateReport, reports: DependencyUpdateReport[], cwd: string): void;
|
|
13
|
+
params: any;
|
|
14
|
+
preparedData: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UpdateAction {
|
|
18
|
+
kind: string;
|
|
19
|
+
path: string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
params: any;
|
|
22
|
+
prepare(data: UpdateActionOptions): UpdateActionResolved;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface VersionFallback {
|
|
26
|
+
readFallback(cwd: string): string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ApplyActionFnArgs<T, K = undefined> = {
|
|
30
|
+
params: T;
|
|
31
|
+
targetPath: string;
|
|
32
|
+
options: UpdateActionOptions;
|
|
33
|
+
report: DependencyUpdateReport;
|
|
34
|
+
reports: DependencyUpdateReport[];
|
|
35
|
+
cwd: string;
|
|
36
|
+
preparedData: K; // Added preparedData to the arguments
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type ApplyActionFn<T, K = undefined> = (args: ApplyActionFnArgs<T, K>) => void;
|
|
40
|
+
|
|
41
|
+
export type PrepareActionFnArgs<T> = {
|
|
42
|
+
params: T;
|
|
43
|
+
targetPath: string;
|
|
44
|
+
options: UpdateActionOptions;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type PrepareActionFn<T, K> = (args: PrepareActionFnArgs<T>) => K;
|
|
48
|
+
|
|
49
|
+
type UpdateBuilderParams<T, K = undefined> = {
|
|
50
|
+
kind: string;
|
|
51
|
+
apply: ApplyActionFn<T, K>; // Passed K here
|
|
52
|
+
prepare?: PrepareActionFn<T, K>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const updateBuilder =
|
|
56
|
+
<T, K = undefined>({ kind, apply, prepare }: UpdateBuilderParams<T, K>) =>
|
|
57
|
+
(targetPath: string, params: T & { required?: boolean }) => ({
|
|
58
|
+
kind,
|
|
59
|
+
path: targetPath,
|
|
60
|
+
params,
|
|
61
|
+
required: params.required,
|
|
62
|
+
prepare(options: UpdateActionOptions): UpdateActionResolved {
|
|
63
|
+
const preparedData = prepare?.({ params, targetPath, options }) as K;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
kind,
|
|
67
|
+
targetPath,
|
|
68
|
+
params,
|
|
69
|
+
preparedData,
|
|
70
|
+
apply(report: DependencyUpdateReport, reports: DependencyUpdateReport[], cwd: string) {
|
|
71
|
+
apply({
|
|
72
|
+
preparedData,
|
|
73
|
+
targetPath,
|
|
74
|
+
params,
|
|
75
|
+
options,
|
|
76
|
+
report,
|
|
77
|
+
reports,
|
|
78
|
+
cwd,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
package/src/updater/changelog.ts
CHANGED
|
@@ -1,50 +1,46 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import type
|
|
4
|
+
import { type ChangelogContext, type Commit } from '../types'; // Assuming these types are defined here
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { updateBuilder, type PrepareActionFnArgs, type ApplyActionFnArgs } from './builder';
|
|
7
7
|
|
|
8
8
|
export type ChangelogUpdateParams = {
|
|
9
|
-
path: string;
|
|
10
9
|
global?: boolean;
|
|
11
10
|
template?: (ctx: ChangelogContext) => string;
|
|
12
11
|
required?: boolean;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
// Define the type for data prepared by the 'prepare' function
|
|
15
|
+
type ChangelogPreparedData = {
|
|
16
|
+
resolvedBlock: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const changelogUpdate = updateBuilder<ChangelogUpdateParams, ChangelogPreparedData>({
|
|
16
20
|
kind: 'changelog',
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
params,
|
|
20
|
-
prepare(data: UpdateActionOptions): UpdateActionResolved {
|
|
21
|
-
const targetCommits = params.global ? data.globalCommits : data.crateCommits;
|
|
21
|
+
prepare: ({ params, options }: PrepareActionFnArgs<ChangelogUpdateParams>) => {
|
|
22
|
+
const targetCommits = params.global ? options.globalCommits : options.crateCommits;
|
|
22
23
|
const context: ChangelogContext = {
|
|
23
|
-
version:
|
|
24
|
+
version: options.newVersion,
|
|
24
25
|
date: new Date().toISOString().split('T')[0] ?? '',
|
|
25
|
-
commits: targetCommits,
|
|
26
|
+
commits: targetCommits || [],
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
const resolvedBlock = params.template
|
|
29
30
|
? params.template(context)
|
|
30
31
|
: defaultChangelogTemplate(context);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
fs.writeFileSync(filePath, newContent.trim() + '\n');
|
|
44
|
-
},
|
|
45
|
-
} satisfies UpdateActionResolved;
|
|
46
|
-
|
|
47
|
-
return t;
|
|
33
|
+
return { resolvedBlock };
|
|
34
|
+
},
|
|
35
|
+
apply: ({
|
|
36
|
+
targetPath,
|
|
37
|
+
preparedData,
|
|
38
|
+
cwd,
|
|
39
|
+
}: ApplyActionFnArgs<ChangelogUpdateParams, ChangelogPreparedData>) => {
|
|
40
|
+
const filePath = path.resolve(cwd, targetPath);
|
|
41
|
+
const oldContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
42
|
+
const newContent = (preparedData.resolvedBlock ?? '') + '\n' + oldContent;
|
|
43
|
+
fs.writeFileSync(filePath, newContent.trim() + '\n');
|
|
48
44
|
},
|
|
49
45
|
});
|
|
50
46
|
|
|
@@ -52,7 +48,7 @@ export function defaultChangelogTemplate({ version, date, commits }: ChangelogCo
|
|
|
52
48
|
let block = `## [${version}] - ${date}\n\n`;
|
|
53
49
|
if (commits.length === 0) return block + `*No notable changes.*\n`;
|
|
54
50
|
|
|
55
|
-
const groups: { breaking:
|
|
51
|
+
const groups: { breaking: Commit[]; feat: Commit[]; fix: Commit[]; other: Commit[] } = {
|
|
56
52
|
breaking: [],
|
|
57
53
|
feat: [],
|
|
58
54
|
fix: [],
|
|
@@ -60,17 +56,19 @@ export function defaultChangelogTemplate({ version, date, commits }: ChangelogCo
|
|
|
60
56
|
};
|
|
61
57
|
|
|
62
58
|
for (const c of commits) {
|
|
63
|
-
|
|
64
|
-
if (c.
|
|
65
|
-
else if (c.type === '
|
|
66
|
-
else
|
|
67
|
-
else groups.other.push(item);
|
|
59
|
+
if (c.isBreaking) groups.breaking.push(c);
|
|
60
|
+
else if (c.type === 'feat') groups.feat.push(c);
|
|
61
|
+
else if (c.type === 'fix') groups.fix.push(c);
|
|
62
|
+
else groups.other.push(c);
|
|
68
63
|
}
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (groups.
|
|
73
|
-
|
|
65
|
+
const formatGroup = (arr: Commit[]) => arr.map((c) => `- ${c.shortHash} ${c.message}`).join('\n');
|
|
66
|
+
|
|
67
|
+
if (groups.breaking.length)
|
|
68
|
+
block += `### ⚠️ BREAKING CHANGES\n${formatGroup(groups.breaking)}\n\n`;
|
|
69
|
+
if (groups.feat.length) block += `### ✨ Features\n${formatGroup(groups.feat)}\n\n`;
|
|
70
|
+
if (groups.fix.length) block += `### 🐛 Bug Fixes\n${formatGroup(groups.fix)}\n\n`;
|
|
71
|
+
if (groups.other.length) block += `### 🛠 Other Changes\n${formatGroup(groups.other)}\n\n`;
|
|
74
72
|
|
|
75
73
|
return block.trim() + '\n';
|
|
76
74
|
}
|
package/src/updater/index.ts
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export * from './builder';
|
|
3
2
|
export * from './changelog';
|
|
4
3
|
export * from './json';
|
|
5
4
|
export * from './regex';
|
|
6
5
|
export * from './toml';
|
|
7
6
|
|
|
8
|
-
export type UpdateActionOptions = {
|
|
9
|
-
newVersion: string;
|
|
10
|
-
globalCommits: Commit[];
|
|
11
|
-
crateCommits: Commit[];
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export interface UpdateActionResolved {
|
|
15
|
-
kind: string;
|
|
16
|
-
path: string;
|
|
17
|
-
apply(report: DependencyUpdateReport, reports: DependencyUpdateReport[], cwd: string): void;
|
|
18
|
-
params: any;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface UpdateAction {
|
|
22
|
-
kind: string;
|
|
23
|
-
path: string;
|
|
24
|
-
required?: boolean;
|
|
25
|
-
params: any;
|
|
26
|
-
prepare(data: UpdateActionOptions): UpdateActionResolved;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface VersionFallback {
|
|
30
|
-
readFallback(cwd: string): string | null;
|
|
31
|
-
}
|
package/src/updater/json.ts
CHANGED
|
@@ -1,95 +1,61 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { DependencyUpdateReport } from '../types';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { type VersionFallback, updateBuilder } from '.';
|
|
7
|
+
|
|
8
|
+
export type JsonFallbackParams = {
|
|
9
|
+
path: string;
|
|
10
|
+
read: (parsed: any) => string | null | undefined;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type JsonUpdateParams = (
|
|
14
|
+
parsed: any,
|
|
15
|
+
report: DependencyUpdateReport,
|
|
16
|
+
reports: DependencyUpdateReport[],
|
|
17
|
+
) => void;
|
|
8
18
|
|
|
9
19
|
export const jsonFallback = (params: JsonFallbackParams): VersionFallback => ({
|
|
10
20
|
readFallback(cwd: string): string | null {
|
|
11
21
|
const filePath = path.resolve(cwd, params.path);
|
|
12
22
|
if (!fs.existsSync(filePath)) return null;
|
|
13
23
|
try {
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
for (const k of keys) {
|
|
18
|
-
if (val == null) return null;
|
|
19
|
-
val = val[k];
|
|
20
|
-
}
|
|
21
|
-
return typeof val === 'string' ? val : null;
|
|
24
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
25
|
+
const parsed = JSON.parse(content);
|
|
26
|
+
return params.read(parsed) ?? null;
|
|
22
27
|
} catch {
|
|
23
28
|
return null;
|
|
24
29
|
}
|
|
25
30
|
},
|
|
26
31
|
});
|
|
27
32
|
|
|
28
|
-
export const jsonUpdate =
|
|
33
|
+
export const jsonUpdate = updateBuilder<JsonUpdateParams>({
|
|
29
34
|
kind: 'json',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
prepare(_data: UpdateActionOptions): UpdateActionResolved {
|
|
34
|
-
return {
|
|
35
|
-
kind: 'json',
|
|
36
|
-
path: params.path,
|
|
37
|
-
params,
|
|
38
|
-
apply(report, reports, cwd) {
|
|
39
|
-
const filePath = path.resolve(cwd, params.path);
|
|
40
|
-
if (!fs.existsSync(filePath)) return;
|
|
41
|
-
|
|
42
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
43
|
-
|
|
44
|
-
// Detect indent size
|
|
45
|
-
const indentMatch = content.match(/^[ \t]+/m);
|
|
46
|
-
const indent = indentMatch ? indentMatch[0] : 2;
|
|
35
|
+
apply({ targetPath, params, report, reports, cwd }) {
|
|
36
|
+
const filePath = path.resolve(cwd, targetPath);
|
|
37
|
+
if (!fs.existsSync(filePath)) return;
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Update specific JSON path
|
|
57
|
-
const keys = params.json.split('.');
|
|
58
|
-
const lastKey = keys.pop();
|
|
59
|
-
if (lastKey) {
|
|
60
|
-
let target = data;
|
|
61
|
-
for (const k of keys) {
|
|
62
|
-
if (target[k] == null || typeof target[k] !== 'object') {
|
|
63
|
-
target[k] = {};
|
|
64
|
-
}
|
|
65
|
-
target = target[k];
|
|
66
|
-
}
|
|
67
|
-
target[lastKey] = report.newVersion;
|
|
68
|
-
}
|
|
39
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
40
|
+
let parsed: any;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(content);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(`Failed to parse JSON file at ${filePath}:`, err);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
69
47
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
'devDependencies',
|
|
74
|
-
'peerDependencies',
|
|
75
|
-
'optionalDependencies',
|
|
76
|
-
];
|
|
48
|
+
// Detect indent size
|
|
49
|
+
const indentMatch = content.match(/^[ \t]+/m);
|
|
50
|
+
const indent = indentMatch ? indentMatch[0] : 2;
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
52
|
+
// Mutate the object using the provided callback
|
|
53
|
+
params(parsed, report, reports);
|
|
80
54
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const prefixMatch = currentVal.match(/^([~^]?)(.*)$/);
|
|
85
|
-
const prefix = prefixMatch ? prefixMatch[1] : '';
|
|
86
|
-
data[section][depReport.name] = `${prefix}${depReport.newVersion}`;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
55
|
+
// Keep trailing newline if it originally existed
|
|
56
|
+
const hasTrailingNewline = content.endsWith('\n');
|
|
57
|
+
const updatedContent = JSON.stringify(parsed, null, indent) + (hasTrailingNewline ? '\n' : '');
|
|
90
58
|
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
};
|
|
59
|
+
fs.writeFileSync(filePath, updatedContent);
|
|
94
60
|
},
|
|
95
61
|
});
|
package/src/updater/regex.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { updateBuilder, type PrepareActionFnArgs, type ApplyActionFnArgs } from './builder';
|
|
5
|
+
|
|
6
|
+
import { type VersionFallback } from '.';
|
|
5
7
|
|
|
6
8
|
export type RegexFallbackParams = { path: string; search: string };
|
|
7
9
|
export type RegexUpdateParams = {
|
|
8
|
-
path: string;
|
|
9
10
|
search: string;
|
|
10
11
|
replace: string;
|
|
11
12
|
required?: boolean;
|
|
@@ -22,27 +23,35 @@ export const regexFallback = (params: RegexFallbackParams): VersionFallback => (
|
|
|
22
23
|
},
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
// Define the type for data prepared by the 'prepare' function
|
|
27
|
+
type RegexPreparedData = {
|
|
28
|
+
resolvedReplace: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const regexUpdate = updateBuilder<RegexUpdateParams, RegexPreparedData>({
|
|
26
32
|
kind: 'regex',
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
|
|
34
|
+
prepare: ({ params, options }: PrepareActionFnArgs<RegexUpdateParams>) => {
|
|
35
|
+
// Pre-calculate the replacement string with the new version
|
|
36
|
+
const resolvedReplace = params.replace.replace('{{version}}', options.newVersion);
|
|
37
|
+
return { resolvedReplace };
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
apply: ({
|
|
41
|
+
targetPath,
|
|
42
|
+
params,
|
|
43
|
+
preparedData,
|
|
44
|
+
cwd,
|
|
45
|
+
}: ApplyActionFnArgs<RegexUpdateParams, RegexPreparedData>) => {
|
|
46
|
+
const filePath = path.resolve(cwd, targetPath);
|
|
47
|
+
if (!fs.existsSync(filePath)) {
|
|
48
|
+
console.warn(`File not found for regex update (path: ${filePath}). Skipping.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
53
|
+
const searchRegex = new RegExp(params.search, 'g');
|
|
54
|
+
content = content.replace(searchRegex, preparedData.resolvedReplace);
|
|
55
|
+
fs.writeFileSync(filePath, content);
|
|
47
56
|
},
|
|
48
57
|
});
|
package/src/updater/toml.ts
CHANGED
|
@@ -1,57 +1,49 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { parse, stringify } from 'smol-toml';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import type { DependencyUpdateReport } from '../types';
|
|
7
|
+
|
|
8
|
+
import { type VersionFallback, updateBuilder } from '.';
|
|
9
|
+
|
|
10
|
+
export type TomlFallbackParams = {
|
|
11
|
+
path: string;
|
|
12
|
+
read: (parsed: any) => string | null | undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TomlUpdateParams = (
|
|
16
|
+
parsed: any,
|
|
17
|
+
report: DependencyUpdateReport,
|
|
18
|
+
reports: DependencyUpdateReport[],
|
|
19
|
+
) => void;
|
|
8
20
|
|
|
9
21
|
export const tomlFallback = (params: TomlFallbackParams): VersionFallback => ({
|
|
10
22
|
readFallback(cwd: string): string | null {
|
|
11
23
|
const filePath = path.resolve(cwd, params.path);
|
|
12
24
|
if (!fs.existsSync(filePath)) return null;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
try {
|
|
26
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
27
|
+
const parsed = parse(content);
|
|
28
|
+
return params.read(parsed) ?? null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
18
32
|
},
|
|
19
33
|
});
|
|
20
34
|
|
|
21
|
-
export const tomlUpdate =
|
|
35
|
+
export const tomlUpdate = updateBuilder<TomlUpdateParams>({
|
|
22
36
|
kind: 'toml',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
36
|
-
|
|
37
|
-
// Update package version inside the target Cargo.toml package block
|
|
38
|
-
content = content.replace(
|
|
39
|
-
/(\[package\][^]*?^version\s*=\s*")[^"]+(")/m,
|
|
40
|
-
`$1${report.newVersion}$2`,
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// Sync workspace dependency entries if internal dependencies changed
|
|
44
|
-
for (const depReport of reports) {
|
|
45
|
-
if (depReport.name === report.name) continue;
|
|
46
|
-
const depRegex = new RegExp(
|
|
47
|
-
`(${depReport.name}\\s*=\\s*\\{[^}]*version\\s*=\\s*")[^"]+(")`,
|
|
48
|
-
'g',
|
|
49
|
-
);
|
|
50
|
-
content = content.replace(depRegex, `$1${depReport.newVersion}$2`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
fs.writeFileSync(filePath, content);
|
|
54
|
-
},
|
|
55
|
-
};
|
|
37
|
+
apply({ targetPath, params, report, reports, cwd }) {
|
|
38
|
+
const filePath = path.resolve(cwd, targetPath);
|
|
39
|
+
if (!fs.existsSync(filePath)) return;
|
|
40
|
+
|
|
41
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
42
|
+
const parsed = parse(content);
|
|
43
|
+
|
|
44
|
+
// Safely mutate the object using the user provided callback
|
|
45
|
+
params(parsed, report, reports);
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(filePath, stringify(parsed));
|
|
56
48
|
},
|
|
57
49
|
});
|
package/src/vcs/jj.ts
CHANGED