relacher 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/lib-types/builder/cargo.d.ts +13 -0
- package/lib-types/builder/index.d.ts +3 -0
- package/lib-types/builder/pnpm.d.ts +13 -0
- package/lib-types/builder/shared.d.ts +12 -0
- package/lib-types/index.d.ts +6 -0
- package/lib-types/prepare.d.ts +14 -0
- package/lib-types/print.d.ts +2 -0
- package/lib-types/run.d.ts +7 -0
- package/lib-types/types.d.ts +87 -0
- package/lib-types/vcs/git.d.ts +14 -0
- package/lib-types/vcs/index.d.ts +8 -0
- package/lib-types/vcs/jj.d.ts +14 -0
- package/package.json +61 -0
- package/src/builder/cargo.ts +111 -0
- package/src/builder/index.ts +3 -0
- package/src/builder/pnpm.ts +162 -0
- package/src/builder/shared.ts +37 -0
- package/src/index.ts +6 -0
- package/src/prepare.ts +262 -0
- package/src/print.ts +109 -0
- package/src/run.ts +97 -0
- package/src/types.ts +114 -0
- package/src/vcs/git.ts +112 -0
- package/src/vcs/index.ts +10 -0
- package/src/vcs/jj.ts +127 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { VcsProvider } from './vcs';
|
|
2
|
+
|
|
3
|
+
export type BumpSize = "major" | "minor" | "patch" | "skip";
|
|
4
|
+
|
|
5
|
+
export interface Commit {
|
|
6
|
+
hash: string;
|
|
7
|
+
shortHash: string;
|
|
8
|
+
author: string;
|
|
9
|
+
date: string;
|
|
10
|
+
message: string; // The full commit message
|
|
11
|
+
type: string; // e.g., 'feat', 'fix', 'chore'
|
|
12
|
+
scope: string | null; // e.g., 'ui', 'api'
|
|
13
|
+
isBreaking: boolean; // true if contains '!' or 'BREAKING CHANGE'
|
|
14
|
+
description: string; // The text after the colon
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ChangelogContext {
|
|
18
|
+
version: string;
|
|
19
|
+
date: string;
|
|
20
|
+
commits: Commit[];
|
|
21
|
+
}
|
|
22
|
+
|
|
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
|
+
export interface DependencyConfig {
|
|
48
|
+
name: string;
|
|
49
|
+
watch?: string[];
|
|
50
|
+
updates?: UpdateAction[];
|
|
51
|
+
depends?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface PatternConfig {
|
|
55
|
+
pattern: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SizePatterns {
|
|
59
|
+
major: PatternConfig;
|
|
60
|
+
minor: PatternConfig;
|
|
61
|
+
patch: PatternConfig;
|
|
62
|
+
skip: PatternConfig;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CascadeRules {
|
|
66
|
+
skip?: Record<BumpSize, BumpSize>;
|
|
67
|
+
patch?: Record<BumpSize, BumpSize>;
|
|
68
|
+
minor?: Record<BumpSize, BumpSize>;
|
|
69
|
+
major?: Record<BumpSize, BumpSize>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PrepareOptions {
|
|
73
|
+
sizes?: SizePatterns;
|
|
74
|
+
cascade?: CascadeRules;
|
|
75
|
+
cwd?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface DependencyUpdateReport {
|
|
79
|
+
name: string;
|
|
80
|
+
currentVersion: string;
|
|
81
|
+
newVersion: string;
|
|
82
|
+
bump: BumpSize;
|
|
83
|
+
originalBump?: BumpSize;
|
|
84
|
+
commits: Commit[];
|
|
85
|
+
updates: UpdateAction[];
|
|
86
|
+
depends?: string[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface IntermediateReport {
|
|
90
|
+
name: string;
|
|
91
|
+
currentVersion: string;
|
|
92
|
+
newVersion: string;
|
|
93
|
+
bump: BumpSize;
|
|
94
|
+
originalBump?: BumpSize;
|
|
95
|
+
commits: Commit[];
|
|
96
|
+
updates: UpdateAction[];
|
|
97
|
+
depends: string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
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
|
+
export type { VcsProvider };
|
package/src/vcs/git.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import type { Commit, VcsProvider } from '../types';
|
|
4
|
+
|
|
5
|
+
export function runGit(cmd: string, cwd: string): string {
|
|
6
|
+
try {
|
|
7
|
+
return execSync(cmd, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
8
|
+
} catch {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseSemver(tag: string, prefix: string): number[] {
|
|
14
|
+
return tag.slice(prefix.length).split('.').map(Number);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function findLatestTag(tags: string[], prefix: string): string | null {
|
|
18
|
+
const matching = tags.filter((t) => t.startsWith(prefix));
|
|
19
|
+
if (matching.length === 0) return null;
|
|
20
|
+
return (
|
|
21
|
+
matching.sort((a, b) => {
|
|
22
|
+
const verA = parseSemver(a, prefix);
|
|
23
|
+
const verB = parseSemver(b, prefix);
|
|
24
|
+
for (let i = 0; i < 3; i++) {
|
|
25
|
+
const diff = (verB[i] ?? 0) - (verA[i] ?? 0);
|
|
26
|
+
if (diff !== 0) return diff;
|
|
27
|
+
}
|
|
28
|
+
return 0;
|
|
29
|
+
})[0] ?? null
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveGitTag(name: string, allTags: string[]): string | null {
|
|
34
|
+
return findLatestTag(allTags, `${name}-v`) || findLatestTag(allTags, 'v');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getGitCommits(
|
|
38
|
+
name: string,
|
|
39
|
+
watch: string[],
|
|
40
|
+
allTags: string[],
|
|
41
|
+
cwd: string,
|
|
42
|
+
): Commit[] {
|
|
43
|
+
const lastTag = resolveGitTag(name, allTags);
|
|
44
|
+
const watchPaths = watch.join(' ');
|
|
45
|
+
const range = lastTag ? `${lastTag}..HEAD` : 'HEAD';
|
|
46
|
+
|
|
47
|
+
// Using shell-safe single quotes and disabling signature lines to prevent parsing corruption
|
|
48
|
+
const gitCmd = watchPaths
|
|
49
|
+
? `git log ${range} --no-show-signature --format='%H|%an|%ad|%s' --date=short -- ${watchPaths}`
|
|
50
|
+
: `git log ${range} --no-show-signature --format='%H|%an|%ad|%s' --date=short`;
|
|
51
|
+
|
|
52
|
+
return runGit(gitCmd, cwd)
|
|
53
|
+
.split('\n')
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.map((line) => {
|
|
56
|
+
const parts = line.split('|');
|
|
57
|
+
const hash = parts[0] ?? '';
|
|
58
|
+
const author = parts[1] ?? '';
|
|
59
|
+
const date = parts[2] ?? '';
|
|
60
|
+
const msgParts = parts.slice(3);
|
|
61
|
+
const message = msgParts.join('|').trim();
|
|
62
|
+
|
|
63
|
+
const ccMatch = message.match(/^([a-zA-Z]+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/);
|
|
64
|
+
|
|
65
|
+
const type = ccMatch ? (ccMatch[1] ?? 'other') : 'other';
|
|
66
|
+
const scope = ccMatch ? (ccMatch[2] ?? null) : null;
|
|
67
|
+
const isBreaking = (ccMatch && !!ccMatch[3]) || message.includes('BREAKING CHANGE');
|
|
68
|
+
const description = ccMatch ? (ccMatch[4] ?? message) : message;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
hash,
|
|
72
|
+
shortHash: hash.slice(0, 7),
|
|
73
|
+
author,
|
|
74
|
+
date,
|
|
75
|
+
message,
|
|
76
|
+
type,
|
|
77
|
+
scope,
|
|
78
|
+
isBreaking,
|
|
79
|
+
description,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class GitVcsProvider implements VcsProvider {
|
|
85
|
+
private allTags: string[] | null = null;
|
|
86
|
+
|
|
87
|
+
constructor(private cwd: string) { }
|
|
88
|
+
|
|
89
|
+
private getAllTags(): string[] {
|
|
90
|
+
if (this.allTags === null) {
|
|
91
|
+
this.allTags = runGit('git tag', this.cwd)
|
|
92
|
+
.split('\n')
|
|
93
|
+
.map((t) => t.trim()) // Strips carriage returns (\r) and trailing whitespaces
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
}
|
|
96
|
+
return this.allTags;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getCommits(name: string, watch: string[]): Promise<Commit[]> {
|
|
100
|
+
const tags = this.getAllTags();
|
|
101
|
+
return getGitCommits(name, watch, tags, this.cwd);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async commit(message: string): Promise<void> {
|
|
105
|
+
runGit('git add .', this.cwd);
|
|
106
|
+
runGit(`git commit -m "${message}"`, this.cwd);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async tag(tagName: string): Promise<void> {
|
|
110
|
+
runGit(`git tag ${tagName}`, this.cwd);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/vcs/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Commit } from '../types';
|
|
2
|
+
|
|
3
|
+
export interface VcsProvider {
|
|
4
|
+
getCommits(name: string, watch: string[]): Promise<Commit[]>;
|
|
5
|
+
commit(message: string): Promise<void>;
|
|
6
|
+
tag(tagName: string): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export * from './git';
|
|
10
|
+
export * from './jj';
|
package/src/vcs/jj.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import type { Commit, VcsProvider } from '../types';
|
|
4
|
+
|
|
5
|
+
export function runJj(cmd: string, cwd: string): string {
|
|
6
|
+
try {
|
|
7
|
+
return execSync(`jj --color=never ${cmd}`, {
|
|
8
|
+
cwd,
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
11
|
+
}).trim();
|
|
12
|
+
} catch {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseSemver(tag: string, prefix: string): number[] {
|
|
18
|
+
return tag.slice(prefix.length).split('.').map(Number);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function findLatestJjTag(tags: string[], prefix: string): string | null {
|
|
22
|
+
const matching = tags.filter((t) => t.startsWith(prefix));
|
|
23
|
+
if (matching.length === 0) return null;
|
|
24
|
+
return (
|
|
25
|
+
matching.sort((a, b) => {
|
|
26
|
+
const verA = parseSemver(a, prefix);
|
|
27
|
+
const verB = parseSemver(b, prefix);
|
|
28
|
+
for (let i = 0; i < 3; i++) {
|
|
29
|
+
const diff = (verB[i] ?? 0) - (verA[i] ?? 0);
|
|
30
|
+
if (diff !== 0) return diff;
|
|
31
|
+
}
|
|
32
|
+
return 0;
|
|
33
|
+
})[0] ?? null
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveJjTag(name: string, allTags: string[]): string | null {
|
|
38
|
+
return findLatestJjTag(allTags, `${name}-v`) || findLatestJjTag(allTags, 'v');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getJjCommits(
|
|
42
|
+
name: string,
|
|
43
|
+
watch: string[],
|
|
44
|
+
allTags: string[],
|
|
45
|
+
cwd: string,
|
|
46
|
+
): Commit[] {
|
|
47
|
+
const lastTag = resolveJjTag(name, allTags);
|
|
48
|
+
|
|
49
|
+
// FIXED: Double quotes explicitly escape the literal tag name for the jj revset engine
|
|
50
|
+
const range = lastTag ? `"${lastTag}"..@ & ~root()` : `::@ & ~root()`;
|
|
51
|
+
|
|
52
|
+
// Calling `.short(40)` to cast the CommitId into a 40-character hex String for template concatenation
|
|
53
|
+
const template =
|
|
54
|
+
'commit_id.short(40) ++ "|" ++ author.name() ++ "|" ++ author.timestamp().format("%Y-%m-%d") ++ "|" ++ description.first_line() ++ "\\n"';
|
|
55
|
+
|
|
56
|
+
const watchPaths = watch.join(' ');
|
|
57
|
+
// Appending --color=never to strip ANSI escape codes so hashes can be parsed cleanly
|
|
58
|
+
const jjCmd = watchPaths
|
|
59
|
+
? `log --no-graph -r '${range}' -T '${template}' -- ${watchPaths}`
|
|
60
|
+
: `log --no-graph -r '${range}' -T '${template}'`;
|
|
61
|
+
|
|
62
|
+
return runJj(jjCmd, cwd)
|
|
63
|
+
.split('\n')
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.map((line) => {
|
|
66
|
+
const parts = line.split('|');
|
|
67
|
+
const hash = parts[0] ?? '';
|
|
68
|
+
const author = parts[1] ?? '';
|
|
69
|
+
const date = parts[2] ?? '';
|
|
70
|
+
const msgParts = parts.slice(3);
|
|
71
|
+
const message = msgParts.join('|').trim();
|
|
72
|
+
|
|
73
|
+
const ccMatch = message.match(/^([a-zA-Z]+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/);
|
|
74
|
+
|
|
75
|
+
const type = ccMatch ? (ccMatch[1] ?? 'other') : 'other';
|
|
76
|
+
const scope = ccMatch ? (ccMatch[2] ?? null) : null;
|
|
77
|
+
const isBreaking = (ccMatch && !!ccMatch[3]) || message.includes('BREAKING CHANGE');
|
|
78
|
+
const description = ccMatch ? (ccMatch[4] ?? message) : message;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
hash,
|
|
82
|
+
shortHash: hash.slice(0, 7),
|
|
83
|
+
author,
|
|
84
|
+
date,
|
|
85
|
+
message,
|
|
86
|
+
type,
|
|
87
|
+
scope,
|
|
88
|
+
isBreaking,
|
|
89
|
+
description,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class JjVcsProvider implements VcsProvider {
|
|
95
|
+
private allTags: string[] | null = null;
|
|
96
|
+
|
|
97
|
+
constructor(private cwd: string) { }
|
|
98
|
+
|
|
99
|
+
private getAllTags(): string[] {
|
|
100
|
+
if (this.allTags === null) {
|
|
101
|
+
// Must enforce --color=never to prevent ANSI escape codes from failing the startsWith prefix checks
|
|
102
|
+
const raw = runJj('tag list', this.cwd);
|
|
103
|
+
this.allTags = raw
|
|
104
|
+
.split('\n')
|
|
105
|
+
.map((line) => {
|
|
106
|
+
const parts = line.split(':');
|
|
107
|
+
return (parts[0] ?? '').trim();
|
|
108
|
+
})
|
|
109
|
+
.filter(Boolean);
|
|
110
|
+
}
|
|
111
|
+
return this.allTags;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getCommits(name: string, watch: string[]): Promise<Commit[]> {
|
|
115
|
+
const tags = this.getAllTags();
|
|
116
|
+
return getJjCommits(name, watch, tags, this.cwd);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async commit(message: string): Promise<void> {
|
|
120
|
+
runJj(`commit -m "${message}"`, this.cwd);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async tag(tagName: string): Promise<void> {
|
|
124
|
+
// Appending --allow-move to update the tag reference cleanly if it already exists
|
|
125
|
+
runJj(`tag set "${tagName}" -r @- --allow-move`, this.cwd);
|
|
126
|
+
}
|
|
127
|
+
}
|