Skip to content

Commit 4041e33

Browse files
authored
fix: strip leading 'v' prefix from version strings to prevent double-v display (#1)
VERSION env var and __HYPERAGENT_VERSION__ build-time injection could include a 'v' prefix (e.g. from git tags like 'v0.1.0'). Since callers already prepend 'v' for display, this caused 'vv0.1.0' in the banner. - Strip leading 'v' from process.env.VERSION in build-binary.js - Strip leading 'v' from __HYPERAGENT_VERSION__ in version.ts - Reject plugin manifest versions with 'v' prefix in validator - Add test for version prefix validation Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent 2b88765 commit 4041e33

5 files changed

Lines changed: 28 additions & 3 deletions

File tree

scripts/build-binary.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ function parseGitDescribe(describe) {
8484
function calculateMinVer() {
8585
// Allow override via environment (for Docker/CI)
8686
if (process.env.VERSION) {
87-
return process.env.VERSION;
87+
// Strip leading "v" if present — callers add their own prefix
88+
return process.env.VERSION.replace(/^v/i, "");
8889
}
8990
try {
9091
const result = spawnSync("git", ["describe", "--tags", "--long", "--always", "--dirty"], {

src/agent/version.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ export function getVersion(): string {
9595

9696
// Check for build-time injected version
9797
if (typeof __HYPERAGENT_VERSION__ !== "undefined") {
98-
cachedVersion = __HYPERAGENT_VERSION__;
98+
// Strip leading "v" if present — callers add their own prefix
99+
cachedVersion = __HYPERAGENT_VERSION__.replace(/^v/i, "");
99100
return cachedVersion;
100101
}
101102

src/plugin-system/manager.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ export function validateManifest(raw: unknown): string[] {
209209
if ("version" in obj && typeof obj.version !== "string") {
210210
errors.push("version must be a string");
211211
}
212+
if (
213+
"version" in obj &&
214+
typeof obj.version === "string" &&
215+
/^v/i.test(obj.version)
216+
) {
217+
// Callers prepend "v" for display — a prefixed version causes "vv1.0.0"
218+
errors.push(
219+
'version must not start with "v" (use bare semver, e.g. "1.0.0")',
220+
);
221+
}
212222
if ("description" in obj && typeof obj.description !== "string") {
213223
errors.push("description must be a string");
214224
}

src/plugin-system/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface ConfigSchemaEntry {
6161
export interface PluginManifest {
6262
/** Unique plugin name (kebab-case, matches directory name). */
6363
name: string;
64-
/** SemVer version string. */
64+
/** SemVer version string (bare, no "v" prefix — e.g. "1.0.0" not "v1.0.0"). */
6565
version: string;
6666
/** One-line description of what the plugin does. */
6767
description: string;

tests/plugin-manager.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ describe("validateManifest", () => {
8282
expect(errors).toContain("hostModules must be an array");
8383
});
8484

85+
it('should reject version with "v" prefix', () => {
86+
const manifest = {
87+
name: "test",
88+
version: "v1.0.0",
89+
description: "Test",
90+
hostModules: ["fs"],
91+
};
92+
const errors = validateManifest(manifest);
93+
expect(errors).toContain(
94+
'version must not start with "v" (use bare semver, e.g. "1.0.0")',
95+
);
96+
});
97+
8598
it("should reject empty hostModules array", () => {
8699
const manifest = {
87100
name: "test",

0 commit comments

Comments
 (0)