versionary 1.2.0 → 1.4.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/dist/config/load-config.js +44 -2
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.js +26 -2
- package/dist/release/plan.js +11 -4
- package/dist/release/release.d.ts +1 -0
- package/dist/release/release.js +6 -1
- package/dist/release/semver.js +0 -3
- package/dist/release/verify-project.js +27 -22
- package/dist/strategy/python.js +46 -11
- package/dist/types/config.d.ts +4 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ const SUPPORTED_FILES = [
|
|
|
7
7
|
{ file: "versionary.jsonc", format: "jsonc" },
|
|
8
8
|
{ file: "versionary.json", format: "json" },
|
|
9
9
|
];
|
|
10
|
+
const warnedDeprecatedConfigPaths = new Set();
|
|
10
11
|
function parseConfig(raw, format) {
|
|
11
12
|
if (format === "json" || format === "jsonc") {
|
|
12
13
|
return parseJsonc(raw);
|
|
@@ -16,6 +17,43 @@ function parseConfig(raw, format) {
|
|
|
16
17
|
function isRecord(value) {
|
|
17
18
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
18
19
|
}
|
|
20
|
+
function normalizeConfig(config) {
|
|
21
|
+
const { "bump-minor-pre-major": rootBumpMinorPreMajor, packages, ...rootConfig } = config;
|
|
22
|
+
let usedDeprecatedPreMajorKey = rootBumpMinorPreMajor !== undefined;
|
|
23
|
+
const normalizedPackages = packages
|
|
24
|
+
? Object.fromEntries(Object.entries(packages).map(([packagePath, packageConfig]) => {
|
|
25
|
+
const { "bump-minor-pre-major": packageBumpMinorPreMajor, ...canonicalPackageConfig } = packageConfig;
|
|
26
|
+
usedDeprecatedPreMajorKey ||= packageBumpMinorPreMajor !== undefined;
|
|
27
|
+
return [
|
|
28
|
+
packagePath,
|
|
29
|
+
{
|
|
30
|
+
...canonicalPackageConfig,
|
|
31
|
+
...(packageBumpMinorPreMajor === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { "allow-stable-major": !packageBumpMinorPreMajor }),
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}))
|
|
37
|
+
: undefined;
|
|
38
|
+
return {
|
|
39
|
+
config: {
|
|
40
|
+
...rootConfig,
|
|
41
|
+
...(rootBumpMinorPreMajor === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: { "allow-stable-major": !rootBumpMinorPreMajor }),
|
|
44
|
+
...(normalizedPackages ? { packages: normalizedPackages } : {}),
|
|
45
|
+
},
|
|
46
|
+
usedDeprecatedPreMajorKey,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function warnForDeprecatedConfig(path) {
|
|
50
|
+
// Release commands can load the same configuration in several stages.
|
|
51
|
+
if (warnedDeprecatedConfigPaths.has(path)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
warnedDeprecatedConfigPaths.add(path);
|
|
55
|
+
console.warn('Warning: "bump-minor-pre-major" is deprecated; use "allow-stable-major" with the inverse value instead.');
|
|
56
|
+
}
|
|
19
57
|
function validateReleaseTypes(config) {
|
|
20
58
|
try {
|
|
21
59
|
resolveVersionStrategy(config);
|
|
@@ -67,10 +105,14 @@ export function loadConfig(cwd = process.cwd()) {
|
|
|
67
105
|
throw new Error('The "plugins" config key is no longer supported. Versionary uses built-in integrations only.');
|
|
68
106
|
}
|
|
69
107
|
const validated = configSchema.parse(parsed);
|
|
70
|
-
|
|
108
|
+
const normalized = normalizeConfig(validated);
|
|
109
|
+
if (normalized.usedDeprecatedPreMajorKey) {
|
|
110
|
+
warnForDeprecatedConfig(found.path);
|
|
111
|
+
}
|
|
112
|
+
validateReleaseTypes(normalized.config);
|
|
71
113
|
return {
|
|
72
114
|
path: found.path,
|
|
73
115
|
format: found.format,
|
|
74
|
-
config:
|
|
116
|
+
config: normalized.config,
|
|
75
117
|
};
|
|
76
118
|
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare const configSchema: z.ZodObject<{
|
|
|
39
39
|
"markdown-changelog": "markdown-changelog";
|
|
40
40
|
"r-news": "r-news";
|
|
41
41
|
}>>;
|
|
42
|
+
"release-draft": z.ZodOptional<z.ZodBoolean>;
|
|
43
|
+
"bump-minor-pre-major": z.ZodOptional<z.ZodBoolean>;
|
|
42
44
|
"allow-stable-major": z.ZodOptional<z.ZodBoolean>;
|
|
43
45
|
"exclude-paths": z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
44
46
|
"extra-files": z.ZodOptional<z.ZodArray<z.ZodObject<{
|
package/dist/config/schema.js
CHANGED
|
@@ -56,6 +56,10 @@ const artifactRuleSchema = z
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
|
+
const deprecatedBumpMinorPreMajorSchema = z.boolean().optional().meta({
|
|
60
|
+
deprecated: true,
|
|
61
|
+
description: 'Deprecated. Use "allow-stable-major" with the inverse value instead.',
|
|
62
|
+
});
|
|
59
63
|
const packageSchema = z
|
|
60
64
|
.object({
|
|
61
65
|
"release-type": z
|
|
@@ -64,12 +68,24 @@ const packageSchema = z
|
|
|
64
68
|
"package-name": z.string().optional(),
|
|
65
69
|
"changelog-file": z.string().optional(),
|
|
66
70
|
"changelog-format": z.enum(["markdown-changelog", "r-news"]).optional(),
|
|
71
|
+
"release-draft": z.boolean().optional(),
|
|
72
|
+
"bump-minor-pre-major": deprecatedBumpMinorPreMajorSchema,
|
|
67
73
|
"allow-stable-major": z.boolean().optional(),
|
|
68
74
|
"exclude-paths": z.array(z.string()).optional(),
|
|
69
75
|
"extra-files": z.array(artifactRuleSchema).optional(),
|
|
70
76
|
follows: z.array(z.string().min(1)).optional(),
|
|
71
77
|
})
|
|
72
|
-
.strict()
|
|
78
|
+
.strict()
|
|
79
|
+
.superRefine((value, ctx) => {
|
|
80
|
+
if (value["bump-minor-pre-major"] !== undefined &&
|
|
81
|
+
value["allow-stable-major"] !== undefined) {
|
|
82
|
+
ctx.addIssue({
|
|
83
|
+
code: z.ZodIssueCode.custom,
|
|
84
|
+
message: '"bump-minor-pre-major" and "allow-stable-major" are inverse aliases; configure only one.',
|
|
85
|
+
path: ["bump-minor-pre-major"],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
73
89
|
export const configSchema = z
|
|
74
90
|
.object({
|
|
75
91
|
$schema: z.string().optional(),
|
|
@@ -87,7 +103,7 @@ export const configSchema = z
|
|
|
87
103
|
"baseline-file": z.string().optional(),
|
|
88
104
|
"bootstrap-sha": z.string().optional(),
|
|
89
105
|
"monorepo-mode": z.enum(["independent", "fixed"]).optional(),
|
|
90
|
-
"bump-minor-pre-major":
|
|
106
|
+
"bump-minor-pre-major": deprecatedBumpMinorPreMajorSchema,
|
|
91
107
|
"allow-stable-major": z.boolean().optional(),
|
|
92
108
|
"include-commit-authors": z.boolean().optional(),
|
|
93
109
|
"exclude-paths": z.array(z.string()).optional(),
|
|
@@ -98,6 +114,14 @@ export const configSchema = z
|
|
|
98
114
|
})
|
|
99
115
|
.strict()
|
|
100
116
|
.superRefine((value, ctx) => {
|
|
117
|
+
if (value["bump-minor-pre-major"] !== undefined &&
|
|
118
|
+
value["allow-stable-major"] !== undefined) {
|
|
119
|
+
ctx.addIssue({
|
|
120
|
+
code: z.ZodIssueCode.custom,
|
|
121
|
+
message: '"bump-minor-pre-major" and "allow-stable-major" are inverse aliases; configure only one.',
|
|
122
|
+
path: ["bump-minor-pre-major"],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
101
125
|
const packages = value.packages;
|
|
102
126
|
if (value["separate-release-prs"]) {
|
|
103
127
|
if (!packages || Object.keys(packages).length === 0) {
|
package/dist/release/plan.js
CHANGED
|
@@ -10,6 +10,12 @@ import { readBaselineSha, readReleaseTargets } from "./state.js";
|
|
|
10
10
|
function getMode(configMode) {
|
|
11
11
|
return configMode ?? "independent";
|
|
12
12
|
}
|
|
13
|
+
function resolveAllowStableMajor(config, packageConfig) {
|
|
14
|
+
if (packageConfig?.["allow-stable-major"] !== undefined) {
|
|
15
|
+
return packageConfig["allow-stable-major"];
|
|
16
|
+
}
|
|
17
|
+
return config["allow-stable-major"] ?? false;
|
|
18
|
+
}
|
|
13
19
|
export function getChangelogDefaults(config) {
|
|
14
20
|
const changelogFormat = config["changelog-format"] ??
|
|
15
21
|
config.defaultChangelogFormat ??
|
|
@@ -50,9 +56,8 @@ export function createReleasePlan(cwd = process.cwd()) {
|
|
|
50
56
|
const releaseBranchPrefix = loaded.config["release-branch"] ?? "versionary/release";
|
|
51
57
|
const baselineSha = readBaselineSha(cwd) ?? loaded.config["bootstrap-sha"] ?? null;
|
|
52
58
|
const releaseTargetByPath = new Map(readReleaseTargets(cwd).map((target) => [target.path, target]));
|
|
53
|
-
const allowStableMajor = loaded.config
|
|
54
|
-
const allowStableMajorForPath = (packagePath) => loaded.config.packages?.[packagePath]
|
|
55
|
-
allowStableMajor;
|
|
59
|
+
const allowStableMajor = resolveAllowStableMajor(loaded.config);
|
|
60
|
+
const allowStableMajorForPath = (packagePath) => resolveAllowStableMajor(loaded.config, loaded.config.packages?.[packagePath]);
|
|
56
61
|
const monorepoMode = getMode(loaded.config["monorepo-mode"]);
|
|
57
62
|
const buildPackagePlan = (pkg) => {
|
|
58
63
|
const packageContext = resolvePackageStrategyContext(loaded.config, pkg.path, pkg.config);
|
|
@@ -429,7 +434,9 @@ export function createReleasePlan(cwd = process.cwd()) {
|
|
|
429
434
|
const fixedNextVersion = rootOverridden
|
|
430
435
|
? rootPackagePlan.nextVersion
|
|
431
436
|
: analyzedFixedType
|
|
432
|
-
? bumpVersion(fixedBaseVersion, analyzedFixedType, {
|
|
437
|
+
? bumpVersion(fixedBaseVersion, analyzedFixedType, {
|
|
438
|
+
allowStableMajor,
|
|
439
|
+
})
|
|
433
440
|
: null;
|
|
434
441
|
// Fixed mode can promote unchanged dependencies into the release, so the
|
|
435
442
|
// final shared version set must drive dependency attribution.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { VersionaryConfig } from "../types/config.js";
|
|
2
2
|
import type { VersionaryPluginContext } from "../types/plugins.js";
|
|
3
3
|
export declare function resolveTargetPackageName(cwd: string, config: VersionaryConfig, targetPath: string): string | undefined;
|
|
4
|
+
export declare function resolveTargetReleaseDraft(config: VersionaryConfig, targetPath: string): boolean;
|
|
4
5
|
export declare function extractReleaseNotes(content: string, version: string, changelogFormat: "markdown-changelog" | "r-news"): string;
|
|
5
6
|
export declare function extractClosingReferencesFromNotes(notes: string): number[];
|
|
6
7
|
export declare function resolveTargetChangelogFile(config: VersionaryConfig, rootChangelogFile: string, targetPath: string): string;
|
package/dist/release/release.js
CHANGED
|
@@ -22,6 +22,11 @@ export function resolveTargetPackageName(cwd, config, targetPath) {
|
|
|
22
22
|
}
|
|
23
23
|
return resolved;
|
|
24
24
|
}
|
|
25
|
+
export function resolveTargetReleaseDraft(config, targetPath) {
|
|
26
|
+
return (config.packages?.[targetPath]?.["release-draft"] ??
|
|
27
|
+
config["release-draft"] ??
|
|
28
|
+
false);
|
|
29
|
+
}
|
|
25
30
|
export function extractReleaseNotes(content, version, changelogFormat) {
|
|
26
31
|
const lines = content.split("\n");
|
|
27
32
|
const shortVersion = version.replace(/\.\d+$/u, "");
|
|
@@ -198,7 +203,7 @@ export async function runReleaseDetailed(cwd = process.cwd(), options = {}) {
|
|
|
198
203
|
tag: target.tag,
|
|
199
204
|
version: target.version,
|
|
200
205
|
notes: releaseNotes,
|
|
201
|
-
draft: loaded.config
|
|
206
|
+
draft: resolveTargetReleaseDraft(loaded.config, target.path),
|
|
202
207
|
makeLatest: target.path === "." ? "true" : "false",
|
|
203
208
|
}, {
|
|
204
209
|
createReleaseMetadata: (input) => scmClient.createReleaseMetadata(input, {
|
package/dist/release/semver.js
CHANGED
|
@@ -142,9 +142,6 @@ export function bumpVersion(current, releaseType, options = {}) {
|
|
|
142
142
|
if (parsed.major === 0 && !allowStableMajor) {
|
|
143
143
|
return `0.${parsed.minor + 1}.0`;
|
|
144
144
|
}
|
|
145
|
-
if (parsed.major === 0 && allowStableMajor) {
|
|
146
|
-
return "1.0.0";
|
|
147
|
-
}
|
|
148
145
|
return `${parsed.major + 1}.0.0`;
|
|
149
146
|
}
|
|
150
147
|
if (releaseType === "minor") {
|
|
@@ -12,32 +12,37 @@ export function verifyProject(cwd = process.cwd()) {
|
|
|
12
12
|
details: `Loaded ${path.basename(config.path)} (${config.format})`,
|
|
13
13
|
category: "config",
|
|
14
14
|
});
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
category: "version-files",
|
|
23
|
-
remediation: exists
|
|
24
|
-
? undefined
|
|
25
|
-
: `Create ${versionFile} or set "version-file" to the correct path for your release strategy.`,
|
|
26
|
-
});
|
|
27
|
-
if (exists) {
|
|
28
|
-
const validationError = strategy.validateProject?.(cwd, config.config);
|
|
15
|
+
const packages = Object.entries(config.config.packages ?? {});
|
|
16
|
+
const shouldValidateRoot = packages.length === 0 ||
|
|
17
|
+
packages.some(([packagePath]) => packagePath === ".");
|
|
18
|
+
if (shouldValidateRoot) {
|
|
19
|
+
const strategy = resolveVersionStrategy(config.config);
|
|
20
|
+
const versionFile = strategy.getVersionFile(config.config);
|
|
21
|
+
const exists = fs.existsSync(path.join(cwd, versionFile));
|
|
29
22
|
checks.push({
|
|
30
|
-
name: `
|
|
31
|
-
ok:
|
|
32
|
-
details:
|
|
23
|
+
name: `version-file:${versionFile}`,
|
|
24
|
+
ok: exists,
|
|
25
|
+
details: exists ? "Version file exists" : `Missing ${versionFile}`,
|
|
33
26
|
category: "version-files",
|
|
34
|
-
remediation:
|
|
35
|
-
?
|
|
36
|
-
:
|
|
27
|
+
remediation: exists
|
|
28
|
+
? undefined
|
|
29
|
+
: `Create ${versionFile} or set "version-file" to the correct path for your release strategy.`,
|
|
37
30
|
});
|
|
31
|
+
if (exists) {
|
|
32
|
+
const validationError = strategy.validateProject?.(cwd, config.config);
|
|
33
|
+
checks.push({
|
|
34
|
+
name: `strategy-validate:${strategy.name}`,
|
|
35
|
+
ok: !validationError,
|
|
36
|
+
details: validationError ?? "Strategy-level validation passed",
|
|
37
|
+
category: "version-files",
|
|
38
|
+
remediation: validationError
|
|
39
|
+
? `Fix strategy-specific version metadata in ${versionFile} for release-type "${strategy.name}".`
|
|
40
|
+
: undefined,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
|
-
if (
|
|
40
|
-
for (const [pkgPathRaw, packageConfig] of
|
|
44
|
+
if (packages.length > 0) {
|
|
45
|
+
for (const [pkgPathRaw, packageConfig] of packages) {
|
|
41
46
|
const pkgPath = path.join(cwd, pkgPathRaw);
|
|
42
47
|
const exists = fs.existsSync(pkgPath);
|
|
43
48
|
checks.push({
|
package/dist/strategy/python.js
CHANGED
|
@@ -140,7 +140,7 @@ const LOCKFILE_SPECS = [
|
|
|
140
140
|
installHint: "install PDM (https://pdm-project.org/)",
|
|
141
141
|
},
|
|
142
142
|
];
|
|
143
|
-
function refreshLockfile(cwd, spec) {
|
|
143
|
+
function refreshLockfile(cwd, spec, lockfilePath) {
|
|
144
144
|
try {
|
|
145
145
|
execFileSync(spec.command, [...spec.args], {
|
|
146
146
|
cwd,
|
|
@@ -149,7 +149,33 @@ function refreshLockfile(cwd, spec) {
|
|
|
149
149
|
}
|
|
150
150
|
catch (error) {
|
|
151
151
|
const message = error instanceof Error ? error.message : String(error);
|
|
152
|
-
throw new Error(`Failed to refresh ${
|
|
152
|
+
throw new Error(`Failed to refresh ${lockfilePath} via "${spec.command} ${spec.args.join(" ")}". Ensure ${spec.command} is on PATH (${spec.installHint}) or remove ${lockfilePath} from the working tree. Details: ${message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function normalizeRelativePath(base, target) {
|
|
156
|
+
return path.relative(base, target).replaceAll("\\", "/");
|
|
157
|
+
}
|
|
158
|
+
function findNearestLockfileDirectory(cwd, packageDirectory, lockfile) {
|
|
159
|
+
const root = path.resolve(cwd);
|
|
160
|
+
let current = path.resolve(packageDirectory);
|
|
161
|
+
const relativePackageDirectory = path.relative(root, current);
|
|
162
|
+
if (relativePackageDirectory === ".." ||
|
|
163
|
+
relativePackageDirectory.startsWith(`..${path.sep}`) ||
|
|
164
|
+
path.isAbsolute(relativePackageDirectory)) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
while (true) {
|
|
168
|
+
if (fs.existsSync(path.join(current, lockfile))) {
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
if (current === root) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const parent = path.dirname(current);
|
|
175
|
+
if (parent === current) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
current = parent;
|
|
153
179
|
}
|
|
154
180
|
}
|
|
155
181
|
function readPyProjectFromCwd(cwd) {
|
|
@@ -301,16 +327,25 @@ export const pythonVersionStrategy = {
|
|
|
301
327
|
}
|
|
302
328
|
return null;
|
|
303
329
|
},
|
|
304
|
-
finalizeVersionWrites(cwd,
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
330
|
+
finalizeVersionWrites(cwd, writes, _context) {
|
|
331
|
+
const packageDirectories = [
|
|
332
|
+
...new Set(writes.map((write) => path.resolve(cwd, write.packagePath === "." ? "" : write.packagePath))),
|
|
333
|
+
].sort((a, b) => a.localeCompare(b));
|
|
334
|
+
const lockfiles = new Map();
|
|
335
|
+
for (const packageDirectory of packageDirectories) {
|
|
336
|
+
for (const spec of LOCKFILE_SPECS) {
|
|
337
|
+
const directory = findNearestLockfileDirectory(cwd, packageDirectory, spec.lockfile);
|
|
338
|
+
if (!directory) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const relativePath = normalizeRelativePath(cwd, path.join(directory, spec.lockfile));
|
|
342
|
+
lockfiles.set(relativePath, { directory, spec });
|
|
310
343
|
}
|
|
311
|
-
refreshLockfile(cwd, spec);
|
|
312
|
-
refreshed.push(spec.lockfile);
|
|
313
344
|
}
|
|
314
|
-
|
|
345
|
+
const refreshed = [...lockfiles.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
346
|
+
for (const [relativePath, { directory, spec }] of refreshed) {
|
|
347
|
+
refreshLockfile(directory, spec, relativePath);
|
|
348
|
+
}
|
|
349
|
+
return refreshed.map(([relativePath]) => relativePath);
|
|
315
350
|
},
|
|
316
351
|
};
|
package/dist/types/config.d.ts
CHANGED
|
@@ -14,6 +14,9 @@ export interface VersionaryPackage {
|
|
|
14
14
|
"package-name"?: string;
|
|
15
15
|
"changelog-file"?: string;
|
|
16
16
|
"changelog-format"?: VersionaryChangelogFormat;
|
|
17
|
+
"release-draft"?: boolean;
|
|
18
|
+
/** @deprecated Use `allow-stable-major` with the inverse value. */
|
|
19
|
+
"bump-minor-pre-major"?: boolean;
|
|
17
20
|
"allow-stable-major"?: boolean;
|
|
18
21
|
"exclude-paths"?: string[];
|
|
19
22
|
"extra-files"?: VersionaryArtifactRule[];
|
|
@@ -32,6 +35,7 @@ export interface VersionaryConfig {
|
|
|
32
35
|
"baseline-file"?: string;
|
|
33
36
|
"bootstrap-sha"?: string;
|
|
34
37
|
"monorepo-mode"?: "independent" | "fixed";
|
|
38
|
+
/** @deprecated Use `allow-stable-major` with the inverse value. */
|
|
35
39
|
"bump-minor-pre-major"?: boolean;
|
|
36
40
|
"allow-stable-major"?: boolean;
|
|
37
41
|
"include-commit-authors"?: boolean;
|