versionary 0.23.0 → 0.24.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/README.md +13 -3
- package/dist/release/changelog.d.ts +4 -10
- package/dist/release/changelog.js +9 -56
- package/dist/release/plan.d.ts +4 -0
- package/dist/release/plan.js +13 -0
- package/dist/release/pr.d.ts +1 -1
- package/dist/release/pr.js +24 -41
- package/dist/strategy/python.d.ts +2 -0
- package/dist/strategy/python.js +245 -0
- package/dist/strategy/resolve.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,8 @@ release/tag.
|
|
|
49
49
|
|
|
50
50
|
Current implementation focuses on:
|
|
51
51
|
|
|
52
|
-
- strategy-based version updates (`simple`, `node`, `rust`, `r`, `latex
|
|
52
|
+
- strategy-based version updates (`simple`, `node`, `rust`, `r`, `latex`,
|
|
53
|
+
`python`)
|
|
53
54
|
- release planning and changelog generation
|
|
54
55
|
- review-mode vs direct-mode release flow
|
|
55
56
|
- a static internal SCM client (`github` provider today)
|
|
@@ -64,7 +65,7 @@ changing release orchestration. A new strategy should implement the
|
|
|
64
65
|
`VersionStrategy` contract in `src/strategy/types.ts` and be wired in
|
|
65
66
|
`src/strategy/resolve.ts`.
|
|
66
67
|
|
|
67
|
-
Checklist for new strategies
|
|
68
|
+
Checklist for new strategies:
|
|
68
69
|
|
|
69
70
|
- define strategy `name`
|
|
70
71
|
- define `getVersionFile(config)` defaults and config override behavior
|
|
@@ -94,6 +95,10 @@ Current ecosystem policy defaults:
|
|
|
94
95
|
- Node strategy updates root `package-lock.json`/`npm-shrinkwrap.json` when
|
|
95
96
|
present
|
|
96
97
|
- Rust release PR prep refreshes all discovered `Cargo.lock` files
|
|
98
|
+
- Python strategy refreshes any `poetry.lock`/`uv.lock`/`pdm.lock` at the
|
|
99
|
+
package root by shelling out to the matching tool (`poetry lock
|
|
100
|
+
--no-update`, `uv lock`, `pdm lock --update-reuse`); the corresponding
|
|
101
|
+
binary must be on `PATH`
|
|
97
102
|
- workspace/inheritance:
|
|
98
103
|
- Rust supports `version.workspace = true` via `[workspace.package].version`
|
|
99
104
|
- other strategies should document equivalent inheritance behavior explicitly
|
|
@@ -105,7 +110,7 @@ Current runtime code uses a flat `src/` layout with clear module boundaries:
|
|
|
105
110
|
- `src/cli/`: command router (`run`, `verify`, `plan`, `changelog`, `pr`, `release`)
|
|
106
111
|
- `src/release/`: release orchestration (plan/changelog/PR/release/state/recovery)
|
|
107
112
|
- `src/strategy/`: strategy contracts, resolver, and built-in implementations
|
|
108
|
-
(`simple`, `node`, `rust`, `r`, `latex`)
|
|
113
|
+
(`simple`, `node`, `rust`, `r`, `latex`, `python`)
|
|
109
114
|
- `src/scm/`: SCM client contracts and provider implementation(s)
|
|
110
115
|
- `src/config/`: config loading and schema validation
|
|
111
116
|
- `src/git/`: git commit/range and repository URL helpers
|
|
@@ -134,6 +139,11 @@ For a quick trial, use:
|
|
|
134
139
|
- `release-type: "latex"` uses `build.lua` as version source and updates LaTeX
|
|
135
140
|
`\ProvidesPackage{...}[YYYY-MM-DD vX.Y.Z ...]` metadata in `src/**/*.dtx`
|
|
136
141
|
using the release commit date
|
|
142
|
+
- `release-type: "python"` uses `pyproject.toml` (default) and updates
|
|
143
|
+
`[project].version` and/or `[tool.poetry].version`; point `version-file` at a
|
|
144
|
+
Python source file (e.g. `src/<pkg>/__init__.py`) to update a `__version__`
|
|
145
|
+
assignment instead. Refreshes `poetry.lock`/`uv.lock`/`pdm.lock` at the
|
|
146
|
+
package root if present
|
|
137
147
|
- simple/default strategy keeps `version.txt` as source of truth and does not
|
|
138
148
|
update `package.json`
|
|
139
149
|
- stable release branch (`release-branch`, default: `versionary/release`) so
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ParsedCommit } from "../git/commits.js";
|
|
2
2
|
import type { VersionaryChangelogFormat } from "../types/config.js";
|
|
3
|
-
import type
|
|
4
|
-
export declare function
|
|
3
|
+
import { type ReleasePlan, type SimplePlan } from "./plan.js";
|
|
4
|
+
export declare function renderReleaseNotesSection(input: {
|
|
5
5
|
currentVersion: string;
|
|
6
6
|
nextVersion: string;
|
|
7
7
|
commits: ParsedCommit[];
|
|
@@ -11,9 +11,10 @@ export declare function renderSimpleReleaseNotes(input: {
|
|
|
11
11
|
version: string;
|
|
12
12
|
}>;
|
|
13
13
|
highlights?: string;
|
|
14
|
+
tagPrefix?: string;
|
|
15
|
+
headerLabel?: string;
|
|
14
16
|
}, options?: {
|
|
15
17
|
includeFooter?: boolean;
|
|
16
|
-
headerLabel?: string;
|
|
17
18
|
}): string;
|
|
18
19
|
export declare function renderReviewRequestFooter(): string;
|
|
19
20
|
export declare function renderReleasePlanChangelog(plan: ReleasePlan, options?: {
|
|
@@ -24,13 +25,6 @@ export declare function renderReleasePlanChangelog(plan: ReleasePlan, options?:
|
|
|
24
25
|
}): string;
|
|
25
26
|
/** @deprecated Use renderReleasePlanChangelog. */
|
|
26
27
|
export declare function renderSimpleChangelog(plan: SimplePlan): string;
|
|
27
|
-
export declare function renderPackageChangelogSection(input: {
|
|
28
|
-
currentVersion: string;
|
|
29
|
-
nextVersion: string;
|
|
30
|
-
commits: ParsedCommit[];
|
|
31
|
-
tagPrefix: string;
|
|
32
|
-
cwd?: string;
|
|
33
|
-
}): string;
|
|
34
28
|
export declare function prependChangelog(cwd: string, changelogFile: string, section: string, format?: VersionaryChangelogFormat): void;
|
|
35
29
|
export declare function renderRNewsReleaseNotes(input: {
|
|
36
30
|
packageName: string;
|
|
@@ -3,17 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.renderReleaseNotesSection = renderReleaseNotesSection;
|
|
7
7
|
exports.renderReviewRequestFooter = renderReviewRequestFooter;
|
|
8
8
|
exports.renderReleasePlanChangelog = renderReleasePlanChangelog;
|
|
9
9
|
exports.renderSimpleChangelog = renderSimpleChangelog;
|
|
10
|
-
exports.renderPackageChangelogSection = renderPackageChangelogSection;
|
|
11
10
|
exports.prependChangelog = prependChangelog;
|
|
12
11
|
exports.renderRNewsReleaseNotes = renderRNewsReleaseNotes;
|
|
13
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
14
|
const commits_js_1 = require("../git/commits.js");
|
|
16
15
|
const repo_url_js_1 = require("../git/repo-url.js");
|
|
16
|
+
const plan_js_1 = require("./plan.js");
|
|
17
17
|
const REVIEW_REQUEST_FOOTER = "---\n\nThis PR was generated by [Versionary](https://github.com/jolars/versionary).";
|
|
18
18
|
function formatDate() {
|
|
19
19
|
return new Date().toISOString().slice(0, 10);
|
|
@@ -140,11 +140,12 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
140
140
|
}
|
|
141
141
|
return { breaking, features, fixes, performance, reverts };
|
|
142
142
|
}
|
|
143
|
-
function
|
|
143
|
+
function renderReleaseNotesSection(input, options = {}) {
|
|
144
144
|
const repoUrl = (0, repo_url_js_1.resolveRepositoryWebBaseUrl)(input.cwd ?? process.cwd());
|
|
145
|
-
const headerLabel =
|
|
145
|
+
const headerLabel = input.headerLabel ?? input.nextVersion;
|
|
146
|
+
const versionPrefix = input.tagPrefix ? `${input.tagPrefix}-v` : "v";
|
|
146
147
|
const header = repoUrl
|
|
147
|
-
? `## [${headerLabel}](${repoUrl}/compare
|
|
148
|
+
? `## [${headerLabel}](${repoUrl}/compare/${versionPrefix}${input.currentVersion}...${versionPrefix}${input.nextVersion}) (${formatDate()})`
|
|
148
149
|
: `## ${headerLabel} (${formatDate()})`;
|
|
149
150
|
const grouped = groupCommitLines(input.commits, repoUrl);
|
|
150
151
|
const sections = [];
|
|
@@ -183,30 +184,6 @@ function renderReleasePlanChangelog(plan, options = {}) {
|
|
|
183
184
|
if (!plan.nextVersion) {
|
|
184
185
|
return "";
|
|
185
186
|
}
|
|
186
|
-
const propagatedRootPackage = plan.packages?.find((pkg) => pkg.path === ".");
|
|
187
|
-
const isDirectBump = (pkg) => pkg.bumpReason === "direct" ||
|
|
188
|
-
(pkg.bumpReason === undefined &&
|
|
189
|
-
Boolean(pkg.nextVersion) &&
|
|
190
|
-
pkg.commits.length > 0);
|
|
191
|
-
const legacyDependencySources = propagatedRootPackage?.bumpReason === "dependency-propagation" &&
|
|
192
|
-
plan.packages
|
|
193
|
-
? plan.packages
|
|
194
|
-
.filter((pkg) => pkg.path !== "." && isDirectBump(pkg) && pkg.nextVersion)
|
|
195
|
-
.map((pkg) => pkg.path)
|
|
196
|
-
: [];
|
|
197
|
-
const dependencySourcePaths = [
|
|
198
|
-
...(propagatedRootPackage?.dependencySourcePaths ??
|
|
199
|
-
legacyDependencySources),
|
|
200
|
-
].sort((a, b) => a.localeCompare(b));
|
|
201
|
-
const dependencies = plan.packages
|
|
202
|
-
? dependencySourcePaths
|
|
203
|
-
.map((sourcePath) => plan.packages?.find((pkg) => pkg.path === sourcePath && pkg.nextVersion))
|
|
204
|
-
.filter((sourcePackage) => Boolean(sourcePackage))
|
|
205
|
-
.map((pkg) => ({
|
|
206
|
-
name: pkg.path,
|
|
207
|
-
version: pkg.nextVersion,
|
|
208
|
-
}))
|
|
209
|
-
: [];
|
|
210
187
|
const dedupedCommits = [
|
|
211
188
|
...new Map(plan.commits.map((commit) => [commit.hash, commit])).values(),
|
|
212
189
|
];
|
|
@@ -219,46 +196,22 @@ function renderReleasePlanChangelog(plan, options = {}) {
|
|
|
219
196
|
highlights: options.highlights,
|
|
220
197
|
});
|
|
221
198
|
}
|
|
222
|
-
return
|
|
199
|
+
return renderReleaseNotesSection({
|
|
223
200
|
currentVersion: plan.currentVersion,
|
|
224
201
|
nextVersion: plan.nextVersion,
|
|
225
202
|
commits: dedupedCommits,
|
|
226
203
|
cwd: options.cwd ?? process.cwd(),
|
|
227
|
-
dependencies,
|
|
204
|
+
dependencies: (0, plan_js_1.resolvePackageDependencies)(plan, "."),
|
|
228
205
|
highlights: options.highlights,
|
|
206
|
+
headerLabel: options.headerLabel,
|
|
229
207
|
}, {
|
|
230
208
|
includeFooter: options.includeFooter,
|
|
231
|
-
headerLabel: options.headerLabel,
|
|
232
209
|
});
|
|
233
210
|
}
|
|
234
211
|
/** @deprecated Use renderReleasePlanChangelog. */
|
|
235
212
|
function renderSimpleChangelog(plan) {
|
|
236
213
|
return renderReleasePlanChangelog(plan);
|
|
237
214
|
}
|
|
238
|
-
function renderPackageChangelogSection(input) {
|
|
239
|
-
const repoUrl = (0, repo_url_js_1.resolveRepositoryWebBaseUrl)(input.cwd ?? process.cwd());
|
|
240
|
-
const header = repoUrl
|
|
241
|
-
? `## [${input.nextVersion}](${repoUrl}/compare/${input.tagPrefix}-v${input.currentVersion}...${input.tagPrefix}-v${input.nextVersion}) (${formatDate()})`
|
|
242
|
-
: `## ${input.nextVersion} (${formatDate()})`;
|
|
243
|
-
const grouped = groupCommitLines(input.commits, repoUrl);
|
|
244
|
-
const sections = [];
|
|
245
|
-
if (grouped.breaking.length > 0) {
|
|
246
|
-
sections.push("### Breaking changes", ...grouped.breaking, "");
|
|
247
|
-
}
|
|
248
|
-
if (grouped.features.length > 0) {
|
|
249
|
-
sections.push("### Features", ...grouped.features, "");
|
|
250
|
-
}
|
|
251
|
-
if (grouped.fixes.length > 0) {
|
|
252
|
-
sections.push("### Bug Fixes", ...grouped.fixes, "");
|
|
253
|
-
}
|
|
254
|
-
if (grouped.performance.length > 0) {
|
|
255
|
-
sections.push("### Performance Improvements", ...grouped.performance, "");
|
|
256
|
-
}
|
|
257
|
-
if (grouped.reverts.length > 0) {
|
|
258
|
-
sections.push("### Reverts", ...grouped.reverts, "");
|
|
259
|
-
}
|
|
260
|
-
return [header, "", ...sections].join("\n");
|
|
261
|
-
}
|
|
262
215
|
function prependChangelog(cwd, changelogFile, section, format = "markdown-changelog") {
|
|
263
216
|
const changelogPath = node_path_1.default.join(cwd, changelogFile);
|
|
264
217
|
const existing = node_fs_1.default.existsSync(changelogPath)
|
package/dist/release/plan.d.ts
CHANGED
|
@@ -37,3 +37,7 @@ export declare function getChangelogDefaults(config: {
|
|
|
37
37
|
export declare function createReleasePlan(cwd?: string): ReleasePlan;
|
|
38
38
|
/** @deprecated Use createReleasePlan. */
|
|
39
39
|
export declare function createSimplePlan(cwd?: string): ReleasePlan;
|
|
40
|
+
export declare function resolvePackageDependencies(plan: ReleasePlan, packagePath: string): Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
}>;
|
package/dist/release/plan.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.getChangelogDefaults = getChangelogDefaults;
|
|
7
7
|
exports.createReleasePlan = createReleasePlan;
|
|
8
8
|
exports.createSimplePlan = createSimplePlan;
|
|
9
|
+
exports.resolvePackageDependencies = resolvePackageDependencies;
|
|
9
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const load_config_js_1 = require("../config/load-config.js");
|
|
@@ -311,3 +312,15 @@ function createReleasePlan(cwd = process.cwd()) {
|
|
|
311
312
|
function createSimplePlan(cwd = process.cwd()) {
|
|
312
313
|
return createReleasePlan(cwd);
|
|
313
314
|
}
|
|
315
|
+
function resolvePackageDependencies(plan, packagePath) {
|
|
316
|
+
const target = plan.packages?.find((pkg) => pkg.path === packagePath);
|
|
317
|
+
if (!target) {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
const sources = target.dependencySourcePaths ?? [];
|
|
321
|
+
return sources
|
|
322
|
+
.map((sourcePath) => plan.packages?.find((pkg) => pkg.path === sourcePath && pkg.nextVersion))
|
|
323
|
+
.filter((sourcePackage) => Boolean(sourcePackage))
|
|
324
|
+
.map((pkg) => ({ name: pkg.path, version: pkg.nextVersion }))
|
|
325
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
326
|
+
}
|
package/dist/release/pr.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare function prepareReleasePr(cwd?: string, options?: {
|
|
|
26
26
|
updated: boolean;
|
|
27
27
|
highlights: string;
|
|
28
28
|
};
|
|
29
|
-
export declare function renderSimpleReviewRequestBody(version: string, previousVersion: string, commits: ParsedCommit[], plan?: SimplePlan | null, cwd?: string, highlights?: string): string;
|
|
29
|
+
export declare function renderSimpleReviewRequestBody(version: string, previousVersion: string, commits: ParsedCommit[], plan?: SimplePlan | null, cwd?: string, highlights?: string, loadedConfig?: VersionaryConfig): string;
|
|
30
30
|
export declare function openOrUpdateReviewRequest(cwd: string, branch: string, title: string, version: string, previousVersion: string, commits: ParsedCommit[], plan?: SimplePlan | null, options?: {
|
|
31
31
|
logger?: VersionaryPluginContext["logger"];
|
|
32
32
|
highlights?: string;
|
package/dist/release/pr.js
CHANGED
|
@@ -315,12 +315,13 @@ function prepareReleasePr(cwd = process.cwd(), options = {}) {
|
|
|
315
315
|
if (!packageMetadata) {
|
|
316
316
|
continue;
|
|
317
317
|
}
|
|
318
|
-
const packageSection = (0, changelog_js_1.
|
|
318
|
+
const packageSection = (0, changelog_js_1.renderReleaseNotesSection)({
|
|
319
319
|
currentVersion: packagePlan.currentVersion,
|
|
320
320
|
nextVersion: packagePlan.nextVersion,
|
|
321
321
|
commits: packagePlan.commits,
|
|
322
322
|
tagPrefix: packageMetadata.tagPrefix,
|
|
323
323
|
cwd,
|
|
324
|
+
dependencies: (0, plan_js_1.resolvePackageDependencies)(plan, packagePlan.path),
|
|
324
325
|
});
|
|
325
326
|
(0, changelog_js_1.prependChangelog)(cwd, node_path_1.default.posix.join(packagePlan.path, packageChangelogFile), packageSection, "markdown-changelog");
|
|
326
327
|
updatedChangelogFiles.push(node_path_1.default.posix.join(packagePlan.path, packageChangelogFile));
|
|
@@ -374,32 +375,20 @@ function prepareReleasePr(cwd = process.cwd(), options = {}) {
|
|
|
374
375
|
highlights,
|
|
375
376
|
};
|
|
376
377
|
}
|
|
377
|
-
function renderSimpleReviewRequestBody(version, previousVersion, commits, plan = null, cwd = process.cwd(), highlights = "") {
|
|
378
|
+
function renderSimpleReviewRequestBody(version, previousVersion, commits, plan = null, cwd = process.cwd(), highlights = "", loadedConfig) {
|
|
378
379
|
const rootPackageLabel = node_path_1.default.basename(cwd);
|
|
379
380
|
const formatPackageLabel = (packagePath) => packagePath === "." ? rootPackageLabel : packagePath;
|
|
380
|
-
const
|
|
381
|
-
(
|
|
382
|
-
|
|
383
|
-
pkg.commits.length > 0);
|
|
384
|
-
const findPropagatedDependencies = (packagePath, packages) => {
|
|
385
|
-
const target = packages.find((pkg) => pkg.path === packagePath);
|
|
386
|
-
if (!target) {
|
|
387
|
-
return [];
|
|
381
|
+
const resolveTagPrefix = (packagePath) => {
|
|
382
|
+
if (packagePath === ".") {
|
|
383
|
+
return undefined;
|
|
388
384
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
.filter((sourcePackage) => Boolean(sourcePackage))
|
|
397
|
-
.map((pkg) => ({
|
|
398
|
-
name: formatPackageLabel(pkg.path),
|
|
399
|
-
version: pkg.nextVersion ?? "",
|
|
400
|
-
}))
|
|
401
|
-
.filter((dependency) => dependency.version.length > 0)
|
|
402
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
385
|
+
if (!loadedConfig) {
|
|
386
|
+
return normalizeReleaseNameForTag(packagePath);
|
|
387
|
+
}
|
|
388
|
+
const packageConfig = loadedConfig.packages?.[packagePath] ?? {};
|
|
389
|
+
const packageContext = (0, package_context_js_1.resolvePackageStrategyContext)(loadedConfig, packagePath, packageConfig);
|
|
390
|
+
const releaseName = resolveReleaseName(cwd, packagePath, packageConfig, packageContext.strategy, packageContext.config);
|
|
391
|
+
return normalizeReleaseNameForTag(releaseName);
|
|
403
392
|
};
|
|
404
393
|
if (plan?.packages && plan.packages.length > 1) {
|
|
405
394
|
const sections = [];
|
|
@@ -414,21 +403,15 @@ function renderSimpleReviewRequestBody(version, previousVersion, commits, plan =
|
|
|
414
403
|
}
|
|
415
404
|
const packageSections = plan.packages
|
|
416
405
|
.filter((pkg) => pkg.path !== "." && pkg.nextVersion)
|
|
417
|
-
.map((pkg) => {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}, {
|
|
427
|
-
includeFooter: false,
|
|
428
|
-
headerLabel: `${packageLabel}: ${pkg.nextVersion ?? ""}`,
|
|
429
|
-
});
|
|
430
|
-
return notes;
|
|
431
|
-
});
|
|
406
|
+
.map((pkg) => (0, changelog_js_1.renderReleaseNotesSection)({
|
|
407
|
+
currentVersion: pkg.currentVersion,
|
|
408
|
+
nextVersion: pkg.nextVersion ?? "",
|
|
409
|
+
commits: pkg.commits,
|
|
410
|
+
cwd,
|
|
411
|
+
dependencies: (0, plan_js_1.resolvePackageDependencies)(plan, pkg.path),
|
|
412
|
+
tagPrefix: resolveTagPrefix(pkg.path),
|
|
413
|
+
headerLabel: `${formatPackageLabel(pkg.path)}: ${pkg.nextVersion ?? ""}`,
|
|
414
|
+
}));
|
|
432
415
|
sections.push(...packageSections);
|
|
433
416
|
const bodySections = sections.join("\n\n");
|
|
434
417
|
if (bodySections.length === 0) {
|
|
@@ -436,7 +419,7 @@ function renderSimpleReviewRequestBody(version, previousVersion, commits, plan =
|
|
|
436
419
|
}
|
|
437
420
|
return `${bodySections}\n\n${(0, changelog_js_1.renderReviewRequestFooter)()}`;
|
|
438
421
|
}
|
|
439
|
-
return (0, changelog_js_1.
|
|
422
|
+
return (0, changelog_js_1.renderReleaseNotesSection)({
|
|
440
423
|
currentVersion: previousVersion,
|
|
441
424
|
nextVersion: version,
|
|
442
425
|
commits,
|
|
@@ -455,7 +438,7 @@ async function openOrUpdateReviewRequest(cwd, branch, title, version, previousVe
|
|
|
455
438
|
baseBranch: process.env.VERSIONARY_BASE_BRANCH ?? "main",
|
|
456
439
|
headBranch: branch,
|
|
457
440
|
title,
|
|
458
|
-
body: renderSimpleReviewRequestBody(version, previousVersion, commits, plan, cwd, options.highlights ?? ""),
|
|
441
|
+
body: renderSimpleReviewRequestBody(version, previousVersion, commits, plan, cwd, options.highlights ?? "", loaded.config),
|
|
459
442
|
labels: ["release"],
|
|
460
443
|
}, {
|
|
461
444
|
cwd,
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pythonVersionStrategy = void 0;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const toml_1 = __importDefault(require("@iarna/toml"));
|
|
11
|
+
const SOURCE_FILE_VERSION_PATTERN = /^(\s*__version__\s*=\s*)(["'])([^"']+)(\2)(\s*(?:#.*)?)?$/mu;
|
|
12
|
+
function isSourceFileMode(versionFile) {
|
|
13
|
+
return versionFile.toLowerCase().endsWith(".py");
|
|
14
|
+
}
|
|
15
|
+
function parsePyProject(content, versionFile) {
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = toml_1.default.parse(content);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
throw new Error(`Failed to parse ${versionFile}: ${message}`);
|
|
23
|
+
}
|
|
24
|
+
const root = parsed;
|
|
25
|
+
const project = root.project && typeof root.project === "object"
|
|
26
|
+
? root.project
|
|
27
|
+
: null;
|
|
28
|
+
const toolPoetry = root.tool &&
|
|
29
|
+
typeof root.tool === "object" &&
|
|
30
|
+
"poetry" in root.tool &&
|
|
31
|
+
typeof root.tool.poetry === "object" &&
|
|
32
|
+
root.tool.poetry !== null
|
|
33
|
+
? root.tool.poetry
|
|
34
|
+
: null;
|
|
35
|
+
return { project, toolPoetry };
|
|
36
|
+
}
|
|
37
|
+
function lookupPyProjectVersions(parsed) {
|
|
38
|
+
const projectVersionRaw = parsed.project?.version;
|
|
39
|
+
const poetryVersionRaw = parsed.toolPoetry?.version;
|
|
40
|
+
return {
|
|
41
|
+
projectVersion: typeof projectVersionRaw === "string" &&
|
|
42
|
+
projectVersionRaw.trim().length > 0
|
|
43
|
+
? projectVersionRaw.trim()
|
|
44
|
+
: null,
|
|
45
|
+
poetryVersion: typeof poetryVersionRaw === "string" && poetryVersionRaw.trim().length > 0
|
|
46
|
+
? poetryVersionRaw.trim()
|
|
47
|
+
: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function readPyProjectVersion(content, versionFile) {
|
|
51
|
+
const parsed = parsePyProject(content, versionFile);
|
|
52
|
+
const { projectVersion, poetryVersion } = lookupPyProjectVersions(parsed);
|
|
53
|
+
const version = projectVersion ?? poetryVersion;
|
|
54
|
+
if (!version) {
|
|
55
|
+
throw new Error(`${versionFile} is missing a valid "[project].version" or "[tool.poetry].version" field required by release-type "python".`);
|
|
56
|
+
}
|
|
57
|
+
return version;
|
|
58
|
+
}
|
|
59
|
+
function replaceVersionInTable(rawContent, targetTables, version) {
|
|
60
|
+
const lineEnding = rawContent.includes("\r\n") ? "\r\n" : "\n";
|
|
61
|
+
const hasFinalLineEnding = rawContent.endsWith("\n") || rawContent.endsWith("\r\n");
|
|
62
|
+
const lines = rawContent.split(/\r?\n/u);
|
|
63
|
+
const replaced = new Set();
|
|
64
|
+
let activeTable = null;
|
|
65
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
66
|
+
const line = lines[index] ?? "";
|
|
67
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/u);
|
|
68
|
+
if (sectionMatch) {
|
|
69
|
+
activeTable = sectionMatch[1]?.trim() ?? null;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!activeTable || !targetTables.has(activeTable)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (replaced.has(activeTable)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const versionMatch = line.match(/^(\s*version\s*=\s*)(["'])([^"']*)(\2)(\s*(?:#.*)?)?$/u);
|
|
79
|
+
if (!versionMatch) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const [, prefix = "", quote = '"', , , suffix = ""] = versionMatch;
|
|
83
|
+
lines[index] = `${prefix}${quote}${version}${quote}${suffix}`;
|
|
84
|
+
replaced.add(activeTable);
|
|
85
|
+
}
|
|
86
|
+
let updated = lines.join(lineEnding);
|
|
87
|
+
if (hasFinalLineEnding && !updated.endsWith(lineEnding)) {
|
|
88
|
+
updated += lineEnding;
|
|
89
|
+
}
|
|
90
|
+
if (!hasFinalLineEnding && updated.endsWith(lineEnding)) {
|
|
91
|
+
updated = updated.slice(0, -lineEnding.length);
|
|
92
|
+
}
|
|
93
|
+
return { content: updated, replaced };
|
|
94
|
+
}
|
|
95
|
+
function writePyProjectVersion(rawContent, versionFile, version) {
|
|
96
|
+
const parsed = parsePyProject(rawContent, versionFile);
|
|
97
|
+
const { projectVersion, poetryVersion } = lookupPyProjectVersions(parsed);
|
|
98
|
+
if (!projectVersion && !poetryVersion) {
|
|
99
|
+
throw new Error(`${versionFile} is missing a valid "[project].version" or "[tool.poetry].version" field required by release-type "python".`);
|
|
100
|
+
}
|
|
101
|
+
const targets = new Set();
|
|
102
|
+
if (projectVersion) {
|
|
103
|
+
targets.add("project");
|
|
104
|
+
}
|
|
105
|
+
if (poetryVersion) {
|
|
106
|
+
targets.add("tool.poetry");
|
|
107
|
+
}
|
|
108
|
+
const { content, replaced } = replaceVersionInTable(rawContent, targets, version);
|
|
109
|
+
for (const target of targets) {
|
|
110
|
+
if (!replaced.has(target)) {
|
|
111
|
+
throw new Error(`Failed to update [${target}].version in ${versionFile}; ensure the version line uses the form \`version = "X.Y.Z"\`.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return content;
|
|
115
|
+
}
|
|
116
|
+
function readSourceFileVersion(content, versionFile) {
|
|
117
|
+
const match = content.match(SOURCE_FILE_VERSION_PATTERN);
|
|
118
|
+
if (!match?.[3]) {
|
|
119
|
+
throw new Error(`${versionFile} is missing a valid \`__version__ = "X.Y.Z"\` assignment required by release-type "python".`);
|
|
120
|
+
}
|
|
121
|
+
return match[3].trim();
|
|
122
|
+
}
|
|
123
|
+
function writeSourceFileVersion(content, versionFile, version) {
|
|
124
|
+
if (!SOURCE_FILE_VERSION_PATTERN.test(content)) {
|
|
125
|
+
throw new Error(`${versionFile} is missing a valid \`__version__ = "X.Y.Z"\` assignment required by release-type "python".`);
|
|
126
|
+
}
|
|
127
|
+
return content.replace(SOURCE_FILE_VERSION_PATTERN, (_full, prefix, quote, _value, _close, suffix = "") => `${prefix}${quote}${version}${quote}${suffix ?? ""}`);
|
|
128
|
+
}
|
|
129
|
+
const LOCKFILE_SPECS = [
|
|
130
|
+
{
|
|
131
|
+
lockfile: "poetry.lock",
|
|
132
|
+
command: "poetry",
|
|
133
|
+
args: ["lock", "--no-update"],
|
|
134
|
+
installHint: "install Poetry (https://python-poetry.org/)",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
lockfile: "uv.lock",
|
|
138
|
+
command: "uv",
|
|
139
|
+
args: ["lock"],
|
|
140
|
+
installHint: "install uv (https://docs.astral.sh/uv/)",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
lockfile: "pdm.lock",
|
|
144
|
+
command: "pdm",
|
|
145
|
+
args: ["lock", "--update-reuse"],
|
|
146
|
+
installHint: "install PDM (https://pdm-project.org/)",
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
function refreshLockfile(cwd, spec) {
|
|
150
|
+
try {
|
|
151
|
+
(0, node_child_process_1.execFileSync)(spec.command, [...spec.args], {
|
|
152
|
+
cwd,
|
|
153
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
158
|
+
throw new Error(`Failed to refresh ${spec.lockfile} via "${spec.command} ${spec.args.join(" ")}". Ensure ${spec.command} is on PATH (${spec.installHint}) or remove ${spec.lockfile} from the working tree. Details: ${message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function readPyProjectFromCwd(cwd) {
|
|
162
|
+
const target = node_path_1.default.join(cwd, "pyproject.toml");
|
|
163
|
+
if (!node_fs_1.default.existsSync(target)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const content = node_fs_1.default.readFileSync(target, "utf8");
|
|
167
|
+
return { parsed: parsePyProject(content, "pyproject.toml"), rawPath: target };
|
|
168
|
+
}
|
|
169
|
+
exports.pythonVersionStrategy = {
|
|
170
|
+
name: "python",
|
|
171
|
+
getVersionFile(config) {
|
|
172
|
+
return config["version-file"] ?? "pyproject.toml";
|
|
173
|
+
},
|
|
174
|
+
validateProject(cwd, config) {
|
|
175
|
+
const versionFile = this.getVersionFile(config);
|
|
176
|
+
const versionPath = node_path_1.default.join(cwd, versionFile);
|
|
177
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const content = node_fs_1.default.readFileSync(versionPath, "utf8");
|
|
182
|
+
if (isSourceFileMode(versionFile)) {
|
|
183
|
+
readSourceFileVersion(content, versionFile);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
readPyProjectVersion(content, versionFile);
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return error instanceof Error ? error.message : String(error);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
readVersion(cwd, config) {
|
|
195
|
+
const versionFile = this.getVersionFile(config);
|
|
196
|
+
const versionPath = node_path_1.default.join(cwd, versionFile);
|
|
197
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
198
|
+
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
199
|
+
}
|
|
200
|
+
const content = node_fs_1.default.readFileSync(versionPath, "utf8");
|
|
201
|
+
return isSourceFileMode(versionFile)
|
|
202
|
+
? readSourceFileVersion(content, versionFile)
|
|
203
|
+
: readPyProjectVersion(content, versionFile);
|
|
204
|
+
},
|
|
205
|
+
writeVersion(cwd, config, version) {
|
|
206
|
+
const versionFile = this.getVersionFile(config);
|
|
207
|
+
const versionPath = node_path_1.default.join(cwd, versionFile);
|
|
208
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
209
|
+
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
210
|
+
}
|
|
211
|
+
const existing = node_fs_1.default.readFileSync(versionPath, "utf8");
|
|
212
|
+
const updated = isSourceFileMode(versionFile)
|
|
213
|
+
? writeSourceFileVersion(existing, versionFile, version)
|
|
214
|
+
: writePyProjectVersion(existing, versionFile, version);
|
|
215
|
+
node_fs_1.default.writeFileSync(versionPath, updated, "utf8");
|
|
216
|
+
return [versionFile];
|
|
217
|
+
},
|
|
218
|
+
readPackageName(cwd, _config) {
|
|
219
|
+
const found = readPyProjectFromCwd(cwd);
|
|
220
|
+
if (!found) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const projectName = found.parsed.project?.name;
|
|
224
|
+
if (typeof projectName === "string" && projectName.trim().length > 0) {
|
|
225
|
+
return projectName.trim();
|
|
226
|
+
}
|
|
227
|
+
const poetryName = found.parsed.toolPoetry?.name;
|
|
228
|
+
if (typeof poetryName === "string" && poetryName.trim().length > 0) {
|
|
229
|
+
return poetryName.trim();
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
},
|
|
233
|
+
finalizeVersionWrites(cwd, _writes, _context) {
|
|
234
|
+
const refreshed = [];
|
|
235
|
+
for (const spec of LOCKFILE_SPECS) {
|
|
236
|
+
const lockPath = node_path_1.default.join(cwd, spec.lockfile);
|
|
237
|
+
if (!node_fs_1.default.existsSync(lockPath)) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
refreshLockfile(cwd, spec);
|
|
241
|
+
refreshed.push(spec.lockfile);
|
|
242
|
+
}
|
|
243
|
+
return refreshed.sort((a, b) => a.localeCompare(b));
|
|
244
|
+
},
|
|
245
|
+
};
|
package/dist/strategy/resolve.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.listKnownReleaseTypes = listKnownReleaseTypes;
|
|
|
4
4
|
exports.resolveVersionStrategy = resolveVersionStrategy;
|
|
5
5
|
const latex_js_1 = require("./latex.js");
|
|
6
6
|
const node_js_1 = require("./node.js");
|
|
7
|
+
const python_js_1 = require("./python.js");
|
|
7
8
|
const r_js_1 = require("./r.js");
|
|
8
9
|
const rust_js_1 = require("./rust.js");
|
|
9
10
|
const simple_js_1 = require("./simple.js");
|
|
@@ -13,6 +14,7 @@ const strategyRegistry = {
|
|
|
13
14
|
node: node_js_1.nodeVersionStrategy,
|
|
14
15
|
rust: rust_js_1.rustVersionStrategy,
|
|
15
16
|
r: r_js_1.rVersionStrategy,
|
|
17
|
+
python: python_js_1.pythonVersionStrategy,
|
|
16
18
|
};
|
|
17
19
|
function listKnownReleaseTypes() {
|
|
18
20
|
return Object.keys(strategyRegistry).sort((a, b) => a.localeCompare(b));
|