Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 141 additions & 36 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ on:
- cron: "0 */3 * * *"
workflow_dispatch:
inputs:
mode:
description: "Validate artifacts without publishing, or publish the release"
required: false
default: validate
type: choice
options:
- validate
- publish
channel:
description: "Release channel"
required: false
Expand Down Expand Up @@ -78,6 +86,7 @@ jobs:
cli_dist_tag: ${{ steps.release_meta.outputs.cli_dist_tag }}
is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }}
make_latest: ${{ steps.release_meta.outputs.make_latest }}
release_mode: ${{ steps.release_meta.outputs.release_mode }}
ref: ${{ github.sha }}
steps:
- name: Checkout
Expand All @@ -100,11 +109,23 @@ jobs:
shell: bash
env:
DISPATCH_CHANNEL: ${{ github.event.inputs.channel }}
DISPATCH_MODE: ${{ github.event.inputs.mode }}
DISPATCH_VERSION: ${{ github.event.inputs.version }}
NIGHTLY_DATE: ${{ github.run_started_at }}
NIGHTLY_SHA: ${{ github.sha }}
NIGHTLY_RUN_NUMBER: ${{ github.run_number }}
run: |
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
release_mode="${DISPATCH_MODE:-validate}"
else
release_mode="publish"
fi
if [[ "$release_mode" != "validate" && "$release_mode" != "publish" ]]; then
echo "Invalid release mode: $release_mode" >&2
exit 1
fi
echo "release_mode=$release_mode" >> "$GITHUB_OUTPUT"

if [[ "${GITHUB_EVENT_NAME}" == "schedule" || ( "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${DISPATCH_CHANNEL:-stable}" == "nightly" ) ]]; then
nightly_date="$(date -u -d "$NIGHTLY_DATE" +%Y%m%d)"

Expand Down Expand Up @@ -138,7 +159,7 @@ jobs:
echo "release_channel=stable" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "name=T3 Code v$version" >> "$GITHUB_OUTPUT"
echo "name=Command Center v$version" >> "$GITHUB_OUTPUT"
echo "cli_dist_tag=latest" >> "$GITHUB_OUTPUT"
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -317,6 +338,7 @@ jobs:
T3CODE_CLERK_JWT_TEMPLATE: ${{ needs.relay_public_config.outputs.clerk_jwt_template }}
T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }}
T3CODE_RELAY_URL: ${{ needs.relay_public_config.outputs.relay_url }}
REQUIRE_SIGNING: ${{ needs.preflight.outputs.release_channel == 'stable' && 'true' || 'false' }}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -440,6 +462,10 @@ jobs:
$env:AZURE_TRUSTED_SIGNING_PUBLISHER_NAME
)
if ($requiredSecrets | Where-Object { [string]::IsNullOrWhiteSpace($_) }) {
if ($env:REQUIRE_SIGNING -eq "true") {
Write-Error "Stable releases require complete Azure Trusted Signing credentials."
exit 1
}
Write-Host "Azure Trusted Signing disabled; skipping TrustedSigning module preparation."
exit 0
}
Expand Down Expand Up @@ -514,12 +540,27 @@ jobs:
return 0
}

if [[ "${{ matrix.platform }}" == "mac" ]]; then
if has_all "$CSC_LINK" "$CSC_KEY_PASSWORD" "$APPLE_API_KEY" "$APPLE_API_KEY_ID" "$APPLE_API_ISSUER"; then
if ! has_all "$APPLE_TEAM_ID" "$MACOS_PROVISIONING_PROFILE"; then
echo "macOS signing is configured, but APPLE_TEAM_ID or MACOS_PROVISIONING_PROFILE is missing." >&2
exit 1
has_any() {
for value in "$@"; do
if [[ -n "$value" ]]; then
return 0
fi
done
return 1
}

if [[ "${{ matrix.platform }}" == "mac" ]]; then
mac_signing_values=(
"$CSC_LINK"
"$CSC_KEY_PASSWORD"
"$APPLE_API_KEY"
"$APPLE_API_KEY_ID"
"$APPLE_API_ISSUER"
"$APPLE_TEAM_ID"
"$MACOS_PROVISIONING_PROFILE"
"$T3CODE_CLERK_PASSKEY_RP_DOMAINS"
)
if has_all "${mac_signing_values[@]}"; then

key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"
printf '%s' "$APPLE_API_KEY" > "$key_path"
Expand All @@ -534,6 +575,10 @@ jobs:
echo "macOS signing enabled."
args+=(--signed)
else
if [[ "$REQUIRE_SIGNING" == "true" ]] || has_any "${mac_signing_values[@]}"; then
echo "Stable macOS releases require complete signing, notarization, provisioning, team, and associated-domain configuration." >&2
exit 1
fi
echo "macOS signing disabled (missing one or more Apple signing secrets)."
fi
elif [[ "${{ matrix.platform }}" == "win" ]]; then
Expand All @@ -552,6 +597,19 @@ jobs:
echo "Windows signing enabled (Azure Trusted Signing)."
args+=(--signed)
else
windows_signing_values=(
"$AZURE_TENANT_ID"
"$AZURE_CLIENT_ID"
"$AZURE_CLIENT_SECRET"
"$AZURE_TRUSTED_SIGNING_ENDPOINT"
"$AZURE_TRUSTED_SIGNING_ACCOUNT_NAME"
"$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME"
"$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME"
)
if [[ "$REQUIRE_SIGNING" == "true" ]] || has_any "${windows_signing_values[@]}"; then
echo "Stable Windows releases require complete Azure Trusted Signing credentials." >&2
exit 1
fi
echo "Windows signing disabled (missing one or more Azure Trusted Signing secrets)."
fi
else
Expand Down Expand Up @@ -605,10 +663,64 @@ jobs:
path: release-publish/*
if-no-files-found: error

validate_artifacts:
name: Validate release artifacts
needs: [preflight, build]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'validate' && needs.build.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: |
args:
- --filter=@t3tools/scripts...

- name: Download all desktop artifacts
uses: actions/download-artifact@v8
with:
pattern: desktop-*
merge-multiple: true
path: release-assets

- name: Merge macOS updater manifests
run: |
shopt -s nullglob
for x64_manifest in release-assets/*-mac-x64.yml; do
arm64_manifest="${x64_manifest%-x64.yml}.yml"
if [[ -f "$arm64_manifest" ]]; then
node scripts/merge-update-manifests.ts --platform mac "$arm64_manifest" "$x64_manifest"
rm -f "$x64_manifest"
fi
done

- name: Validate installers and updater paths
run: node scripts/validate-release-assets.ts --directory release-assets --version "${{ needs.preflight.outputs.version }}"

- name: Generate release checksums
working-directory: release-assets
run: sha256sum *.dmg *.zip *.AppImage *.exe *.blockmap *.yml > SHA256SUMS.txt

- name: Retain consolidated validation bundle
uses: actions/upload-artifact@v7
with:
name: command-center-${{ needs.preflight.outputs.version }}-validation
path: release-assets/*
if-no-files-found: error
retention-days: 14

publish_cli:
name: Publish CLI to npm
needs: [preflight, relay_public_config, build]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build.result == 'success' }}
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.relay_public_config.result == 'success' && needs.build.result == 'success' }}
runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
permissions:
Expand All @@ -632,7 +744,7 @@ jobs:
cache: true
run-install: |
args:
- --filter=t3...
- --filter=@awtprod/command-center...
- --filter=@t3tools/web...
- --filter=@t3tools/scripts...

Expand All @@ -657,15 +769,15 @@ jobs:
run: vp run --filter @t3tools/web build

- name: Build CLI package
run: vp run --filter t3 build
run: vp run --filter @awtprod/command-center build

- name: Publish CLI package
run: node apps/server/scripts/cli.ts publish --tag "${{ needs.preflight.outputs.cli_dist_tag }}" --app-version "${{ needs.preflight.outputs.version }}" --verbose

release:
name: Publish GitHub Release
needs: [preflight, build, publish_cli]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.build.result == 'success' && needs.publish_cli.result == 'success' }}
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.build.result == 'success' && needs.publish_cli.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
steps:
Expand Down Expand Up @@ -709,6 +821,13 @@ jobs:
fi
done

- name: Generate release checksums
working-directory: release-assets
run: sha256sum *.dmg *.zip *.AppImage *.exe *.blockmap *.yml > SHA256SUMS.txt

- name: Validate installers and updater paths
run: node scripts/validate-release-assets.ts --directory release-assets --version "${{ needs.preflight.outputs.version }}"

# - name: Merge Windows updater manifests
# run: |
# shopt -s nullglob
Expand Down Expand Up @@ -756,6 +875,7 @@ jobs:
release-assets/*.exe
release-assets/*.blockmap
release-assets/*.yml
release-assets/SHA256SUMS.txt
fail_on_unmatched_files: true
token: ${{ steps.app_token.outputs.token }}

Expand All @@ -776,13 +896,14 @@ jobs:
release-assets/*.exe
release-assets/*.blockmap
release-assets/*.yml
release-assets/SHA256SUMS.txt
fail_on_unmatched_files: true
token: ${{ steps.app_token.outputs.token }}

deploy_web:
name: Deploy hosted web app
needs: [preflight, relay_public_config, release]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.release.result == 'success' }}
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.relay_public_config.result == 'success' && needs.release.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
env:
Expand All @@ -793,8 +914,6 @@ jobs:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
T3CODE_WEB_ROUTER_URL: ${{ vars.T3CODE_WEB_ROUTER_URL }}
T3CODE_WEB_LATEST_DOMAIN: ${{ vars.T3CODE_WEB_LATEST_DOMAIN }}
T3CODE_WEB_NIGHTLY_DOMAIN: ${{ vars.T3CODE_WEB_NIGHTLY_DOMAIN }}
VERCEL_TEAM_SLUG: ${{ vars.VERCEL_TEAM_SLUG }}
steps:
- name: Checkout
Expand Down Expand Up @@ -832,7 +951,7 @@ jobs:
- name: Refresh release lockfile
run: vp install --lockfile-only --ignore-scripts

- name: Deploy and alias channel
- name: Deploy hosted channel
shell: bash
run: |
set -euo pipefail
Expand All @@ -842,19 +961,14 @@ jobs:
exit 1
fi

router_url="${T3CODE_WEB_ROUTER_URL:-https://app.t3.codes}"
latest_domain="${T3CODE_WEB_LATEST_DOMAIN:-latest.app.t3.codes}"
nightly_domain="${T3CODE_WEB_NIGHTLY_DOMAIN:-nightly.app.t3.codes}"
router_domain="${router_url#http://}"
router_domain="${router_domain#https://}"
router_domain="${router_domain%%/*}"
router_url="${T3CODE_WEB_ROUTER_URL:-https://awtprod-command-center.vercel.app}"

if [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then
channel_domain="$latest_domain"
channel_name="latest"
deployment_target_args=(--prod)
else
channel_domain="$nightly_domain"
channel_name="nightly"
deployment_target_args=()
fi

vercel_scope="${VERCEL_TEAM_SLUG:-$VERCEL_ORG_ID}"
Expand All @@ -863,8 +977,7 @@ jobs:
echo "Deploying hosted web app for $channel_name channel."
deployment_url="$(
vp dlx vercel@53.1.1 deploy \
--prod \
--skip-domain \
"${deployment_target_args[@]}" \
--yes \
--token "$VERCEL_TOKEN" \
"${vercel_scope_args[@]}" \
Expand All @@ -879,21 +992,12 @@ jobs:
--build-env "VITE_HOSTED_APP_CHANNEL=$channel_name"
)"

echo "Aliasing $deployment_url to $channel_domain."
vp dlx vercel@53.1.1 alias set "$deployment_url" "$channel_domain" \
--token "$VERCEL_TOKEN" \
"${vercel_scope_args[@]}"

if [[ "$channel_name" == "latest" && -n "$router_domain" && "$router_domain" != "$channel_domain" ]]; then
echo "Aliasing $deployment_url to router domain $router_domain."
vp dlx vercel@53.1.1 alias set "$deployment_url" "$router_domain" \
--token "$VERCEL_TOKEN" \
"${vercel_scope_args[@]}"
fi
echo "Hosted $channel_name deployment: $deployment_url"
printf '### Hosted %s deployment\n\n%s\n' "$channel_name" "$deployment_url" >> "$GITHUB_STEP_SUMMARY"

finalize:
name: Finalize release
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }}
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }}
needs: [preflight, release]
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
Expand Down Expand Up @@ -971,6 +1075,7 @@ jobs:
if: |
always() && !cancelled() &&
needs.preflight.result == 'success' &&
needs.preflight.outputs.release_mode == 'publish' &&
needs.relay_public_config.result == 'success' &&
needs.release.result == 'success' &&
needs.deploy_web.result == 'success' &&
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@
"tailwindcss": "^4.0.0",
"vite-plus": "catalog:"
},
"productName": "Command Center (Alpha)"
"productName": "Command Center"
}
8 changes: 4 additions & 4 deletions apps/desktop/scripts/electron-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ const devBundleIdSuffix = NodePath.basename(repoRoot)
.replaceAll(/[^a-z0-9]+/g, "");
export const APP_DISPLAY_NAME = isDevelopment ? "Command Center (Dev)" : "Command Center";
export const APP_BUNDLE_ID = isDevelopment
? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`
: "com.t3tools.t3code";
const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
const LAUNCHER_VERSION = 14;
? `com.awtprod.commandcenter.dev.${devBundleIdSuffix || "local"}`
: "com.awtprod.commandcenter";
const APP_PROTOCOL_SCHEMES = isDevelopment ? ["commandcenter-dev"] : ["commandcenter"];
const LAUNCHER_VERSION = 15;
const defaultIconPath = NodePath.join(desktopDir, "resources", "icon.icns");
const developmentMacIconPngPath = NodePath.join(
repoRoot,
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ describe("DesktopAppIdentity", () => {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
yield* identity.configure;

assert.deepEqual(calls.setName, ["Command Center (Alpha)"]);
assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "Command Center (Alpha)");
assert.deepEqual(calls.setName, ["Command Center"]);
assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "Command Center");
assert.equal(calls.setAboutPanelOptions[0]?.applicationVersion, "1.2.3");
assert.equal(calls.setAboutPanelOptions[0]?.version, "0123456789ab");
assert.deepEqual(calls.setDockIcon, ["/icon.png"]);
Expand Down
Loading
Loading