Release ForgeFlow 0.6.0

This commit is contained in:
NuklearRabbit
2026-07-25 05:59:07 +02:00
parent cf1f67a823
commit 9d3933c878
48 changed files with 2208 additions and 466 deletions
+30 -3
View File
@@ -7,7 +7,7 @@ const { safeStorage } = require('electron');
const { assertHttpUrl, assertWorkflowFileName, assertBranchName, assertEnvironmentName, assertCloneRemote, assertRepositoryRelativePaths } = require('../shared/validation.cjs');
const DEFAULT_CONFIG = {
schemaVersion: 5,
schemaVersion: 7,
setupComplete: false,
appearance: 'dark',
gitea: { baseUrl: '', user: null, encryptedToken: null },
@@ -58,7 +58,20 @@ class ConfigStore {
gitea: { ...DEFAULT_CONFIG.gitea, ...(source.gitea || {}) },
workspaceRoots: uniqueStrings(source.workspaceRoots),
repositoryMappings: source.repositoryMappings && typeof source.repositoryMappings === 'object' ? source.repositoryMappings : {},
deploymentProfiles: source.deploymentProfiles && typeof source.deploymentProfiles === 'object' ? source.deploymentProfiles : {},
deploymentProfiles: source.deploymentProfiles && typeof source.deploymentProfiles === 'object'
? Object.fromEntries(Object.entries(source.deploymentProfiles).map(([key, profiles]) => [key, (Array.isArray(profiles) ? profiles : []).map((profile) => {
if (!profile || typeof profile !== 'object' || profile.provider !== 'ssh-unraid') return profile;
const iconUrl = String(profile.iconUrl || '').trim();
const iconFilePath = String(profile.iconFilePath || '').trim();
const requestedMode = String(profile.iconMode || '').trim();
const iconMode = ['builtin', 'upload', 'url', 'none'].includes(requestedMode)
? requestedMode
: iconFilePath ? 'upload' : iconUrl && !/itworx\.tech\/assets\/itworx-icon\.png/i.test(iconUrl) ? 'url' : 'builtin';
const visibleName = String(profile.containerName || profile.remoteFolder || '').trim();
const internalService = String(profile.composeService || profile.remoteFolder || 'app').trim().toLowerCase().replace(/[^a-z0-9._-]/g, '-') || 'app';
return { ...profile, composeService: internalService, containerName: visibleName || internalService, iconMode };
})]))
: {},
deploymentStates: source.deploymentStates && typeof source.deploymentStates === 'object' ? source.deploymentStates : {},
favorites: uniqueStrings(source.favorites).map((item) => item.toLowerCase()),
updates: { ...DEFAULT_CONFIG.updates, ...(source.updates || {}) },
@@ -300,13 +313,27 @@ class ConfigStore {
serverId: String(profile.serverId || '').trim(),
remoteFolder,
composeFile: String(profile.composeFile || 'docker-compose.yml').trim(),
composeService: String(profile.composeService || '').trim(),
composeService: (() => {
const value = String(profile.composeService || remoteFolder).trim().toLowerCase();
if (!/^[a-z0-9._-]+$/.test(value)) throw new Error('Compose service must be lowercase and contain only letters, numbers, dots, underscores and dashes.');
return value;
})(),
containerName: (() => {
const value = String(profile.containerName || remoteFolder).trim();
if (!/^[A-Za-z0-9._-]+$/.test(value)) throw new Error('Container name must contain only letters, numbers, dots, underscores and dashes.');
return value;
})(),
cloneUrl: profile.cloneUrl ? assertCloneRemote(profile.cloneUrl) : '',
alignRemote: profile.alignRemote === true,
hostPort: profile.hostPort ? Math.min(Math.max(Number(profile.hostPort), 1), 65535) : null,
containerPort: profile.containerPort ? Math.min(Math.max(Number(profile.containerPort), 1), 65535) : null,
webUiUrl: assertHttpUrl(profile.webUiUrl, { optional: true, label: 'Web UI URL' }),
iconMode: ['builtin', 'upload', 'url', 'none'].includes(profile.iconMode)
? profile.iconMode
: profile.iconFilePath ? 'upload' : profile.iconUrl ? 'url' : 'builtin',
iconUrl: assertHttpUrl(profile.iconUrl, { optional: true, label: 'Icon URL' }),
iconFilePath: String(profile.iconFilePath || '').trim(),
dockerShell: ['/bin/sh', '/bin/bash'].includes(profile.dockerShell) ? profile.dockerShell : '/bin/sh',
preservePaths,
generatedCompose: profile.generatedCompose === true
};