versionary 0.31.0 → 1.0.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.
@@ -30,11 +30,3 @@ export interface RunReleaseOptions {
30
30
  "dry-run"?: boolean;
31
31
  }
32
32
  export declare function runReleaseDetailed(cwd?: string, options?: RunReleaseOptions): Promise<RunReleaseResult>;
33
- /** @deprecated Use RunReleaseResult. */
34
- export type SimpleRunReleaseResult = RunReleaseResult;
35
- /** @deprecated Use RunReleaseOptions. */
36
- export type RunSimpleReleaseOptions = RunReleaseOptions;
37
- /** @deprecated Use runRelease. */
38
- export declare function runSimpleRelease(cwd?: string): Promise<string>;
39
- /** @deprecated Use runReleaseDetailed. */
40
- export declare function runSimpleReleaseDetailed(cwd?: string, options?: RunSimpleReleaseOptions): Promise<SimpleRunReleaseResult>;
@@ -224,11 +224,3 @@ export async function runReleaseDetailed(cwd = process.cwd(), options = {}) {
224
224
  message: `Published releases ${published.join(", ")}`,
225
225
  };
226
226
  }
227
- /** @deprecated Use runRelease. */
228
- export async function runSimpleRelease(cwd = process.cwd()) {
229
- return runRelease(cwd);
230
- }
231
- /** @deprecated Use runReleaseDetailed. */
232
- export async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
233
- return runReleaseDetailed(cwd, options);
234
- }
@@ -49,15 +49,7 @@ export function getBaselineStatePath(cwd) {
49
49
  if (configured) {
50
50
  return path.join(cwd, configured);
51
51
  }
52
- const preferred = path.join(cwd, ".versionary-manifest.json");
53
- if (fs.existsSync(preferred)) {
54
- return preferred;
55
- }
56
- const legacy = path.join(cwd, "versionary.versions.json");
57
- if (fs.existsSync(legacy)) {
58
- return legacy;
59
- }
60
- return preferred;
52
+ return path.join(cwd, ".versionary-manifest.json");
61
53
  }
62
54
  export function readBaselineSha(cwd = process.cwd()) {
63
55
  const filePath = getBaselineStatePath(cwd);
@@ -80,15 +72,14 @@ export function readReleaseTargets(cwd = process.cwd()) {
80
72
  }
81
73
  // The publish set introduced by the current release PR. `release` consumes this
82
74
  // so it only publishes/announces what this PR bumped, not every package in the
83
- // accumulated baseline. Falls back to the accumulated targets for manifests
84
- // written before this key existed (legacy compatibility).
75
+ // accumulated baseline.
85
76
  export function readPendingReleaseTargets(cwd = process.cwd()) {
86
77
  const filePath = getBaselineStatePath(cwd);
87
78
  if (!fs.existsSync(filePath)) {
88
79
  return [];
89
80
  }
90
81
  const parsed = parseStateFile(fs.readFileSync(filePath, "utf8"), filePath);
91
- return (parsed[PENDING_RELEASE_TARGETS_KEY] ?? parsed[RELEASE_TARGETS_KEY] ?? []);
82
+ return parsed[PENDING_RELEASE_TARGETS_KEY] ?? [];
92
83
  }
93
84
  export function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets) {
94
85
  const baselineShaValue = sha ??
@@ -71,6 +71,17 @@ export function compositeVersionStrategy(strategies) {
71
71
  return primary.readPackageName?.(cwd, config) ?? null;
72
72
  };
73
73
  }
74
+ if (primary.isPublishable || secondaries.some((s) => s.isPublishable)) {
75
+ composite.isPublishable = (cwd, pkg) => {
76
+ // Only strategies that recognize this package's version file have an
77
+ // opinion; among those, one publishing facet is enough to expose the
78
+ // package on a registry.
79
+ const opinions = [primary, ...secondaries]
80
+ .map((strategy) => strategy.isPublishable?.(cwd, pkg))
81
+ .filter((opinion) => typeof opinion === "boolean");
82
+ return opinions.length === 0 ? undefined : opinions.some(Boolean);
83
+ };
84
+ }
74
85
  if (primary.propagateDependentPatchImpacts ||
75
86
  secondaries.some((strategy) => strategy.propagateDependentPatchImpacts)) {
76
87
  composite.propagateDependentPatchImpacts = (cwd, packages) => {
@@ -71,4 +71,19 @@ export const nodeVersionStrategy = {
71
71
  }
72
72
  return name.trim();
73
73
  },
74
+ isPublishable(cwd, pkg) {
75
+ if (path.basename(pkg.versionFile) !== "package.json") {
76
+ return undefined;
77
+ }
78
+ const versionPath = path.join(cwd, pkg.versionFile);
79
+ if (!fs.existsSync(versionPath)) {
80
+ return true;
81
+ }
82
+ try {
83
+ return readJsonFile(versionPath).private !== true;
84
+ }
85
+ catch {
86
+ return true;
87
+ }
88
+ },
74
89
  };
@@ -253,13 +253,53 @@ function collectRustTargetManifests(cwd, versionFile, includeWorkspaceMembers) {
253
253
  : `"${versionFile}" has neither [package] nor [workspace].`;
254
254
  throw new Error(`Configured rust target "${versionFile}" is not a Rust crate manifest. ${detail} Either remove the "packages" config so the workspace is auto-discovered, or point a package at a member crate path (e.g. "packages": { "crates/foo": {} }).`);
255
255
  }
256
- function isWorkspaceInheritedVersion(rawVersion) {
256
+ function isWorkspaceInheritedValue(rawVersion) {
257
257
  if (!rawVersion || typeof rawVersion !== "object") {
258
258
  return false;
259
259
  }
260
260
  const versionRecord = rawVersion;
261
261
  return versionRecord.workspace === true;
262
262
  }
263
+ function isPublishValueUnpublishable(rawPublish) {
264
+ // Cargo treats both `publish = false` and an empty registry list as
265
+ // "never publish"; a non-empty list still reaches a registry.
266
+ return (rawPublish === false ||
267
+ (Array.isArray(rawPublish) && rawPublish.length === 0));
268
+ }
269
+ function readManifestPublishable(cwd, manifest) {
270
+ const manifestPath = path.join(cwd, manifest);
271
+ if (!fs.existsSync(manifestPath)) {
272
+ return true;
273
+ }
274
+ const cargoTomlRaw = fs.readFileSync(manifestPath, "utf8");
275
+ let packageTable;
276
+ try {
277
+ ({ packageTable } = parseCargoManifest(manifest, cargoTomlRaw));
278
+ }
279
+ catch {
280
+ return true;
281
+ }
282
+ if (!packageTable) {
283
+ return true;
284
+ }
285
+ const rawPublish = packageTable.publish;
286
+ if (!isWorkspaceInheritedValue(rawPublish)) {
287
+ return !isPublishValueUnpublishable(rawPublish);
288
+ }
289
+ let workspaceManifest;
290
+ try {
291
+ workspaceManifest = findWorkspaceManifestForMember(cwd, manifest);
292
+ }
293
+ catch {
294
+ return true;
295
+ }
296
+ const workspaceRaw = fs.readFileSync(path.join(cwd, workspaceManifest), "utf8");
297
+ const { workspaceTable } = parseCargoManifest(workspaceManifest, workspaceRaw);
298
+ const workspacePackage = workspaceTable?.package && typeof workspaceTable.package === "object"
299
+ ? workspaceTable.package
300
+ : null;
301
+ return !isPublishValueUnpublishable(workspacePackage?.publish);
302
+ }
263
303
  function readWorkspacePackageVersion(cargoTomlRaw, versionFile) {
264
304
  const { workspaceTable } = parseCargoManifest(versionFile, cargoTomlRaw);
265
305
  if (!workspaceTable || typeof workspaceTable !== "object") {
@@ -314,7 +354,7 @@ function readResolvedCargoVersion(cwd, manifest, cargoTomlRaw) {
314
354
  if (typeof rawVersion === "string" && rawVersion.trim().length > 0) {
315
355
  return rawVersion.trim();
316
356
  }
317
- if (!isWorkspaceInheritedVersion(rawVersion)) {
357
+ if (!isWorkspaceInheritedValue(rawVersion)) {
318
358
  throw new Error(`${manifest} has invalid [package].version. Expected a non-empty SemVer string or version.workspace = true.`);
319
359
  }
320
360
  const workspaceManifest = findWorkspaceManifestForMember(cwd, manifest);
@@ -425,7 +465,7 @@ function usesWorkspaceInheritedVersion(cargoTomlRaw, versionFile) {
425
465
  return false;
426
466
  }
427
467
  const rawVersion = packageTable.version;
428
- return isWorkspaceInheritedVersion(rawVersion);
468
+ return isWorkspaceInheritedValue(rawVersion);
429
469
  }
430
470
  function isDependencySection(section) {
431
471
  if (ROOT_DEPENDENCY_SECTIONS.has(section)) {
@@ -733,6 +773,13 @@ export const rustVersionStrategy = {
733
773
  }
734
774
  return readCargoPackageName(cargoTomlRaw, selectedManifest);
735
775
  },
776
+ isPublishable(cwd, pkg) {
777
+ const manifest = normalizeSlashPath(pkg.versionFile);
778
+ if (path.posix.basename(manifest) !== "Cargo.toml") {
779
+ return undefined;
780
+ }
781
+ return readManifestPublishable(cwd, manifest);
782
+ },
736
783
  propagateDependentPatchImpacts(cwd, packages) {
737
784
  const manifestToVersion = {};
738
785
  const candidateManifests = [];
@@ -22,6 +22,17 @@ export interface VersionStrategy {
22
22
  writeVersion(cwd: string, config: VersionaryConfig, version: string): string[];
23
23
  validateProject?(cwd: string, config: VersionaryConfig): string | null;
24
24
  readPackageName?(cwd: string, config: VersionaryConfig): string | null;
25
+ /**
26
+ * Whether this package is published to a registry. A package that never
27
+ * reaches a registry cannot leave a stale published copy behind, so release
28
+ * planning exempts it from published-dependency freshness enforcement.
29
+ *
30
+ * Returns `undefined` to abstain, which a strategy must do for a version
31
+ * file it does not recognize — otherwise, composed with another strategy, it
32
+ * would outvote the one that actually owns the manifest. Strategies that
33
+ * omit this hook entirely are assumed to publish everything.
34
+ */
35
+ isPublishable?(cwd: string, pkg: StrategyPackagePlanContext): boolean | undefined;
25
36
  propagateDependentPatchImpacts?(cwd: string, packages: StrategyPackagePlanContext[]): string[];
26
37
  finalizeVersionWrites?(cwd: string, writes: StrategyVersionWriteContext[], context: StrategyFinalizeContext): string[];
27
38
  }
@@ -5,8 +5,8 @@ export interface VersionaryArtifactRule {
5
5
  type: "json" | "toml" | "yaml" | "nix" | "regex";
6
6
  path: string;
7
7
  "field-path"?: string;
8
- jsonpath?: string;
9
8
  pattern?: string;
9
+ replacement?: string;
10
10
  }
11
11
  export interface VersionaryPackage {
12
12
  "release-type"?: string | string[];
@@ -20,7 +20,7 @@ export interface VersionaryPackage {
20
20
  }
21
21
  export interface VersionaryConfig {
22
22
  version: 1;
23
- "review-mode"?: "direct" | "pr" | "review";
23
+ "review-mode"?: "direct" | "pr";
24
24
  "version-file"?: string;
25
25
  "changelog-file"?: string;
26
26
  "changelog-format"?: VersionaryChangelogFormat;
@@ -28,7 +28,6 @@ export interface VersionaryConfig {
28
28
  "release-reference-comments"?: ReleaseReferenceCommentsMode;
29
29
  "release-branch"?: string;
30
30
  "baseline-file"?: string;
31
- "next-release-file"?: string;
32
31
  "bootstrap-sha"?: string;
33
32
  "monorepo-mode"?: "independent" | "fixed";
34
33
  "bump-minor-pre-major"?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versionary",
3
- "version": "0.31.0",
3
+ "version": "1.0.0",
4
4
  "description": "Automatic release framework based on conventional commits and semantic versioning",
5
5
  "keywords": [
6
6
  "releasing",
@@ -27,16 +27,18 @@
27
27
  "main": "dist/index.js",
28
28
  "types": "dist/index.d.ts",
29
29
  "dependencies": {
30
- "smol-toml": "^1.4.2",
31
30
  "@octokit/rest": "^22.0.0",
32
31
  "jsonc-parser": "^3.3.1",
32
+ "smol-toml": "^1.4.2",
33
33
  "yaml": "^2.8.3",
34
34
  "zod": "^4.1.12"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/node": "^25.6.0",
37
+ "@types/node": "^26.0.1",
38
38
  "tsx": "^4.20.6",
39
- "typescript": "^6.0.3",
39
+ "typescript": "^7.0.2",
40
+ "vite": "^8.0.0",
41
+ "vitepress": "^1.6.4",
40
42
  "vitest": "^4.1.4"
41
43
  },
42
44
  "scripts": {
@@ -49,6 +51,9 @@
49
51
  "plan": "tsx src/cli/index.ts plan",
50
52
  "changelog": "tsx src/cli/index.ts changelog",
51
53
  "pr": "tsx src/cli/index.ts pr",
52
- "release": "tsx src/cli/index.ts release"
54
+ "release": "tsx src/cli/index.ts release",
55
+ "docs:dev": "vitepress dev docs",
56
+ "docs:build": "vitepress build docs",
57
+ "docs:preview": "vitepress preview docs"
53
58
  }
54
59
  }