updated deployer code
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
<key>CFBundleShortVersionString</key><string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key><string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSApplicationCategoryType</key><string>public.app-category.developer-tools</string>
|
||||
<key>SonarDeskEnvironment</key><string>$(SONARDESK_ENVIRONMENT)</string>
|
||||
<key>SonarDeskLicenseBaseURL</key><string>$(SONARDESK_LICENSE_BASE_URL)</string>
|
||||
<key>NSUbiquitousContainers</key>
|
||||
<dict>
|
||||
<key>iCloud.net.absdev.SonarDesk</key>
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
A12 /* Debug */ = {
|
||||
A12 /* Develop */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
@@ -160,8 +160,52 @@
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
name = Develop;
|
||||
};
|
||||
A16 /* Stage */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 39R6U3437D;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Stage;
|
||||
};
|
||||
|
||||
A13 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -203,7 +247,7 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
A14 /* Debug */ = {
|
||||
A14 /* Develop */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
@@ -226,12 +270,46 @@
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.SonarDesk;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SONARDESK_ENVIRONMENT = Develop;
|
||||
SONARDESK_LICENSE_BASE_URL = "https://sonardesk.vm";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_STRICT_CONCURRENCY = targeted;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
name = Develop;
|
||||
};
|
||||
A18 /* Stage */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = SonarDesk/SonarDesk.entitlements;
|
||||
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Configuration/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SonarDesk;
|
||||
INFOPLIST_KEY_CFBundleIconName = AppIcon;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.SonarDesk;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SONARDESK_ENVIRONMENT = Stage;
|
||||
SONARDESK_LICENSE_BASE_URL = "https://sonardesk.stage.absdev.net";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_STRICT_CONCURRENCY = targeted;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Stage;
|
||||
};
|
||||
|
||||
A15 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -255,6 +333,8 @@
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.SonarDesk;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SONARDESK_ENVIRONMENT = Production;
|
||||
SONARDESK_LICENSE_BASE_URL = "https://sonardsk.absdev.net";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
@@ -270,7 +350,8 @@
|
||||
A2 /* Build configuration list for PBXProject "SonarDesk" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A12 /* Debug */,
|
||||
A12 /* Develop */,
|
||||
A16 /* Stage */,
|
||||
A13 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
@@ -279,7 +360,8 @@
|
||||
A8 /* Build configuration list for PBXNativeTarget "SonarDesk" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A14 /* Debug */,
|
||||
A14 /* Develop */,
|
||||
A18 /* Stage */,
|
||||
A15 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
|
||||
@@ -13,6 +13,7 @@ struct SonarDeskApp: App {
|
||||
@NSApplicationDelegateAdaptor(SonarDeskAppDelegate.self) private var appDelegate
|
||||
@StateObject private var appState = AppState()
|
||||
@StateObject private var reportStore = ReportStore()
|
||||
@StateObject private var licenseStore = LicenseStore()
|
||||
@State private var launchPhase: LaunchPhase = .splash
|
||||
|
||||
private enum LaunchPhase {
|
||||
@@ -28,10 +29,17 @@ struct SonarDeskApp: App {
|
||||
LaunchView()
|
||||
.task { await startApplication() }
|
||||
case .application:
|
||||
RootView()
|
||||
.environmentObject(appState)
|
||||
.environmentObject(reportStore)
|
||||
.transition(.opacity)
|
||||
Group {
|
||||
if licenseStore.isLicensed {
|
||||
RootView()
|
||||
.environmentObject(appState)
|
||||
.environmentObject(reportStore)
|
||||
} else {
|
||||
LicenseRequiredView()
|
||||
}
|
||||
}
|
||||
.environmentObject(licenseStore)
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 1040, minHeight: 680)
|
||||
@@ -51,7 +59,8 @@ struct SonarDeskApp: App {
|
||||
SettingsView()
|
||||
.environmentObject(appState)
|
||||
.environmentObject(reportStore)
|
||||
.frame(width: 620, height: 430)
|
||||
.environmentObject(licenseStore)
|
||||
.frame(width: 720, height: 560)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,11 +69,13 @@ struct SonarDeskApp: App {
|
||||
// Keep the branded launch experience visible long enough to be perceptible,
|
||||
// while using the time to perform the first SonarQube refresh.
|
||||
async let minimumSplash: Void = Task.sleep(for: .seconds(1.6))
|
||||
async let licenceRefresh: Void = licenseStore.refresh()
|
||||
|
||||
if appState.projects.isEmpty, appState.selectedServer != nil {
|
||||
_ = await licenceRefresh
|
||||
|
||||
if licenseStore.isLicensed, appState.projects.isEmpty, appState.selectedServer != nil {
|
||||
await appState.refresh()
|
||||
}
|
||||
|
||||
_ = try? await minimumSplash
|
||||
withAnimation(.easeInOut(duration: 0.32)) {
|
||||
launchPhase = .application
|
||||
|
||||
@@ -79,3 +79,33 @@ enum KeychainError: LocalizedError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KeychainService {
|
||||
private static let licenseTokenAccount = "sonardesk.license-token"
|
||||
|
||||
static func saveLicenseToken(_ token: String) throws {
|
||||
let clean = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !clean.isEmpty else { throw KeychainError.emptyToken }
|
||||
let data = Data(clean.utf8)
|
||||
SecItemDelete(query(account: licenseTokenAccount, synchronizable: kSecAttrSynchronizableAny) as CFDictionary)
|
||||
var add = query(account: licenseTokenAccount, synchronizable: kCFBooleanFalse)
|
||||
add[kSecValueData as String] = data
|
||||
add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
||||
let status = SecItemAdd(add as CFDictionary, nil)
|
||||
guard status == errSecSuccess else { throw KeychainError.status(status) }
|
||||
}
|
||||
|
||||
static func licenseToken() -> String? {
|
||||
var lookup = query(account: licenseTokenAccount, synchronizable: kSecAttrSynchronizableAny)
|
||||
lookup[kSecReturnData as String] = true
|
||||
lookup[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||
var item: CFTypeRef?
|
||||
guard SecItemCopyMatching(lookup as CFDictionary, &item) == errSecSuccess,
|
||||
let data = item as? Data else { return nil }
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
|
||||
static func deleteLicenseToken() {
|
||||
SecItemDelete(query(account: licenseTokenAccount, synchronizable: kSecAttrSynchronizableAny) as CFDictionary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ struct ProjectDetailView: View {
|
||||
@State private var credentialError: String?
|
||||
@State private var generatingCredentials = false
|
||||
@State private var exportingAI = false
|
||||
@State private var exportingPDF = false
|
||||
@State private var exportError: String?
|
||||
private var item: ProjectSnapshot? { app.projects.first { $0.project.key == projectKey } }
|
||||
|
||||
@@ -18,9 +19,15 @@ struct ProjectDetailView: View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) { Text(item.project.name).font(.system(size: 30, weight: .bold)); Text(item.project.key).foregroundStyle(.secondary) }
|
||||
Spacer()
|
||||
Button { Task { await exportAIReport(item) } } label: {
|
||||
if exportingAI { ProgressView().controlSize(.small) } else { Label("Export for AI Analysis…", systemImage: "brain.head.profile") }
|
||||
}.buttonStyle(.bordered).disabled(exportingAI)
|
||||
Menu {
|
||||
Button { Task { await exportPDFReport(item) } } label: { Label("PDF Report…", systemImage: "doc.richtext") }
|
||||
Button { Task { await exportAIReport(item) } } label: { Label("AI Analysis Export…", systemImage: "brain.head.profile") }
|
||||
} label: {
|
||||
if exportingPDF || exportingAI { ProgressView().controlSize(.small) } else { Label("Export Report…", systemImage: "square.and.arrow.up") }
|
||||
}
|
||||
.menuStyle(.borderlessButton)
|
||||
.fixedSize()
|
||||
.disabled(exportingPDF || exportingAI)
|
||||
GateBadge(status: item.qualityGate)
|
||||
}.padding(.horizontal, 26).padding(.top, 22).padding(.bottom, 14)
|
||||
|
||||
@@ -172,6 +179,20 @@ struct ProjectDetailView: View {
|
||||
catch { credentialError = error.localizedDescription }
|
||||
}
|
||||
|
||||
@MainActor private func exportPDFReport(_ item: ProjectSnapshot) async {
|
||||
exportingPDF = true; exportError = nil
|
||||
defer { exportingPDF = false }
|
||||
do {
|
||||
let issues = try await app.allIssues(projectKey: item.project.key)
|
||||
try ProjectPDFReportExporter.export(
|
||||
project: item,
|
||||
issues: issues,
|
||||
serverURL: app.selectedServer?.baseURL ?? "Unknown",
|
||||
serverVersion: app.serverVersion
|
||||
)
|
||||
} catch { exportError = error.localizedDescription }
|
||||
}
|
||||
|
||||
@MainActor private func exportAIReport(_ item: ProjectSnapshot) async {
|
||||
exportingAI = true; exportError = nil
|
||||
defer { exportingAI = false }
|
||||
|
||||
@@ -3,6 +3,7 @@ import SwiftUI
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject private var app: AppState
|
||||
@EnvironmentObject private var reportStore: ReportStore
|
||||
@EnvironmentObject private var license: LicenseStore
|
||||
@State private var editingServer: SonarServer?
|
||||
@State private var showingAddServer = false
|
||||
|
||||
@@ -10,6 +11,9 @@ struct SettingsView: View {
|
||||
TabView {
|
||||
serversPage
|
||||
.tabItem { Label("Servers", systemImage: "server.rack") }
|
||||
LicenseSettingsView()
|
||||
.environmentObject(license)
|
||||
.tabItem { Label("Licence", systemImage: "key.fill") }
|
||||
reportsPage
|
||||
.tabItem { Label("Reports", systemImage: "doc.text") }
|
||||
aboutPage
|
||||
|
||||
Reference in New Issue
Block a user