Skip to content

Commit f015439

Browse files
authored
feat(pdk): support direct release version/tag and improved prerelease (#1768)
1 parent 7b86e25 commit f015439

13 files changed

Lines changed: 541 additions & 82 deletions

File tree

infra/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## pdk@0.0.6-beta.2 (2025-12-15)
4+
5+
## What's Changed
6+
7+
### Bug Fixes 🐛
8+
9+
* fix(pdk): support mixed tag formats in changelog generation by @ulivz in d7e6016
10+
* fix(pdk): support custom git tag formats in changelog generation by @ulivz in 70a427f
11+
12+
### Other Changes
13+
14+
* chore: tweaks by @ulivz in a777b4d
15+
* chore(pdk): update changelog for 0.0.6-beta.1 by @ulivz in 7a5645c
16+
* : Revert "fix(pdk): support mixed tag formats in changelog generation" by @ulivz in d2d9cb7
17+
18+
**Full Changelog**: [pdk@0.0.6-beta.1...pdk@0.0.6-beta.2](https://github.com/bytedance/UI-TARS-desktop/compare/pdk@0.0.6-beta.1...pdk@0.0.6-beta.2)
319
## vpdk@0.0.5 (2025-12-09)
420

521
## What's Changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/**
7+
* Tests for tagPrefix-based tag filtering in changelog generation
8+
*/
9+
10+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11+
import { getPreviousTag } from '../utils/github';
12+
13+
// Mock execa
14+
vi.mock('execa', () => ({
15+
execa: vi.fn(),
16+
}));
17+
18+
import { execa } from 'execa';
19+
20+
describe('getPreviousTag with tagPrefix filtering', () => {
21+
beforeEach(() => {
22+
vi.clearAllMocks();
23+
});
24+
25+
afterEach(() => {
26+
vi.restoreAllMocks();
27+
});
28+
29+
it('should filter tags by tagPrefix correctly', async () => {
30+
// Mock git tag output
31+
const mockTags = [
32+
'pdk@0.0.6-beta.1',
33+
'pdk@0.0.5',
34+
'@agent-tars@0.3.0',
35+
'v0.3.0',
36+
'pdk@0.0.4',
37+
].join('\n');
38+
39+
vi.mocked(execa).mockResolvedValue({
40+
stdout: mockTags,
41+
} as any);
42+
43+
const result = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
44+
45+
expect(result).toBe('pdk@0.0.5');
46+
expect(execa).toHaveBeenCalledWith('git', ['tag', '--sort=-creatordate'], {
47+
cwd: '/test/cwd',
48+
});
49+
});
50+
51+
it('should return null when no tags match tagPrefix', async () => {
52+
const mockTags = [
53+
'@agent-tars@0.3.0',
54+
'v0.3.0',
55+
'v0.2.0',
56+
].join('\n');
57+
58+
vi.mocked(execa).mockResolvedValue({
59+
stdout: mockTags,
60+
} as any);
61+
62+
const result = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
63+
64+
expect(result).toBeNull();
65+
});
66+
67+
it('should handle mixed tag formats correctly', async () => {
68+
const mockTags = [
69+
'pdk@0.0.6-beta.1',
70+
'@agent-tars@0.3.0',
71+
'v0.3.0',
72+
'pdk@0.0.5',
73+
'@agent-tars@0.2.9',
74+
'v0.2.0',
75+
'pdk@0.0.4',
76+
].join('\n');
77+
78+
vi.mocked(execa).mockResolvedValue({
79+
stdout: mockTags,
80+
} as any);
81+
82+
const result = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
83+
84+
expect(result).toBe('pdk@0.0.5');
85+
});
86+
87+
it('should filter out canary releases even with tagPrefix', async () => {
88+
const mockTags = [
89+
'pdk@0.0.6-beta.1',
90+
'pdk@0.0.5-canary-abc123',
91+
'pdk@0.0.5',
92+
'pdk@0.0.4-canary-def456',
93+
'pdk@0.0.4',
94+
].join('\n');
95+
96+
vi.mocked(execa).mockResolvedValue({
97+
stdout: mockTags,
98+
} as any);
99+
100+
const result = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
101+
102+
expect(result).toBe('pdk@0.0.5');
103+
});
104+
105+
it('should return most recent tag when current tag not found', async () => {
106+
const mockTags = [
107+
'pdk@0.0.5',
108+
'pdk@0.0.4',
109+
'pdk@0.0.3',
110+
].join('\n');
111+
112+
vi.mocked(execa).mockResolvedValue({
113+
stdout: mockTags,
114+
} as any);
115+
116+
const result = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
117+
118+
expect(result).toBe('pdk@0.0.5');
119+
});
120+
121+
it('should work without tagPrefix (backward compatibility)', async () => {
122+
const mockTags = [
123+
'v0.3.0',
124+
'v0.2.0',
125+
'v0.1.0',
126+
].join('\n');
127+
128+
vi.mocked(execa).mockResolvedValue({
129+
stdout: mockTags,
130+
} as any);
131+
132+
const result = await getPreviousTag('v0.3.0', '/test/cwd');
133+
134+
expect(result).toBe('v0.2.0');
135+
});
136+
137+
it('should handle empty tag list', async () => {
138+
vi.mocked(execa).mockResolvedValue({
139+
stdout: '',
140+
} as any);
141+
142+
const result = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
143+
144+
expect(result).toBeNull();
145+
});
146+
147+
it('should handle git command errors gracefully', async () => {
148+
vi.mocked(execa).mockRejectedValue(new Error('Git command failed'));
149+
150+
const result = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
151+
152+
expect(result).toBeNull();
153+
});
154+
155+
it('should handle complex real-world scenario', async () => {
156+
// Simulate the real tag list from the repository
157+
const mockTags = [
158+
'pdk@0.0.6-beta.1',
159+
'pdk@0.0.5-canary-7d05b7ce-20251213170600',
160+
'pdk@0.0.4-canary-156ae14f-20251213163615-canary-156ae14f-20251213163717',
161+
'pdk@0.0.4-canary-598a6df1e-20251202172337',
162+
'@agent-tars@0.3.0-beta.1',
163+
'@agent-tars@0.3.0-beta.0',
164+
'@agent-tars@0.2.9',
165+
'v0.3.0',
166+
'v0.2.0',
167+
'pdk@0.0.5',
168+
'pdk@0.0.4',
169+
'v0.1.0',
170+
].join('\n');
171+
172+
vi.mocked(execa).mockResolvedValue({
173+
stdout: mockTags,
174+
} as any);
175+
176+
// Test case 1: Current version is 0.0.5, releasing 0.0.6-beta.1
177+
const result1 = await getPreviousTag('pdk@0.0.6-beta.1', '/test/cwd', 'pdk@');
178+
expect(result1).toBe('pdk@0.0.5');
179+
180+
// Test case 2: Current version is 0.0.6-beta.1, releasing 0.0.6-beta.2
181+
const result2 = await getPreviousTag('pdk@0.0.6-beta.2', '/test/cwd', 'pdk@');
182+
expect(result2).toBe('pdk@0.0.6-beta.1');
183+
});
184+
185+
it('should handle @agent-tars@ tagPrefix correctly', async () => {
186+
const mockTags = [
187+
'@agent-tars@0.3.0-beta.1',
188+
'@agent-tars@0.3.0-beta.0',
189+
'@agent-tars@0.2.9',
190+
'@agent-tars@0.2.8',
191+
'pdk@0.0.6-beta.1',
192+
'v0.3.0',
193+
].join('\n');
194+
195+
vi.mocked(execa).mockResolvedValue({
196+
stdout: mockTags,
197+
} as any);
198+
199+
const result = await getPreviousTag('@agent-tars@0.3.0-beta.1', '/test/cwd', '@agent-tars@');
200+
201+
expect(result).toBe('@agent-tars@0.3.0-beta.0');
202+
});
203+
204+
it('should handle v tagPrefix correctly', async () => {
205+
const mockTags = [
206+
'v0.3.0',
207+
'v0.2.0',
208+
'v0.1.0',
209+
'@agent-tars@0.3.0-beta.1',
210+
'pdk@0.0.6-beta.1',
211+
].join('\n');
212+
213+
vi.mocked(execa).mockResolvedValue({
214+
stdout: mockTags,
215+
} as any);
216+
217+
const result = await getPreviousTag('v0.3.0', '/test/cwd', 'v');
218+
219+
expect(result).toBe('v0.2.0');
220+
});
221+
});

infra/pdk/src/cli.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* CLI entry point for PDK
88
*/
99
import { cac } from 'cac';
10-
import { dev, release, patch, changelog, githubRelease } from './commands';
10+
import { dev, release, patch, changelog, githubRelease, nextVersion } from './commands';
1111
import { logger } from './utils/logger';
1212
import { loadPDKConfig, mergeOptions } from './utils/config';
1313

@@ -114,6 +114,18 @@ export function bootstrapCli() {
114114
'--auto-create-release-branch',
115115
'Automatically create release branch before release',
116116
)
117+
.option(
118+
'--release-version <version>',
119+
'Directly specify release version (skips interactive selection)',
120+
)
121+
.option(
122+
'--release-tag <tag>',
123+
'Directly specify release tag (skips interactive selection)',
124+
)
125+
.option(
126+
'--skip-confirm',
127+
'Skip confirmation prompts during release',
128+
)
117129
.alias('release')
118130
.action((opts) => {
119131
// Process filter options
@@ -219,6 +231,14 @@ export function bootstrapCli() {
219231
return wrapCommand(githubRelease, opts);
220232
});
221233

234+
// Next Version command
235+
cli
236+
.command('next-version', 'Show next version options based on current version')
237+
.option('--cwd <cwd>', 'Current working directory')
238+
.action((opts) => {
239+
return wrapCommand(nextVersion, opts);
240+
});
241+
222242
cli.version(pkg.version);
223243
cli.help();
224244

infra/pdk/src/commands/changelog.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
import { existsSync, readFileSync, writeFileSync } from 'fs';
1111
import { join } from 'path';
12+
import { execa } from 'execa';
1213
import { resolveWorkspaceConfig } from '../utils/workspace';
1314
import { gitCommit, gitPush } from '../utils/git';
1415
import { logger } from '../utils/logger';
@@ -82,14 +83,26 @@ export async function changelog(options: ChangelogOptions = {}): Promise<void> {
8283
const changelogPath = join(cwd, 'CHANGELOG.md');
8384
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
8485

85-
// Construct tag name
86-
const tagName = `${tagPrefix}${version}`;
86+
// Construct tag name - try to find actual git tag first
87+
let tagName = `${tagPrefix}${version}`;
88+
89+
// Try to find the actual git tag that matches this version
90+
try {
91+
const { stdout } = await execa('git', ['tag', '--list', `${tagPrefix}${version}`], { cwd });
92+
const matchingTags = stdout.trim().split('\n').filter(Boolean);
93+
if (matchingTags.length > 0) {
94+
// Use the actual tag that exists
95+
tagName = matchingTags[0];
96+
}
97+
} catch {
98+
// Fall back to constructed tag name
99+
}
87100

88101
// Resolve repo info
89102
const repoInfo = await getRepositoryInfo(cwd);
90103

91-
// Get previous tag (non-canary)
92-
const previousTag = await getPreviousTag(tagName, cwd);
104+
// Get previous tag (non-canary) with tagPrefix filter
105+
const previousTag = await getPreviousTag(tagName, cwd, tagPrefix);
93106

94107
logger.info(
95108
`📝 Generating unified changelog from ${previousTag || 'repository start'} to ${tagName}`,
@@ -123,8 +136,7 @@ export async function changelog(options: ChangelogOptions = {}): Promise<void> {
123136
}
124137

125138
// Compose final changelog entry with version header
126-
const versionHeader = tagName.startsWith('v') ? tagName : `v${tagName}`;
127-
const entry = `## ${versionHeader} (${today})\n\n${releaseNotes}\n`;
139+
const entry = `## ${tagName} (${today})\n\n${releaseNotes}\n`;
128140

129141
if (dryRun) {
130142
logger.info(`[dry-run] Would update CHANGELOG.md with:`);

infra/pdk/src/commands/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './release';
88
export * from './patch';
99
export * from './changelog';
1010
export * from './github-release';
11+
export * from './next-version';

0 commit comments

Comments
 (0)