Files
poshmanager/server/services/recipeService.js
2026-08-09 17:33:11 -05:00

96 lines
3.2 KiB
JavaScript

import { RECIPES } from '../data/recipeCatalog.js';
import { analyzeInstaller } from './installerIntelService.js';
import { buildDetectionRule } from './detectionRuleService.js';
// Lists/searches the curated catalog and turns a recipe into the request bodies
// the models accept (PSADT profile + Intune deployment + winget-tracked app).
// installTask type enum is exe|msi|msp|script; everything EXE-based maps to exe.
function taskTypeFor(installerType = '') {
if (installerType === 'msi') return 'msi';
if (installerType === 'msp') return 'msp';
return 'exe';
}
function summarize(recipe) {
return {
id: recipe.id,
vendor: recipe.vendor,
name: recipe.name,
category: recipe.category || 'Other',
installerType: recipe.installerType,
fileName: recipe.fileName,
arch: recipe.arch || 'x64',
installArgs: recipe.installArgs || '',
detectionKind: recipe.detection?.type || '',
wingetId: recipe.wingetId || '',
homepage: recipe.homepage || ''
};
}
export function listRecipes() {
return RECIPES.map(summarize);
}
export function searchRecipes(query = '') {
const q = String(query).trim().toLowerCase();
if (!q) return listRecipes();
return RECIPES.filter((r) =>
[r.vendor, r.name, r.category, r.wingetId, r.id]
.some((field) => String(field || '').toLowerCase().includes(q))
).map(summarize);
}
export function getRecipe(id) {
return RECIPES.find((r) => r.id === id) || null;
}
// recipe → { profile, deployment, application }; application is null with no winget id.
export function buildRecipeArtifacts(recipe, { visibility = 'personal', groupId = null } = {}) {
if (!recipe) throw new Error('recipe is required');
const scope = { visibility, groupId: visibility === 'group' ? groupId || null : null };
const displayName = `${recipe.vendor} ${recipe.name}`.trim();
const analysis = analyzeInstaller({ fileName: recipe.fileName, productCode: recipe.productCode || '' });
const detection = buildDetectionRule(recipe.detection || {});
const profile = {
name: displayName,
appVendor: recipe.vendor,
appName: recipe.name,
appVersion: recipe.version || '',
appArch: recipe.arch === 'x86' ? 'x86' : 'x64',
closeProcesses: recipe.closeProcesses || [],
installTasks: [{
type: taskTypeFor(recipe.installerType),
phase: 'Install',
filePath: recipe.fileName,
arguments: recipe.installArgs || ''
}],
...scope
};
const deployment = {
name: displayName,
appType: 'Windows app (Win32)',
commandStyle: 'v4',
requirements: { architecture: recipe.arch === 'x86' ? 'x86' : 'x64' },
detectionType: detection.detectionType,
detectionRule: detection.detectionRule,
status: 'draft',
notes: `Created from the "${recipe.name}" recipe. Source: ${recipe.homepage || 'n/a'}`,
...scope
};
const application = recipe.wingetId ? {
name: displayName,
vendor: recipe.vendor,
versionSource: 'winget',
versionSourceRef: recipe.wingetId,
autoCheck: true,
notes: `Auto-tracking ${recipe.wingetId} via winget (from the "${recipe.name}" recipe).`,
...scope
} : null;
return { recipeId: recipe.id, displayName, profile, deployment, application, analysis, detection };
}