Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { deriveChartTag } from './useEditDeploymentData';

describe('deriveChartTag', () => {
it('returns undefined when ref is undefined', () => {
expect(deriveChartTag(undefined)).toBeUndefined();
});

it('returns tag when ref.tag is set', () => {
expect(deriveChartTag({ tag: '1.2.3' })).toBe('1.2.3');
});

it('returns undefined when only digest is set', () => {
expect(deriveChartTag({ digest: 'sha256:abc' })).toBeUndefined();
});

describe('concrete semver ranges', () => {
it('extracts version from >= range', () => {
expect(deriveChartTag({ semver: '>=1.2.3 <2.0.0' })).toBe('1.2.3');
});

it('extracts version from tilde range', () => {
expect(deriveChartTag({ semver: '~1.2.3' })).toBe('1.2.3');
});

it('extracts version from caret range', () => {
expect(deriveChartTag({ semver: '^1.2.3' })).toBe('1.2.3');
});

it('extracts version with pre-release suffix', () => {
expect(deriveChartTag({ semver: '>=1.2.3-beta.1' })).toBe('1.2.3-beta.1');
});

it('extracts bare version', () => {
expect(deriveChartTag({ semver: '1.2.3' })).toBe('1.2.3');
});
});

describe('patch wildcards', () => {
it('handles 1.2.x', () => {
expect(deriveChartTag({ semver: '1.2.x' })).toBe('1.2.0');
});

it('handles 1.2.*', () => {
expect(deriveChartTag({ semver: '1.2.*' })).toBe('1.2.0');
});

it('handles 1.2.X', () => {
expect(deriveChartTag({ semver: '1.2.X' })).toBe('1.2.0');
});

it('handles ~1.2.x', () => {
expect(deriveChartTag({ semver: '~1.2.x' })).toBe('1.2.0');
});
});

describe('minor wildcards', () => {
it('handles 1.x', () => {
expect(deriveChartTag({ semver: '1.x' })).toBe('1.0.0');
});

it('handles 1.*', () => {
expect(deriveChartTag({ semver: '1.*' })).toBe('1.0.0');
});

it('handles 1.X', () => {
expect(deriveChartTag({ semver: '1.X' })).toBe('1.0.0');
});

it('handles 1.x.x', () => {
expect(deriveChartTag({ semver: '1.x.x' })).toBe('1.0.0');
});

it('handles 1.*.*', () => {
expect(deriveChartTag({ semver: '1.*.*' })).toBe('1.0.0');
});

it('handles ^1.x', () => {
expect(deriveChartTag({ semver: '^1.x' })).toBe('1.0.0');
});
});

describe('full wildcards', () => {
it('handles *', () => {
expect(deriveChartTag({ semver: '*' })).toBe('0.0.0');
});

it('handles x', () => {
expect(deriveChartTag({ semver: 'x' })).toBe('0.0.0');
});

it('handles X', () => {
expect(deriveChartTag({ semver: 'X' })).toBe('0.0.0');
});

it('handles x.x.x', () => {
expect(deriveChartTag({ semver: 'x.x.x' })).toBe('0.0.0');
});

it('handles *.*.*', () => {
expect(deriveChartTag({ semver: '*.*.*' })).toBe('0.0.0');
});
});

it('handles leading/trailing whitespace', () => {
expect(deriveChartTag({ semver: ' 1.2.x ' })).toBe('1.2.0');
expect(deriveChartTag({ semver: ' ~1.2.3 ' })).toBe('1.2.3');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,40 @@ function deriveChartRef(ociUrl: string | undefined): string | undefined {
* Derives the chart tag from an OCIRepository reference.
* For semver ranges like `>=1.2.3`, extracts the base version.
* For exact tags like `1.2.3`, returns as-is.
* Supports Masterminds/semver wildcard placeholders (x, X, *),
* normalizing them to `0` (e.g. `1.2.x` → `1.2.0`).
*/
function deriveChartTag(
export function deriveChartTag(
ref: { semver?: string; tag?: string; digest?: string } | undefined,
): string | undefined {
if (!ref) return undefined;

if (ref.tag) return ref.tag;
if (!ref.semver) return undefined;

const s = ref.semver.trim();

if (ref.semver) {
// Extract version from semver range, e.g. ">=1.2.3 <2.0.0" → "1.2.3"
const match = ref.semver.match(/(\d+\.\d+\.\d+(?:-[^\s<>]+)?)/);
return match ? match[1] : undefined;
// Strip leading constraint operator (>=, <=, !=, >, <, =, ~, ^)
const stripped = s.replace(/^(?:>=|<=|!=|[><=~^])\s*/, '');

// Match a version where components may be digits or wildcards (x, X, *)
const wcMatch = stripped.match(
/^(\d+|[xX*])(?:\.(\d+|[xX*])(?:\.(\d+|[xX*])(?:-([^\s<>]+))?)?)?$/,
);
if (wcMatch) {
const toNum = (v: string | undefined) =>
!v || /^[xX*]$/.test(v) ? '0' : v;
const major = toNum(wcMatch[1]);
const minor = toNum(wcMatch[2]);
const patch = toNum(wcMatch[3]);
const pre = wcMatch[4];
return pre
? `${major}.${minor}.${patch}-${pre}`
: `${major}.${minor}.${patch}`;
}

return undefined;
// Fallback: extract first concrete version from compound range (e.g. ">=1.2.3 <2.0.0")
const match = s.match(/(\d+\.\d+\.\d+(?:-[^\s<>]+)?)/);
return match ? match[1] : undefined;
}

export function useEditDeploymentData(
Expand Down