relacher 0.0.1 → 0.0.3
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 +12 -16
- package/lib-types/builder/cargo.d.ts +6 -6
- package/lib-types/builder/shared.d.ts +2 -3
- package/lib-types/default-data.d.ts +3 -0
- package/lib-types/index.d.ts +1 -0
- package/lib-types/prepare.d.ts +7 -6
- package/lib-types/run.d.ts +5 -0
- package/lib-types/src/builder/cargo.d.ts +13 -0
- package/lib-types/src/builder/index.d.ts +3 -0
- package/lib-types/src/builder/pnpm.d.ts +13 -0
- package/lib-types/src/builder/shared.d.ts +11 -0
- package/lib-types/src/default-data.d.ts +3 -0
- package/lib-types/src/index.d.ts +6 -0
- package/lib-types/src/prepare.d.ts +15 -0
- package/lib-types/src/print.d.ts +2 -0
- package/lib-types/src/run.d.ts +12 -0
- package/lib-types/src/types.d.ts +71 -0
- package/lib-types/src/updater/changelog.d.ts +10 -0
- package/lib-types/src/updater/index.d.ts +26 -0
- package/lib-types/src/updater/json.d.ts +12 -0
- package/lib-types/src/updater/regex.d.ts +13 -0
- package/lib-types/src/updater/toml.d.ts +12 -0
- package/lib-types/src/vcs/git.d.ts +15 -0
- package/lib-types/src/vcs/index.d.ts +9 -0
- package/lib-types/src/vcs/jj.d.ts +15 -0
- package/lib-types/types.d.ts +9 -25
- package/lib-types/updater/changelog.d.ts +10 -0
- package/lib-types/updater/index.d.ts +26 -0
- package/lib-types/updater/json.d.ts +12 -0
- package/lib-types/updater/regex.d.ts +13 -0
- package/lib-types/updater/toml.d.ts +12 -0
- package/lib-types/vcs/git.d.ts +1 -0
- package/lib-types/vcs/index.d.ts +1 -0
- package/lib-types/vcs/jj.d.ts +1 -0
- package/package.json +2 -2
- package/src/builder/cargo.ts +81 -18
- package/src/builder/pnpm.ts +14 -12
- package/src/builder/shared.ts +2 -9
- package/src/default-data.ts +15 -0
- package/src/index.ts +1 -0
- package/src/prepare.ts +68 -108
- package/src/print.ts +9 -4
- package/src/run.ts +50 -56
- package/src/types.ts +14 -46
- package/src/updater/changelog.ts +76 -0
- package/src/updater/index.ts +31 -0
- package/src/updater/json.ts +95 -0
- package/src/updater/regex.ts +48 -0
- package/src/updater/toml.ts +57 -0
- package/src/vcs/git.ts +24 -3
- package/src/vcs/index.ts +1 -0
- package/src/vcs/jj.ts +23 -6
package/src/types.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import type { UpdateAction, UpdateActionResolved, VersionFallback } from './updater';
|
|
1
2
|
import type { VcsProvider } from './vcs';
|
|
2
3
|
|
|
3
|
-
export type BumpSize =
|
|
4
|
+
export type BumpSize = 'major' | 'minor' | 'patch' | 'skip';
|
|
4
5
|
|
|
5
6
|
export interface Commit {
|
|
6
7
|
hash: string;
|
|
7
8
|
shortHash: string;
|
|
8
9
|
author: string;
|
|
9
10
|
date: string;
|
|
10
|
-
message: string;
|
|
11
|
-
type: string;
|
|
12
|
-
scope: string | null;
|
|
13
|
-
isBreaking: boolean;
|
|
14
|
-
description: string;
|
|
11
|
+
message: string; // The full commit message
|
|
12
|
+
type: string; // e.g., 'feat', 'fix', 'chore'
|
|
13
|
+
scope: string | null; // e.g., 'ui', 'api'
|
|
14
|
+
isBreaking: boolean; // true if contains '!' or 'BREAKING CHANGE'
|
|
15
|
+
description: string; // The text after the colon
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface ChangelogContext {
|
|
@@ -20,34 +21,11 @@ export interface ChangelogContext {
|
|
|
20
21
|
commits: Commit[];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export interface ChangelogUpdate {
|
|
24
|
-
kind: "changelog";
|
|
25
|
-
path: string;
|
|
26
|
-
global?: boolean;
|
|
27
|
-
template?: (ctx: ChangelogContext) => string;
|
|
28
|
-
resolvedBlock?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface TomlUpdate {
|
|
32
|
-
kind: "toml";
|
|
33
|
-
path: string;
|
|
34
|
-
toml: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface RegexUpdate {
|
|
38
|
-
kind: "regex";
|
|
39
|
-
path: string;
|
|
40
|
-
search: string;
|
|
41
|
-
replace: string;
|
|
42
|
-
resolvedReplace?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type UpdateAction = TomlUpdate | RegexUpdate | ChangelogUpdate;
|
|
46
|
-
|
|
47
24
|
export interface DependencyConfig {
|
|
48
25
|
name: string;
|
|
49
26
|
watch?: string[];
|
|
50
|
-
updates
|
|
27
|
+
updates: UpdateAction[];
|
|
28
|
+
versionFallback?: VersionFallback;
|
|
51
29
|
depends?: string[];
|
|
52
30
|
}
|
|
53
31
|
|
|
@@ -82,8 +60,10 @@ export interface DependencyUpdateReport {
|
|
|
82
60
|
bump: BumpSize;
|
|
83
61
|
originalBump?: BumpSize;
|
|
84
62
|
commits: Commit[];
|
|
85
|
-
updates:
|
|
63
|
+
updates: UpdateActionResolved[];
|
|
86
64
|
depends?: string[];
|
|
65
|
+
isErroneous?: boolean;
|
|
66
|
+
isFirstRelease?: boolean;
|
|
87
67
|
}
|
|
88
68
|
|
|
89
69
|
export interface IntermediateReport {
|
|
@@ -95,20 +75,8 @@ export interface IntermediateReport {
|
|
|
95
75
|
commits: Commit[];
|
|
96
76
|
updates: UpdateAction[];
|
|
97
77
|
depends: string[];
|
|
78
|
+
isErroneous?: boolean;
|
|
79
|
+
isFirstRelease?: boolean;
|
|
98
80
|
}
|
|
99
81
|
|
|
100
|
-
export const defaultSizes: SizePatterns = {
|
|
101
|
-
major: { pattern: "^[a-z]+(?:\\([^)]+\\))?!:|BREAKING CHANGE" },
|
|
102
|
-
minor: { pattern: "^feat|^revert" },
|
|
103
|
-
patch: { pattern: "^fix|^build|^refactor|^nit|^style" },
|
|
104
|
-
skip: { pattern: "^release|^chore|^infra|^docs|^test|^ci|^build" },
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export const defaultCascadeRules: Required<CascadeRules> = {
|
|
108
|
-
skip: { skip: 'skip', patch: 'patch', minor: 'patch', major: 'patch' },
|
|
109
|
-
patch: { skip: 'patch', patch: 'patch', minor: 'minor', major: 'minor' },
|
|
110
|
-
minor: { skip: 'minor', patch: 'minor', minor: 'minor', major: 'minor' },
|
|
111
|
-
major: { skip: 'major', patch: 'major', minor: 'major', major: 'major' },
|
|
112
|
-
};
|
|
113
|
-
|
|
114
82
|
export type { VcsProvider };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { ChangelogContext } from '../types';
|
|
5
|
+
|
|
6
|
+
import type { UpdateAction, UpdateActionResolved, UpdateActionOptions } from '.';
|
|
7
|
+
|
|
8
|
+
export type ChangelogUpdateParams = {
|
|
9
|
+
path: string;
|
|
10
|
+
global?: boolean;
|
|
11
|
+
template?: (ctx: ChangelogContext) => string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const changelogUpdate = (params: ChangelogUpdateParams): UpdateAction => ({
|
|
16
|
+
kind: 'changelog',
|
|
17
|
+
path: params.path,
|
|
18
|
+
required: params.required,
|
|
19
|
+
params,
|
|
20
|
+
prepare(data: UpdateActionOptions): UpdateActionResolved {
|
|
21
|
+
const targetCommits = params.global ? data.globalCommits : data.crateCommits;
|
|
22
|
+
const context: ChangelogContext = {
|
|
23
|
+
version: data.newVersion,
|
|
24
|
+
date: new Date().toISOString().split('T')[0] ?? '',
|
|
25
|
+
commits: targetCommits,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const resolvedBlock = params.template
|
|
29
|
+
? params.template(context)
|
|
30
|
+
: defaultChangelogTemplate(context);
|
|
31
|
+
|
|
32
|
+
const t = {
|
|
33
|
+
kind: 'changelog',
|
|
34
|
+
path: params.path,
|
|
35
|
+
params: {
|
|
36
|
+
...params,
|
|
37
|
+
resolvedBlock,
|
|
38
|
+
},
|
|
39
|
+
apply(_report, _reports, cwd) {
|
|
40
|
+
const filePath = path.resolve(cwd, params.path);
|
|
41
|
+
const oldContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
42
|
+
const newContent = (resolvedBlock ?? '') + '\n' + oldContent;
|
|
43
|
+
fs.writeFileSync(filePath, newContent.trim() + '\n');
|
|
44
|
+
},
|
|
45
|
+
} satisfies UpdateActionResolved;
|
|
46
|
+
|
|
47
|
+
return t;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export function defaultChangelogTemplate({ version, date, commits }: ChangelogContext): string {
|
|
52
|
+
let block = `## [${version}] - ${date}\n\n`;
|
|
53
|
+
if (commits.length === 0) return block + `*No notable changes.*\n`;
|
|
54
|
+
|
|
55
|
+
const groups: { breaking: string[]; feat: string[]; fix: string[]; other: string[] } = {
|
|
56
|
+
breaking: [],
|
|
57
|
+
feat: [],
|
|
58
|
+
fix: [],
|
|
59
|
+
other: [],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (const c of commits) {
|
|
63
|
+
const item = `- ${c.shortHash} ${c.message}`;
|
|
64
|
+
if (c.isBreaking) groups.breaking.push(item);
|
|
65
|
+
else if (c.type === 'feat') groups.feat.push(item);
|
|
66
|
+
else if (c.type === 'fix') groups.fix.push(item);
|
|
67
|
+
else groups.other.push(item);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (groups.breaking.length) block += `### ⚠️ BREAKING CHANGES\n${groups.breaking.join('\n')}\n\n`;
|
|
71
|
+
if (groups.feat.length) block += `### ✨ Features\n${groups.feat.join('\n')}\n\n`;
|
|
72
|
+
if (groups.fix.length) block += `### 🐛 Bug Fixes\n${groups.fix.join('\n')}\n\n`;
|
|
73
|
+
if (groups.other.length) block += `### 🛠 Other Changes\n${groups.other.join('\n')}\n\n`;
|
|
74
|
+
|
|
75
|
+
return block.trim() + '\n';
|
|
76
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Commit, DependencyUpdateReport } from '../types';
|
|
2
|
+
|
|
3
|
+
export * from './changelog';
|
|
4
|
+
export * from './json';
|
|
5
|
+
export * from './regex';
|
|
6
|
+
export * from './toml';
|
|
7
|
+
|
|
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
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { VersionFallback, UpdateAction, UpdateActionResolved, UpdateActionOptions } from '.';
|
|
5
|
+
|
|
6
|
+
export type JsonFallbackParams = { path: string; json: string };
|
|
7
|
+
export type JsonUpdateParams = { path: string; json: string; required?: boolean };
|
|
8
|
+
|
|
9
|
+
export const jsonFallback = (params: JsonFallbackParams): VersionFallback => ({
|
|
10
|
+
readFallback(cwd: string): string | null {
|
|
11
|
+
const filePath = path.resolve(cwd, params.path);
|
|
12
|
+
if (!fs.existsSync(filePath)) return null;
|
|
13
|
+
try {
|
|
14
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
15
|
+
const keys = params.json.split('.');
|
|
16
|
+
let val: any = data;
|
|
17
|
+
for (const k of keys) {
|
|
18
|
+
if (val == null) return null;
|
|
19
|
+
val = val[k];
|
|
20
|
+
}
|
|
21
|
+
return typeof val === 'string' ? val : null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const jsonUpdate = (params: JsonUpdateParams): UpdateAction => ({
|
|
29
|
+
kind: 'json',
|
|
30
|
+
path: params.path,
|
|
31
|
+
required: params.required,
|
|
32
|
+
params,
|
|
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;
|
|
47
|
+
|
|
48
|
+
let data: Record<string, any>;
|
|
49
|
+
try {
|
|
50
|
+
data = JSON.parse(content);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(`Failed to parse JSON file at ${filePath}`);
|
|
53
|
+
return;
|
|
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
|
+
}
|
|
69
|
+
|
|
70
|
+
// Sync internal workspace dependency entries
|
|
71
|
+
const depSections = [
|
|
72
|
+
'dependencies',
|
|
73
|
+
'devDependencies',
|
|
74
|
+
'peerDependencies',
|
|
75
|
+
'optionalDependencies',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
for (const depReport of reports) {
|
|
79
|
+
if (depReport.name === report.name) continue;
|
|
80
|
+
|
|
81
|
+
for (const section of depSections) {
|
|
82
|
+
if (data[section] && data[section][depReport.name]) {
|
|
83
|
+
const currentVal = data[section][depReport.name] as string;
|
|
84
|
+
const prefixMatch = currentVal.match(/^([~^]?)(.*)$/);
|
|
85
|
+
const prefix = prefixMatch ? prefixMatch[1] : '';
|
|
86
|
+
data[section][depReport.name] = `${prefix}${depReport.newVersion}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, indent) + '\n');
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { VersionFallback, UpdateAction, UpdateActionResolved, UpdateActionOptions } from '.';
|
|
5
|
+
|
|
6
|
+
export type RegexFallbackParams = { path: string; search: string };
|
|
7
|
+
export type RegexUpdateParams = {
|
|
8
|
+
path: string;
|
|
9
|
+
search: string;
|
|
10
|
+
replace: string;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const regexFallback = (params: RegexFallbackParams): VersionFallback => ({
|
|
15
|
+
readFallback(cwd: string): string | null {
|
|
16
|
+
const filePath = path.resolve(cwd, params.path);
|
|
17
|
+
if (!fs.existsSync(filePath)) return null;
|
|
18
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
19
|
+
const searchRegex = new RegExp(params.search, 'm');
|
|
20
|
+
const match = content.match(searchRegex);
|
|
21
|
+
return match && match[1] ? match[1] : null;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const regexUpdate = (params: RegexUpdateParams): UpdateAction => ({
|
|
26
|
+
kind: 'regex',
|
|
27
|
+
path: params.path,
|
|
28
|
+
required: params.required,
|
|
29
|
+
params,
|
|
30
|
+
prepare(data: UpdateActionOptions): UpdateActionResolved {
|
|
31
|
+
const resolvedReplace = params.replace.replace('{{version}}', data.newVersion);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
kind: 'regex',
|
|
35
|
+
path: params.path,
|
|
36
|
+
params: { ...params, resolvedReplace },
|
|
37
|
+
apply(_report, _reports, cwd) {
|
|
38
|
+
const filePath = path.resolve(cwd, params.path);
|
|
39
|
+
if (!fs.existsSync(filePath)) return;
|
|
40
|
+
|
|
41
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
42
|
+
const searchRegex = new RegExp(params.search, 'g');
|
|
43
|
+
content = content.replace(searchRegex, resolvedReplace);
|
|
44
|
+
fs.writeFileSync(filePath, content);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { VersionFallback, UpdateAction, UpdateActionResolved, UpdateActionOptions } from '.';
|
|
5
|
+
|
|
6
|
+
export type TomlFallbackParams = { path: string; toml: string };
|
|
7
|
+
export type TomlUpdateParams = { path: string; toml: string; required?: boolean };
|
|
8
|
+
|
|
9
|
+
export const tomlFallback = (params: TomlFallbackParams): VersionFallback => ({
|
|
10
|
+
readFallback(cwd: string): string | null {
|
|
11
|
+
const filePath = path.resolve(cwd, params.path);
|
|
12
|
+
if (!fs.existsSync(filePath)) return null;
|
|
13
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
14
|
+
const packageMatch = content.match(/\[package\][^]*?(?=^\[|z)/);
|
|
15
|
+
const targetText = packageMatch ? packageMatch[0] : content;
|
|
16
|
+
const versionMatch = targetText.match(/^version\s*=\s*"([^"]+)"/m);
|
|
17
|
+
return versionMatch && versionMatch[1] ? versionMatch[1] : null;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const tomlUpdate = (params: TomlUpdateParams): UpdateAction => ({
|
|
22
|
+
kind: 'toml',
|
|
23
|
+
path: params.path,
|
|
24
|
+
params,
|
|
25
|
+
required: params.required,
|
|
26
|
+
prepare(_data: UpdateActionOptions): UpdateActionResolved {
|
|
27
|
+
return {
|
|
28
|
+
kind: 'toml',
|
|
29
|
+
path: params.path,
|
|
30
|
+
params,
|
|
31
|
+
apply(report, reports, cwd) {
|
|
32
|
+
const filePath = path.resolve(cwd, params.path);
|
|
33
|
+
if (!fs.existsSync(filePath)) return;
|
|
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
|
+
};
|
|
56
|
+
},
|
|
57
|
+
});
|
package/src/vcs/git.ts
CHANGED
|
@@ -11,7 +11,11 @@ export function runGit(cmd: string, cwd: string): string {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function parseSemver(tag: string, prefix: string): number[] {
|
|
14
|
-
|
|
14
|
+
const versionStr = tag.slice(prefix.length);
|
|
15
|
+
return versionStr.split('.').map((part) => {
|
|
16
|
+
const val = Number.parseInt(part, 10);
|
|
17
|
+
return Number.isNaN(val) ? 0 : val;
|
|
18
|
+
});
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export function findLatestTag(tags: string[], prefix: string): string | null {
|
|
@@ -44,7 +48,6 @@ export function getGitCommits(
|
|
|
44
48
|
const watchPaths = watch.join(' ');
|
|
45
49
|
const range = lastTag ? `${lastTag}..HEAD` : 'HEAD';
|
|
46
50
|
|
|
47
|
-
// Using shell-safe single quotes and disabling signature lines to prevent parsing corruption
|
|
48
51
|
const gitCmd = watchPaths
|
|
49
52
|
? `git log ${range} --no-show-signature --format='%H|%an|%ad|%s' --date=short -- ${watchPaths}`
|
|
50
53
|
: `git log ${range} --no-show-signature --format='%H|%an|%ad|%s' --date=short`;
|
|
@@ -90,7 +93,7 @@ export class GitVcsProvider implements VcsProvider {
|
|
|
90
93
|
if (this.allTags === null) {
|
|
91
94
|
this.allTags = runGit('git tag', this.cwd)
|
|
92
95
|
.split('\n')
|
|
93
|
-
.map((t) => t.trim())
|
|
96
|
+
.map((t) => t.trim())
|
|
94
97
|
.filter(Boolean);
|
|
95
98
|
}
|
|
96
99
|
return this.allTags;
|
|
@@ -101,6 +104,24 @@ export class GitVcsProvider implements VcsProvider {
|
|
|
101
104
|
return getGitCommits(name, watch, tags, this.cwd);
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
async getLatestTag(name?: string): Promise<string | null> {
|
|
108
|
+
const tags = this.getAllTags();
|
|
109
|
+
|
|
110
|
+
if (typeof name === 'string') {
|
|
111
|
+
// Check specific crate tag first (e.g., "my_crate-v1.0.0")
|
|
112
|
+
const specificPrefix = `${name}-v`;
|
|
113
|
+
const specificTag = findLatestTag(tags, specificPrefix);
|
|
114
|
+
if (specificTag) return specificTag.slice(specificPrefix.length);
|
|
115
|
+
} else {
|
|
116
|
+
// Fallback to global release tag (e.g., "v1.0.0")
|
|
117
|
+
const genericPrefix = 'v';
|
|
118
|
+
const genericTag = findLatestTag(tags, genericPrefix);
|
|
119
|
+
if (genericTag) return genericTag.slice(genericPrefix.length);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
104
125
|
async commit(message: string): Promise<void> {
|
|
105
126
|
runGit('git add .', this.cwd);
|
|
106
127
|
runGit(`git commit -m "${message}"`, this.cwd);
|
package/src/vcs/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Commit } from '../types';
|
|
|
2
2
|
|
|
3
3
|
export interface VcsProvider {
|
|
4
4
|
getCommits(name: string, watch: string[]): Promise<Commit[]>;
|
|
5
|
+
getLatestTag(name?: string): Promise<string | null>;
|
|
5
6
|
commit(message: string): Promise<void>;
|
|
6
7
|
tag(tagName: string): Promise<void>;
|
|
7
8
|
}
|
package/src/vcs/jj.ts
CHANGED
|
@@ -15,7 +15,11 @@ export function runJj(cmd: string, cwd: string): string {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function parseSemver(tag: string, prefix: string): number[] {
|
|
18
|
-
|
|
18
|
+
const versionStr = tag.slice(prefix.length);
|
|
19
|
+
return versionStr.split('.').map((part) => {
|
|
20
|
+
const val = Number.parseInt(part, 10);
|
|
21
|
+
return Number.isNaN(val) ? 0 : val;
|
|
22
|
+
});
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export function findLatestJjTag(tags: string[], prefix: string): string | null {
|
|
@@ -46,15 +50,12 @@ export function getJjCommits(
|
|
|
46
50
|
): Commit[] {
|
|
47
51
|
const lastTag = resolveJjTag(name, allTags);
|
|
48
52
|
|
|
49
|
-
// FIXED: Double quotes explicitly escape the literal tag name for the jj revset engine
|
|
50
53
|
const range = lastTag ? `"${lastTag}"..@ & ~root()` : `::@ & ~root()`;
|
|
51
54
|
|
|
52
|
-
// Calling `.short(40)` to cast the CommitId into a 40-character hex String for template concatenation
|
|
53
55
|
const template =
|
|
54
56
|
'commit_id.short(40) ++ "|" ++ author.name() ++ "|" ++ author.timestamp().format("%Y-%m-%d") ++ "|" ++ description.first_line() ++ "\\n"';
|
|
55
57
|
|
|
56
58
|
const watchPaths = watch.join(' ');
|
|
57
|
-
// Appending --color=never to strip ANSI escape codes so hashes can be parsed cleanly
|
|
58
59
|
const jjCmd = watchPaths
|
|
59
60
|
? `log --no-graph -r '${range}' -T '${template}' -- ${watchPaths}`
|
|
60
61
|
: `log --no-graph -r '${range}' -T '${template}'`;
|
|
@@ -98,7 +99,6 @@ export class JjVcsProvider implements VcsProvider {
|
|
|
98
99
|
|
|
99
100
|
private getAllTags(): string[] {
|
|
100
101
|
if (this.allTags === null) {
|
|
101
|
-
// Must enforce --color=never to prevent ANSI escape codes from failing the startsWith prefix checks
|
|
102
102
|
const raw = runJj('tag list', this.cwd);
|
|
103
103
|
this.allTags = raw
|
|
104
104
|
.split('\n')
|
|
@@ -116,12 +116,29 @@ export class JjVcsProvider implements VcsProvider {
|
|
|
116
116
|
return getJjCommits(name, watch, tags, this.cwd);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
async getLatestTag(name?: string): Promise<string | null> {
|
|
120
|
+
const tags = this.getAllTags();
|
|
121
|
+
|
|
122
|
+
if (typeof name === 'string') {
|
|
123
|
+
// Check specific crate tag first
|
|
124
|
+
const specificPrefix = `${name}-v`;
|
|
125
|
+
const specificTag = findLatestJjTag(tags, specificPrefix);
|
|
126
|
+
if (specificTag) return specificTag.slice(specificPrefix.length);
|
|
127
|
+
} else {
|
|
128
|
+
// Fallback to global release tag
|
|
129
|
+
const genericPrefix = 'v';
|
|
130
|
+
const genericTag = findLatestJjTag(tags, genericPrefix);
|
|
131
|
+
if (genericTag) return genericTag.slice(genericPrefix.length);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
119
137
|
async commit(message: string): Promise<void> {
|
|
120
138
|
runJj(`commit -m "${message}"`, this.cwd);
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
async tag(tagName: string): Promise<void> {
|
|
124
|
-
// Appending --allow-move to update the tag reference cleanly if it already exists
|
|
125
142
|
runJj(`tag set "${tagName}" -r @- --allow-move`, this.cwd);
|
|
126
143
|
}
|
|
127
144
|
}
|