🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

versionary

Package Overview
Dependencies
Maintainers
1
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

versionary - npm Package Compare versions

Comparing version
0.30.0
to
0.31.0
+2
dist/strategy/julia.d.ts
import type { VersionStrategy } from "./types.js";
export declare const juliaVersionStrategy: VersionStrategy;
import fs from "node:fs";
import path from "node:path";
import { parse as parseToml } from "smol-toml";
function parseProjectToml(content, versionFile) {
let parsed;
try {
parsed = parseToml(content);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to parse ${versionFile}: ${message}`);
}
if (parsed && typeof parsed === "object") {
return parsed;
}
throw new Error(`Failed to parse ${versionFile}: not a TOML table.`);
}
function readProjectVersion(content, versionFile) {
const parsed = parseProjectToml(content, versionFile);
const version = parsed.version;
if (typeof version === "string" && version.trim().length > 0) {
return version.trim();
}
throw new Error(`${versionFile} is missing a valid root "version" field required by release-type "julia".`);
}
function writeProjectVersion(rawContent, versionFile, version) {
const lineEnding = rawContent.includes("\r\n") ? "\r\n" : "\n";
const hasFinalLineEnding = rawContent.endsWith("\n") || rawContent.endsWith("\r\n");
const lines = rawContent.split(/\r?\n/u);
let activeTable = null;
let replaced = false;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index] ?? "";
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/u);
if (sectionMatch) {
activeTable = sectionMatch[1]?.trim() ?? null;
continue;
}
// The Julia version is a root key: only match before the first table header.
if (activeTable !== null) {
continue;
}
const versionMatch = line.match(/^(\s*version\s*=\s*)(["'])([^"']*)(\2)(\s*(?:#.*)?)?$/u);
if (!versionMatch) {
continue;
}
const [, prefix = "", quote = '"', , , suffix = ""] = versionMatch;
lines[index] = `${prefix}${quote}${version}${quote}${suffix}`;
replaced = true;
break;
}
if (!replaced) {
throw new Error(`${versionFile} is missing a valid root "version" field required by release-type "julia".`);
}
let updated = lines.join(lineEnding);
if (hasFinalLineEnding && !updated.endsWith(lineEnding)) {
updated += lineEnding;
}
if (!hasFinalLineEnding && updated.endsWith(lineEnding)) {
updated = updated.slice(0, -lineEnding.length);
}
return updated;
}
export const juliaVersionStrategy = {
name: "julia",
getVersionFile(config) {
return config["version-file"] ?? "Project.toml";
},
validateProject(cwd, config) {
const versionFile = this.getVersionFile(config);
const versionPath = path.join(cwd, versionFile);
if (!fs.existsSync(versionPath)) {
return null;
}
try {
readProjectVersion(fs.readFileSync(versionPath, "utf8"), versionFile);
return null;
}
catch (error) {
return error instanceof Error ? error.message : String(error);
}
},
readVersion(cwd, config) {
const versionFile = this.getVersionFile(config);
const versionPath = path.join(cwd, versionFile);
if (!fs.existsSync(versionPath)) {
throw new Error(`Versionary requires ${versionFile} to exist.`);
}
return readProjectVersion(fs.readFileSync(versionPath, "utf8"), versionFile);
},
writeVersion(cwd, config, version) {
const versionFile = this.getVersionFile(config);
const versionPath = path.join(cwd, versionFile);
if (!fs.existsSync(versionPath)) {
throw new Error(`Versionary requires ${versionFile} to exist.`);
}
const existing = fs.readFileSync(versionPath, "utf8");
const updated = writeProjectVersion(existing, versionFile, version);
fs.writeFileSync(versionPath, updated, "utf8");
return [versionFile];
},
readPackageName(cwd, config) {
const versionFile = this.getVersionFile(config);
const versionPath = path.join(cwd, versionFile);
if (!fs.existsSync(versionPath)) {
throw new Error(`Versionary requires ${versionFile} to exist.`);
}
const parsed = parseProjectToml(fs.readFileSync(versionPath, "utf8"), versionFile);
const name = parsed.name;
if (typeof name === "string" && name.trim().length > 0) {
return name.trim();
}
return null;
},
};
+1
-0

@@ -43,2 +43,3 @@ import { z } from "zod";

}>>;
"allow-stable-major": z.ZodOptional<z.ZodBoolean>;
"exclude-paths": z.ZodOptional<z.ZodArray<z.ZodString>>;

@@ -45,0 +46,0 @@ "extra-files": z.ZodOptional<z.ZodArray<z.ZodObject<{

@@ -60,2 +60,3 @@ import { z } from "zod";

"changelog-format": z.enum(["markdown-changelog", "r-news"]).optional(),
"allow-stable-major": z.boolean().optional(),
"exclude-paths": z.array(z.string()).optional(),

@@ -62,0 +63,0 @@ "extra-files": z.array(artifactRuleSchema).optional(),

+11
-3

@@ -53,2 +53,4 @@ import fs from "node:fs";

const allowStableMajor = loaded.config["allow-stable-major"] ?? false;
const allowStableMajorForPath = (packagePath) => loaded.config.packages?.[packagePath]?.["allow-stable-major"] ??
allowStableMajor;
const monorepoMode = getMode(loaded.config["monorepo-mode"]);

@@ -82,3 +84,5 @@ const buildPackagePlan = (pkg) => {

const nextVersion = releaseType
? bumpVersion(packageCurrentVersion, releaseType, { allowStableMajor })
? bumpVersion(packageCurrentVersion, releaseType, {
allowStableMajor: allowStableMajorForPath(pkg.path),
})
: null;

@@ -203,3 +207,5 @@ return {

releaseType: "patch",
nextVersion: bumpVersion(current, "patch", { allowStableMajor }),
nextVersion: bumpVersion(current, "patch", {
allowStableMajor: allowStableMajorForPath(pkgPlan.path),
}),
bumpReason: "dependency-propagation",

@@ -240,3 +246,5 @@ dependencySourcePaths,

const nextVersion = combinedReleaseType
? bumpVersion(baseVersion, combinedReleaseType, { allowStableMajor })
? bumpVersion(baseVersion, combinedReleaseType, {
allowStableMajor: allowStableMajorForPath(pkgPlan.path),
})
: null;

@@ -243,0 +251,0 @@ return {

@@ -17,10 +17,12 @@ export type ReleaseType = "major" | "minor" | "patch" | null;

* Decide whether a changelog heading denotes a released version (e.g.
* `## [0.28.2](url) (2026-06-02)`, `## 1.2.3`, `## v1.2.3`, `## 0.28.2.9000`)
* rather than a manual-notes heading (e.g. `## Unreleased`, `## Upcoming`).
* `## [0.28.2](url) (2026-06-02)`, `## 1.2.3`, `## v1.2.3`, `## 0.28.2.9000`,
* `# pkg 8.0`) rather than a manual-notes heading (e.g. `## Unreleased`,
* `## Upcoming`).
*
* Intentionally liberal: a heading counts as a version if it contains any
* version-like token that {@link isValidVersion} accepts. This errs toward
* "it's a version", so a real release heading is never mistaken for a notes
* block (and therefore never stripped). Dates such as `2026-06-02` use dashes,
* not dots, so they never match the three-component token.
* version-like token that {@link isValidVersion} accepts, or ends with a bare
* `major.minor` token (the R `NEWS.md` convention). This errs toward "it's a
* version", so a real release heading is never mistaken for a notes block (and
* therefore never stripped). Dates such as `2026-06-02` use dashes, not dots,
* so they never match either token.
*/

@@ -27,0 +29,0 @@ export declare function isVersionHeading(heading: string): boolean;

@@ -42,19 +42,26 @@ export function maxReleaseType(types) {

const VERSION_TOKEN_PATTERN = /v?(\d+\.\d+\.\d+(?:\.\d+)?)/u;
// R `NEWS.md` headings conventionally abbreviate to `major.minor` (e.g.
// `# pkg 8.0`). Accept a bare two-component token only when it is the trailing
// token of the heading, so genuine R release headings register as versions
// while prose like `## Notes for 2.0 milestone` does not.
const TRAILING_MAJOR_MINOR_PATTERN = /\bv?\d+\.\d+\s*$/u;
/**
* Decide whether a changelog heading denotes a released version (e.g.
* `## [0.28.2](url) (2026-06-02)`, `## 1.2.3`, `## v1.2.3`, `## 0.28.2.9000`)
* rather than a manual-notes heading (e.g. `## Unreleased`, `## Upcoming`).
* `## [0.28.2](url) (2026-06-02)`, `## 1.2.3`, `## v1.2.3`, `## 0.28.2.9000`,
* `# pkg 8.0`) rather than a manual-notes heading (e.g. `## Unreleased`,
* `## Upcoming`).
*
* Intentionally liberal: a heading counts as a version if it contains any
* version-like token that {@link isValidVersion} accepts. This errs toward
* "it's a version", so a real release heading is never mistaken for a notes
* block (and therefore never stripped). Dates such as `2026-06-02` use dashes,
* not dots, so they never match the three-component token.
* version-like token that {@link isValidVersion} accepts, or ends with a bare
* `major.minor` token (the R `NEWS.md` convention). This errs toward "it's a
* version", so a real release heading is never mistaken for a notes block (and
* therefore never stripped). Dates such as `2026-06-02` use dashes, not dots,
* so they never match either token.
*/
export function isVersionHeading(heading) {
const match = heading.match(VERSION_TOKEN_PATTERN);
if (!match?.[1]) {
return false;
if (match?.[1] && isValidVersion(match[1])) {
return true;
}
return isValidVersion(match[1]);
return TRAILING_MAJOR_MINOR_PATTERN.test(heading);
}

@@ -61,0 +68,0 @@ function isNumericIdentifier(identifier) {

import { compositeVersionStrategy } from "./composite.js";
import { juliaVersionStrategy } from "./julia.js";
import { latexVersionStrategy } from "./latex.js";

@@ -9,2 +10,3 @@ import { nodeVersionStrategy } from "./node.js";

const strategyRegistry = {
julia: juliaVersionStrategy,
latex: latexVersionStrategy,

@@ -11,0 +13,0 @@ simple: simpleVersionStrategy,

@@ -16,2 +16,3 @@ export type ConfigFileFormat = "jsonc" | "json" | "toml" | "js";

"changelog-format"?: VersionaryChangelogFormat;
"allow-stable-major"?: boolean;
"exclude-paths"?: string[];

@@ -18,0 +19,0 @@ "extra-files"?: VersionaryArtifactRule[];

{
"name": "versionary",
"version": "0.30.0",
"version": "0.31.0",
"description": "Automatic release framework based on conventional commits and semantic versioning",

@@ -45,2 +45,3 @@ "keywords": [

"typecheck": "tsc -p tsconfig.json --noEmit",
"gen:schema": "tsx scripts/generate-schema.ts && biome format --write schemas/config.json",
"test": "vitest run",

@@ -47,0 +48,0 @@ "verify": "tsx src/cli/index.ts verify",

@@ -53,3 +53,3 @@ # Versionary

- strategy-based version updates (`simple`, `node`, `rust`, `r`, `latex`,
`python`)
`python`, `julia`)
- release planning and changelog generation

@@ -114,3 +114,3 @@ - review-mode vs direct-mode release flow

- `src/strategy/`: strategy contracts, resolver, and built-in implementations
(`simple`, `node`, `rust`, `r`, `latex`, `python`)
(`simple`, `node`, `rust`, `r`, `latex`, `python`, `julia`)
- `src/scm/`: SCM client contracts and provider implementation(s)

@@ -149,2 +149,5 @@ - `src/config/`: config loading and schema validation

package root if present
- `release-type: "julia"` uses `Project.toml` (default) as version source and
updates the top-level `version` field (Julia keeps `version`/`name` as root
keys, not under a section)
- `release-type` can also be an array of strategy names to compose them across

@@ -202,2 +205,7 @@ manifests, e.g. `["python", "rust"]` for a PyO3/maturin project: the first

repository.
- per-package `allow-stable-major` overrides the top-level setting for that
package's own bump (including dependency-propagation and `follows`-driven
bumps), so a `0.y.z` package can transition to `1.0.0` on a breaking release
independently of its siblings. In `fixed` mode the single shared version is
governed by the top-level `allow-stable-major` only.

@@ -204,0 +212,0 @@ ```jsonc