Skip to content
Merged
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
4 changes: 3 additions & 1 deletion plugins/cad/src/components/AddPackagePage/AddPackagePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ export const AddPackagePage = ({ action }: AddPackagePageProps) => {
]);

allRepositories.current = thisAllRepositories;
allClonablePackageRevisions.current = allPackages.filter(canCloneRevision);
allClonablePackageRevisions.current = allPackages.filter(packageRevision =>
canCloneRevision(packageRevision, allPackages),
);

const thisRepository = repositoryName ? getRepository(thisAllRepositories, repositoryName) : undefined;

Expand Down
9 changes: 8 additions & 1 deletion plugins/cad/src/types/PackageRevision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type PackageRevisionSpec = {
packageName: string;
repository: string;
workspaceName?: string;
revision?: string;
revision?: string | number;
lifecycle: PackageRevisionLifecycle;
tasks: PackageRevisionTask[];
readinessGates?: ReadinessGate[];
Expand All @@ -53,6 +53,7 @@ export type PackageRevisionTask = {
clone?: PackageRevisionTaskClone;
update?: PackageRevisionTaskUpdate;
eval?: PackageRevisionTaskEval;
edit?: PackageRevisionTaskEdit;
};

export type PackageRevisionTaskInit = {
Expand All @@ -61,6 +62,12 @@ export type PackageRevisionTaskInit = {
site?: string;
};

export type PackageRevisionTaskEdit = {
sourceRef: {
name: string;
};
};

export type PackageRevisionTaskClone = {
upstreamRef: PackageRevisionTaskUpstreamRef;
};
Expand Down
119 changes: 88 additions & 31 deletions plugins/cad/src/utils/packageRevision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,22 @@ import {
} from '../types/PackageRevision';
import { toLowerCase } from './string';

const getRevisionNumber = (revision: string, defaultNumber: number = NaN): number => {
if (revision && revision.startsWith('v')) {
const revisionNumber = parseInt(revision.substring(1), 10);

const getRevisionNumber = (revision: string | number, defaultNumber: number = Number.NaN): number => {
// Handle integer revision (new Porch API returns int instead of string)
if (typeof revision === 'number' && Number.isInteger(revision)) {
return revision;
}
if (revision && String(revision).startsWith('v')) {
const revisionNumber = Number.parseInt(String(revision).substring(1), 10);
if (Number.isInteger(revisionNumber)) {
return revisionNumber;
}
}

return defaultNumber;
};

const getNextRevision = (revision: string): string => {
const getNextRevision = (revision: string | number): string => {
if (revision === -1 || revision === '-1') return 'v1';
const revisionNumber = getRevisionNumber(revision, 0);

return `v${revisionNumber + 1}`;
Expand Down Expand Up @@ -71,17 +74,60 @@ export const getUpstreamPackageRevisionDetails = (
return undefined;
};

export const isLatestPublishedRevision = (packageRevision: PackageRevision): boolean => {
return (
packageRevision.spec.lifecycle === PackageRevisionLifecycle.PUBLISHED &&
!!packageRevision.metadata.labels?.['kpt.dev/latest-revision']
);
const getWorkspaceNameVersion = (workspaceName: string): number[] => {
// 'main' gets version [0] (lowest)
if (!workspaceName || workspaceName === 'main') return [0];
// 'v3.0.0' → [3, 0, 0]
const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(workspaceName);
if (match) return [Number.parseInt(match[1], 10), Number.parseInt(match[2], 10), Number.parseInt(match[3], 10)];
// fallback
return [0];
};

export const findLatestPublishedRevision = (packageRevisions: PackageRevision[]): PackageRevision | undefined => {
const latestPublishedRevision = packageRevisions.find(isLatestPublishedRevision);
const compareWorkspaceVersions = (a: string, b: string): number => {
const aVer = getWorkspaceNameVersion(a);
const bVer = getWorkspaceNameVersion(b);
for (let i = 0; i < Math.max(aVer.length, bVer.length); i++) {
const diff = (bVer[i] ?? 0) - (aVer[i] ?? 0);
if (diff !== 0) return diff;
}
return 0;
};

export const isLatestPublishedRevision = (
packageRevision: PackageRevision,
allRevisions?: PackageRevision[],
): boolean => {
if (packageRevision.spec.lifecycle !== PackageRevisionLifecycle.PUBLISHED) {
return false;
}

// External catalog repos use revision -1 with no labels
if (packageRevision.spec.revision === -1 || packageRevision.spec.revision === '-1') {
if (allRevisions) {
// Find all revisions for the same package in the same repo
const siblings = allRevisions.filter(
r =>
r.spec.packageName === packageRevision.spec.packageName &&
r.spec.repository === packageRevision.spec.repository &&
r.spec.lifecycle === PackageRevisionLifecycle.PUBLISHED &&
(r.spec.revision === -1 || r.spec.revision === '-1'),
);
// Sort by workspaceName version descending
const sorted = [...siblings].sort((a, b) =>
compareWorkspaceVersions(a.spec.workspaceName ?? '', b.spec.workspaceName ?? ''),
);
// Only the highest versioned one is "latest"
return sorted[0]?.metadata.name === packageRevision.metadata.name;
}
return true;
}

return !!packageRevision.metadata.labels?.['kpt.dev/latest-revision'];
};

return latestPublishedRevision;
export const findLatestPublishedRevision = (packageRevisions: PackageRevision[]): PackageRevision | undefined => {
return packageRevisions.find(r => isLatestPublishedRevision(r, packageRevisions));
};

export const findPackageRevision = (
Expand All @@ -99,22 +145,27 @@ export const findPackageRevision = (
};

export const getPackageRevisionRevision = (packageRevision: PackageRevision): string => {
return packageRevision.spec.revision || '';
const revision = packageRevision.spec.revision;
if (revision === undefined || revision === null) return '';
if (typeof revision === 'number') {
return revision === -1 ? '-1' : `v${revision}`;
}
return String(revision);
};

export const isPublishedRevision = (packageRevision: PackageRevision): boolean => {
return packageRevision.spec.lifecycle === PackageRevisionLifecycle.PUBLISHED;
};

export const getPackageRevisionTitle = (packageRevision: PackageRevision, packageNameOnly: boolean = false): string => {
const { packageName, lifecycle, revision } = packageRevision.spec;
const { packageName, lifecycle } = packageRevision.spec;

if (packageNameOnly) {
return packageName;
}

if (isPublishedRevision(packageRevision)) {
return `${packageName} ${revision}`;
return `${packageName} ${getPackageRevisionRevision(packageRevision)}`;
}

return `${packageName} ${toLowerCase(lifecycle)} revision`;
Expand All @@ -130,7 +181,7 @@ export const filterPackageRevisions = (
packageRevision.spec.packageName === packageName &&
packageRevision.spec.repository === repositoryName &&
(!isPublishedRevision(packageRevision) ||
Number.isFinite(getRevisionNumber(packageRevision.spec.revision || ''))),
Number.isFinite(getRevisionNumber(packageRevision.spec.revision ?? ''))),
);
};

Expand All @@ -146,8 +197,8 @@ export const getPackageRevision = (packageRevisions: PackageRevision[], fullPack
return packageRevision;
};

export const canCloneRevision = (packageRevision: PackageRevision): boolean => {
return isLatestPublishedRevision(packageRevision);
export const canCloneRevision = (packageRevision: PackageRevision, allRevisions?: PackageRevision[]): boolean => {
return isLatestPublishedRevision(packageRevision, allRevisions);
};

export const isNotAPublishedRevision = (packageRevision: PackageRevision): boolean => {
Expand Down Expand Up @@ -182,6 +233,17 @@ export const getCloneTask = (fullPackageName: string): PackageRevisionTask => {
return cloneTask;
};

export const getEditTask = (fullPackageName: string): PackageRevisionTask => {
return {
type: 'edit',
edit: {
sourceRef: {
name: fullPackageName,
},
},
};
};

export const getUpdateTask = (fullUpstreamPackageName: string): PackageRevisionTask => {
const updateTask: PackageRevisionTask = {
type: 'update',
Expand Down Expand Up @@ -223,16 +285,12 @@ export const getPackageRevisionResource = (
};

export const getNextPackageRevisionResource = (currentRevision: PackageRevision): PackageRevision => {
const { repository, packageName, tasks } = currentRevision.spec;
const { repository, packageName } = currentRevision.spec;
const nextRevision = getNextRevision(getPackageRevisionRevision(currentRevision));

const resource = getPackageRevisionResource(
repository,
packageName,
nextRevision,
PackageRevisionLifecycle.DRAFT,
cloneDeep(tasks),
);
const resource = getPackageRevisionResource(repository, packageName, nextRevision, PackageRevisionLifecycle.DRAFT, [
getEditTask(currentRevision.metadata.name),
]);

return resource;
};
Expand Down Expand Up @@ -286,9 +344,8 @@ export const getPackageConditions = (packageRevision: PackageRevision): Conditio
const allConditions = cloneDeep(conditions);

const readinessConditions = readinessGates.map(gate => gate.conditionType);
const existingConditions = conditions.map(condition => condition.type);

const missingReadinessConditions = readinessConditions.filter(type => !existingConditions.includes(type));
const existingConditions = new Set(conditions.map(condition => condition.type));
const missingReadinessConditions = readinessConditions.filter(type => !existingConditions.has(type));
missingReadinessConditions.forEach(type =>
allConditions.push({
type: type,
Expand Down
3 changes: 2 additions & 1 deletion plugins/cad/src/utils/packageSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export const getPackageSummariesForRepository = (
allRepositories: Repository[],
): PackageSummary[] => {
const latestPackageRevisions = packageRevisions.filter(
packageRevision => isNotAPublishedRevision(packageRevision) || isLatestPublishedRevision(packageRevision),
packageRevision =>
isNotAPublishedRevision(packageRevision) || isLatestPublishedRevision(packageRevision, packageRevisions),
);

latestPackageRevisions.sort(sortByPackageNameAndRevisionComparison);
Expand Down
28 changes: 14 additions & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7580,13 +7580,13 @@ before-after-hook@^2.2.0:
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c"
integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==

better-sqlite3@^7.5.0:
version "7.6.2"
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-7.6.2.tgz#47cd8cad5b9573cace535f950ac321166bc31384"
integrity sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==
better-sqlite3@^11.8.0:
version "11.10.0"
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-11.10.0.tgz#2b1b14c5acd75a43fd84d12cc291ea98cef57d98"
integrity sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==
dependencies:
bindings "^1.5.0"
prebuild-install "^7.1.0"
prebuild-install "^7.1.1"

bfj@^8.0.0:
version "8.0.0"
Expand Down Expand Up @@ -15082,10 +15082,10 @@ nanoid@^5.0.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6"
integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==

napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
napi-build-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==

natural-compare@^1.4.0:
version "1.4.0"
Expand Down Expand Up @@ -16637,17 +16637,17 @@ postgres-interval@^1.1.0:
dependencies:
xtend "^4.0.0"

prebuild-install@^7.1.0:
version "7.1.2"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056"
integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==
prebuild-install@^7.1.1:
version "7.1.3"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
dependencies:
detect-libc "^2.0.0"
expand-template "^2.0.3"
github-from-package "0.0.0"
minimist "^1.2.3"
mkdirp-classic "^0.5.3"
napi-build-utils "^1.0.1"
napi-build-utils "^2.0.0"
node-abi "^3.3.0"
pump "^3.0.0"
rc "^1.2.7"
Expand Down
Loading