new release script

This commit is contained in:
Mark Gregory
2026-10-18 14:49:36 +01:00
parent ebd0bd1e29
commit 4306211981
4 changed files with 161 additions and 32 deletions
+36
View File
@@ -2728,6 +2728,14 @@ struct WorkbenchAdministrationView: View {
.frame(width: 175)
}
Button {
exportAdministrationCSV()
} label: {
Label("Export CSV…", systemImage: "square.and.arrow.up")
}
.disabled(result.columns.isEmpty || result.rows.isEmpty)
.help("Export the currently filtered administration results as UTF-8 CSV")
Toggle("Auto-refresh", isOn: $autoRefresh)
.toggleStyle(.checkbox)
.help("Refresh every five seconds")
@@ -2748,6 +2756,34 @@ struct WorkbenchAdministrationView: View {
.padding(18)
}
// Phase 60: Export the currently displayed administration data without
// requiring an external MySQL client. RFC 4180 quoting protects commas,
// double quotes and multiline field values.
@MainActor
private func exportAdministrationCSV() {
guard !result.columns.isEmpty else { return }
let panel = NSSavePanel()
panel.allowedContentTypes = [.commaSeparatedText]
panel.canCreateDirectories = true
panel.nameFieldStringValue = "DBSyn-" + page.rawValue
.replacingOccurrences(of: " ", with: "-") + ".csv"
guard panel.runModal() == .OK, let url = panel.url else { return }
func record(_ values: [String]) -> String {
values.map { value in
let escaped = value.replacingOccurrences(of: "\"", with: "\"\"")
return "\"" + escaped + "\""
}.joined(separator: ",") + "\r\n"
}
do {
let rows = [result.columns] + filteredRows
let csv = rows.map(record).joined()
try csv.write(to: url, atomically: true, encoding: .utf8)
processActionStatus = "Exported \(filteredRows.count) rows to \(url.lastPathComponent)."
} catch {
errorMessage = "CSV export failed: \(error.localizedDescription)"
}
}
// The live charts occupy a fixed region ABOVE an independently scrolling
// counter table. They cannot disappear when the user scrolls the rows.
@ViewBuilder
+15
View File
@@ -0,0 +1,15 @@
# Phase 60 — Database Administration
Builds on DBSyn Phase 59.2 without changing the Phase 50 dashboard design.
## Administration tools
The existing administration workspace includes Server Status, Client Connections, Users and Privileges, and Status and System Variables. These screens query the connected MySQL server directly. Session termination and account changes require appropriate MySQL privileges and confirmation.
## Added in Phase 60
An **Export CSV…** action is available in the administration toolbar for populated result sets. It exports the current filtered rows and column headers as UTF-8 CSV, with fields quoted and embedded double quotes escaped. This works for Server Status, Client Connections, Users and Privileges, and system variables, as well as the other administration result screens. The export is a snapshot of the currently loaded data; it does not export chart history.
## Validation
Swift parser and ZIP integrity checks were performed on Linux. A macOS Xcode build and live MySQL integration test must be performed on the target Mac.
+110 -15
View File
@@ -1,17 +1,112 @@
#!/bin/bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PHASE="${1:?Usage: release-dbsyn.sh PHASE NAME}"
NAME="${2:?Usage: release-dbsyn.sh PHASE NAME}"
cd "$ROOT"
mkdir -p releases
xcodebuild -project DBSyn.xcodeproj -scheme DBSyn -configuration Release -destination 'platform=macOS' -derivedDataPath "$ROOT/build" clean build
APP="$ROOT/build/Build/Products/Release/DBSyn.app"
test -d "$APP"
SOURCE="DBSyn-Phase${PHASE}-${NAME}-Source.zip"
BINARY="DBSyn-Phase${PHASE}-${NAME}-App-Unsigned.zip"
/usr/bin/zip -qr "releases/$SOURCE" DBSyn DBSyn.xcodeproj docs scripts -x '*.DS_Store'
(cd "$ROOT/build/Build/Products/Release" && /usr/bin/zip -qr "$ROOT/releases/$BINARY" DBSyn.app)
/usr/bin/unzip -tq "releases/$SOURCE" >/dev/null
/usr/bin/unzip -tq "releases/$BINARY" >/dev/null
printf 'Created:\n %s\n %s\n' "releases/$SOURCE" "releases/$BINARY"
# DBSyn — Developer ID direct distribution (signed and notarized app + DMG)
# Install at: <project>/scripts/release-dbsyn-direct.sh
# Run from any directory: ./scripts/release-dbsyn-direct.sh
APP_NAME="DBSyn"
SCHEME="DBSyn"
PROJECT="DBSyn.xcodeproj"
TEAM_ID="${TEAM_ID:-39R6U3437D}"
SIGNING_IDENTITY="${SIGNING_IDENTITY:-B99D353C88996866C49E4389B375645F76870A3F}"
NOTARY_PROFILE="${NOTARY_PROFILE:-orion-notary}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -d "$SCRIPT_DIR/$PROJECT" ]]; then
ROOT_DIR="$SCRIPT_DIR"
elif [[ -d "$SCRIPT_DIR/../$PROJECT" ]]; then
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
else
echo "ERROR: $PROJECT not found next to this script or in its parent directory." >&2
exit 1
fi
BUILD_DIR="$ROOT_DIR/build/direct-distribution"
ARCHIVE_PATH="$BUILD_DIR/$APP_NAME.xcarchive"
EXPORT_DIR="$BUILD_DIR/export"
DMG_STAGE="$BUILD_DIR/dmg-stage"
EXPORT_OPTIONS="$BUILD_DIR/ExportOptions.plist"
APP_ZIP="$BUILD_DIR/$APP_NAME-notarization.zip"
RELEASE_DIR="$ROOT_DIR/releases"
DMG_PATH="$RELEASE_DIR/$APP_NAME.dmg"
for tool in xcodebuild xcrun codesign spctl ditto hdiutil shasum security; do
command -v "$tool" >/dev/null || { echo "ERROR: Missing $tool" >&2; exit 1; }
done
[[ -d "$ROOT_DIR/$PROJECT" ]] || { echo "ERROR: Missing $PROJECT" >&2; exit 1; }
security find-identity -v -p codesigning | grep -Fq "$SIGNING_IDENTITY" || {
echo "ERROR: Signing identity $SIGNING_IDENTITY not available in Keychain." >&2
exit 1
}
# Check that notarytool profile exists before spending time compiling.
xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" >/dev/null || {
echo "ERROR: Notarytool profile '$NOTARY_PROFILE' is unavailable or invalid." >&2
exit 1
}
printf '\n========== DBSyn DIRECT DISTRIBUTION ==========\n'
cd "$ROOT_DIR"
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR" "$EXPORT_DIR" "$DMG_STAGE" "$RELEASE_DIR"
# Keep previous releases until a new notarized DMG has been successfully built.
cat > "$EXPORT_OPTIONS" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>method</key><string>developer-id</string>
<key>signingStyle</key><string>automatic</string>
<key>teamID</key><string>$TEAM_ID</string>
</dict></plist>
PLIST
echo '==> Archiving DBSyn (Release)...'
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration Release \
-destination 'generic/platform=macOS' -archivePath "$ARCHIVE_PATH" \
DEVELOPMENT_TEAM="$TEAM_ID" CODE_SIGN_STYLE=Automatic \
clean archive
echo '==> Exporting Developer ID application...'
xcodebuild -exportArchive -archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_DIR" -exportOptionsPlist "$EXPORT_OPTIONS"
APP_PATH="$EXPORT_DIR/$APP_NAME.app"
[[ -d "$APP_PATH" ]] || { echo "ERROR: Exported app missing: $APP_PATH" >&2; exit 1; }
echo '==> Checking application signature and hardened runtime...'
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
codesign -dv --verbose=4 "$APP_PATH" 2>&1 | grep -E 'Identifier=|Authority=|TeamIdentifier=|Runtime Version=|flags=' || true
ACTUAL_TEAM="$(codesign -dv --verbose=4 "$APP_PATH" 2>&1 | sed -n 's/^TeamIdentifier=//p' | head -1)"
[[ "$ACTUAL_TEAM" == "$TEAM_ID" ]] || { echo "ERROR: Unexpected signing team: $ACTUAL_TEAM" >&2; exit 1; }
codesign -dv --verbose=4 "$APP_PATH" 2>&1 | grep -q 'runtime' || {
echo 'ERROR: App is not signed with the hardened runtime required for notarization.' >&2
exit 1
}
echo '==> Notarizing application...'
ditto -c -k --keepParent "$APP_PATH" "$APP_ZIP"
xcrun notarytool submit "$APP_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait
xcrun stapler staple "$APP_PATH"
xcrun stapler validate "$APP_PATH"
spctl --assess --type execute --verbose=4 "$APP_PATH"
echo '==> Creating installer DMG...'
ditto "$APP_PATH" "$DMG_STAGE/$APP_NAME.app"
ln -s /Applications "$DMG_STAGE/Applications"
TMP_DMG="$BUILD_DIR/$APP_NAME.dmg"
hdiutil create -volname "$APP_NAME" -srcfolder "$DMG_STAGE" -ov -format UDZO "$TMP_DMG"
echo '==> Signing and notarizing DMG...'
codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$TMP_DMG"
codesign --verify --verbose=2 "$TMP_DMG"
xcrun notarytool submit "$TMP_DMG" --keychain-profile "$NOTARY_PROFILE" --wait
xcrun stapler staple "$TMP_DMG"
xcrun stapler validate "$TMP_DMG"
spctl --assess --type open --context context:primary-signature --verbose=4 "$TMP_DMG"
# Publish only after all checks have passed.
cp -f "$TMP_DMG" "$DMG_PATH"
echo '==> Release ready:'
ls -lh "$DMG_PATH"
shasum -a 256 "$DMG_PATH"
printf '\n========== DBSyn RELEASE COMPLETE ==========\n%s\n' "$DMG_PATH"
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PHASE="${1:?Usage: release-workbench.sh PHASE NAME}"
NAME="${2:?Usage: release-workbench.sh PHASE NAME}"
cd "$ROOT"
mkdir -p releases
xcodebuild -project WorkbenchReplacement.xcodeproj -scheme WorkbenchReplacement -configuration Release -destination 'platform=macOS' -derivedDataPath "$ROOT/build" clean build
APP="$ROOT/build/Build/Products/Release/WorkbenchReplacement.app"
test -d "$APP"
SOURCE="WorkbenchReplacement-Phase${PHASE}-${NAME}-Source.zip"
BINARY="WorkbenchReplacement-Phase${PHASE}-${NAME}-App-Unsigned.zip"
/usr/bin/zip -qr "releases/$SOURCE" WorkbenchReplacement WorkbenchReplacement.xcodeproj docs scripts -x '*.DS_Store'
(cd "$ROOT/build/Build/Products/Release" && /usr/bin/zip -qr "$ROOT/releases/$BINARY" WorkbenchReplacement.app)
/usr/bin/unzip -tq "releases/$SOURCE" >/dev/null
/usr/bin/unzip -tq "releases/$BINARY" >/dev/null
printf 'Created:\n %s\n %s\n' "releases/$SOURCE" "releases/$BINARY"