Files
poshmanager/server/services/intunePromotionService.js
2026-06-25 12:05:53 -05:00

70 lines
2.9 KiB
JavaScript

// Pure helpers for the application promotion pipeline: producing the next
// version's deployment plan and advancing assignment rings. No I/O, so both are
// unit-tested; the controller persists/pushes the results.
// Bump the trailing numeric component of a version string (1.2.3 -> 1.2.4,
// 126 -> 127). Falls back to appending ".1" when no number is present.
export function bumpVersion(version = '') {
const text = String(version).trim();
if (!text) return '1.0.0';
const match = text.match(/^(.*?)(\d+)(\D*)$/);
if (!match) return `${text}.1`;
const [, head, num, tail] = match;
return `${head}${Number(num) + 1}${tail}`;
}
// Build a fresh deployment payload for the next version, based on an existing
// plan. The new plan is unpublished (no graphAppId), draft status, and keeps the
// application link so the catalog groups both versions.
export function cloneForNextVersion(deployment, { version, applicationId } = {}) {
if (!deployment) throw new Error('A source deployment is required');
const nextVersion = version || bumpVersion(extractVersion(deployment.name));
const baseName = deployment.name.replace(/\s+\d[\d.]*$/, '').trim() || deployment.name;
return {
name: `${baseName} ${nextVersion}`.trim(),
profileId: deployment.profileId || null,
scriptId: deployment.scriptId || null,
applicationId: applicationId || deployment.applicationId || null,
sourceFolder: deployment.sourceFolder || '',
intunewinFile: '',
commandStyle: deployment.commandStyle,
installBehavior: deployment.installBehavior,
restartBehavior: deployment.restartBehavior,
uiMode: deployment.uiMode,
requirements: { ...(deployment.requirements || {}) },
installCommand: deployment.installCommand,
uninstallCommand: deployment.uninstallCommand,
detectionType: deployment.detectionType,
detectionRule: deployment.detectionRule,
returnCodes: deployment.returnCodes || [],
assignments: deployment.assignments || [],
relationships: deployment.relationships || [],
status: 'draft',
graphAppId: '',
graphConnectionId: deployment.graphConnectionId || null,
notes: `Version ${nextVersion} created from ${deployment.name}.`,
visibility: deployment.visibility,
groupId: deployment.groupId || null
};
}
function extractVersion(name = '') {
const match = String(name).match(/(\d[\d.]*)\s*$/);
return match ? match[1] : '';
}
// Advance a named assignment ring to a new intent (e.g. promote "Broad" from
// available to required). Returns a new assignments array; unknown rings are a
// no-op so callers can detect "nothing changed".
export function setRingIntent(assignments = [], ring, intent) {
let changed = false;
const next = assignments.map((assignment) => {
if (assignment.ring?.toLowerCase() === String(ring).toLowerCase() && assignment.intent !== intent) {
changed = true;
return { ...assignment, intent };
}
return assignment;
});
return { assignments: next, changed };
}