diff --git a/.github/workflows/axolotl-release.yml b/.github/workflows/axolotl-release.yml index 3709c38..bae9f35 100644 --- a/.github/workflows/axolotl-release.yml +++ b/.github/workflows/axolotl-release.yml @@ -1,4 +1,4 @@ -name: Axolotl release +name: Starlight release on: push: @@ -53,6 +53,15 @@ jobs: node scripts/axolotl/i18n-check.mjs cargo fmt --all --check + - name: Check updater signing configuration + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + run: | + if [[ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]]; then + echo '::error::Configure TAURI_SIGNING_PRIVATE_KEY with the private key matching apps/app/tauri-release.conf.json before publishing updates.' + exit 1 + fi + - name: Prepare draft release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -72,7 +81,7 @@ jobs: release_args=( --draft --notes-file "$release_notes" - --title "Axolotl Launcher ${GITHUB_REF_NAME}" + --title "Starlight Launcher ${GITHUB_REF_NAME}" ) if [[ "${GITHUB_REF_NAME}" == *-* ]]; then release_args+=(--prerelease) @@ -175,7 +184,7 @@ jobs: tauriScript: pnpm tauri args: ${{ matrix.args }} tagName: ${{ github.ref_name }} - releaseName: Axolotl Launcher ${{ github.ref_name }} + releaseName: Starlight Launcher ${{ github.ref_name }} releaseDraft: true prerelease: ${{ contains(github.ref_name, '-') }} generateReleaseNotes: false @@ -277,12 +286,17 @@ jobs: name: Verify and publish needs: build runs-on: ubuntu-24.04 + env: + UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }} + UPDATE_SERVER_WEBHOOK_SECRET: ${{ secrets.UPDATE_SERVER_WEBHOOK_SECRET }} + CNB_TOKEN: ${{ secrets.CNB_TOKEN }} + CNB_REPO_URL_HTTPS: ${{ secrets.CNB_REPO_URL_HTTPS }} steps: - name: Check out source uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - ref: main + ref: ${{ github.ref }} - name: Download updater signatures env: @@ -349,6 +363,7 @@ jobs: node scripts/axolotl/create-update-server-catalog.test.mjs - name: Notify Update Server to publish release assets + if: ${{ env.UPDATE_SERVER_URL != '' && env.UPDATE_SERVER_WEBHOOK_SECRET != '' }} env: UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }} UPDATE_SERVER_WEBHOOK_SECRET: ${{ secrets.UPDATE_SERVER_WEBHOOK_SECRET }} @@ -359,6 +374,13 @@ jobs: release-check/release.json \ "${GITHUB_REF_NAME}" + - name: Check out website branch + if: ${{ !contains(github.ref_name, '-') }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: main + clean: false + - name: Publish website download metadata if: ${{ !contains(github.ref_name, '-') }} env: @@ -381,11 +403,14 @@ jobs: fi - name: Mirror published release to CNB + if: ${{ env.CNB_TOKEN != '' && env.CNB_REPO_URL_HTTPS != '' }} env: CNB_REPO_URL_HTTPS: ${{ secrets.CNB_REPO_URL_HTTPS }} CNB_TOKEN: ${{ secrets.CNB_TOKEN }} CNB_TOKEN_USER_NAME: ${{ secrets.CNB_TOKEN_USER_NAME }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_API_BASE_URL: https://api.github.com/repos/${{ github.repository }} + GITHUB_RELEASE_BASE_URL: https://github.com/${{ github.repository }}/releases/download run: | set -euo pipefail if [[ -z "${CNB_TOKEN:-}" ]]; then diff --git a/.github/workflows/sync-cnb-source.yml b/.github/workflows/sync-cnb-source.yml index 96f5fa6..71cf563 100644 --- a/.github/workflows/sync-cnb-source.yml +++ b/.github/workflows/sync-cnb-source.yml @@ -19,6 +19,7 @@ concurrency: jobs: sync: name: Sync Git ref + if: ${{ vars.ENABLE_CNB_SYNC == 'true' }} runs-on: ubuntu-24.04 timeout-minutes: 5 steps: @@ -38,8 +39,8 @@ jobs: DELETED_REF_TYPE: ${{ github.event.ref_type }} run: | set -euo pipefail - if [[ -z "${CNB_TOKEN:-}" ]]; then - echo "CNB_TOKEN is not configured" + if [[ -z "${CNB_TOKEN:-}" || -z "${CNB_REPO_URL_HTTPS:-}" ]]; then + echo "CNB_TOKEN and CNB_REPO_URL_HTTPS must be configured" exit 1 fi diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index ce8bc27..d0a30b2 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -1,7 +1,7 @@ { "name": "@modrinth/app-frontend", "private": true, - "version": "1.0.0-beta1", + "version": "1.0.0-beta12", "type": "module", "scripts": { "dev": "vite", diff --git a/apps/app-frontend/src/components/RowDisplay.vue b/apps/app-frontend/src/components/RowDisplay.vue index b55fb5c..723204d 100644 --- a/apps/app-frontend/src/components/RowDisplay.vue +++ b/apps/app-frontend/src/components/RowDisplay.vue @@ -42,6 +42,7 @@ const { offline } = useNetworkStatus() const handleMinecraftLaunchError = useMinecraftLaunchError() const messages = defineMessages({ + stopAll: { id: 'app.instance.stop-all', defaultMessage: 'Stop all windows of this instance' }, addContent: { id: 'app.instances.add-content', defaultMessage: 'Add content' }, viewInstance: { id: 'app.instances.view-instance', defaultMessage: 'View instance' }, duplicateInstance: { @@ -265,7 +266,12 @@ onUnmounted(() => { @delete="deleteInstance" />
-
+
{{ row.label }} @@ -307,7 +313,7 @@ onUnmounted(() => {
- + diff --git a/apps/app-frontend/src/components/home/HomeMinimal.vue b/apps/app-frontend/src/components/home/HomeMinimal.vue index 865498f..b913783 100644 --- a/apps/app-frontend/src/components/home/HomeMinimal.vue +++ b/apps/app-frontend/src/components/home/HomeMinimal.vue @@ -29,6 +29,7 @@ import { process_listener } from '@/helpers/events' import { install_existing_instance, install_pack_to_existing_instance } from '@/helpers/install' import { kill, run } from '@/helpers/instance' import { get_by_instance_id } from '@/helpers/process' +import { isInstanceLaunching } from '@/helpers/instance-launch-state' import type { GameInstance } from '@/helpers/types' import { handleSevereError } from '@/store/error' @@ -50,6 +51,8 @@ const handleMinecraftLaunchError = useMinecraftLaunchError() const { offline } = useNetworkStatus() const messages = defineMessages({ + launchAnother: { id: 'app.instance.launch-another', defaultMessage: 'Launch another window' }, + stopAll: { id: 'app.instance.stop-all', defaultMessage: 'Stop all windows of this instance' }, chooseInstance: { id: 'app.home.minimal.choose-instance', defaultMessage: 'Choose instance', @@ -88,14 +91,17 @@ const selectedInstance = computed(() => props.instances.find((instance) => instance.id === props.selectedInstanceId), ) const running = ref(false) -const loading = ref(false) -const currentEvent = ref(null) +const installingId = ref() const installed = computed(() => selectedInstance.value?.install_stage === 'installed') const installing = computed( () => selectedInstance.value?.install_stage.includes('installing') ?? false, ) const busy = computed( - () => loading.value || installing.value || (currentEvent.value === 'launched' && !running.value), + () => + !!selectedInstance.value && + (isInstanceLaunching(selectedInstance.value.id) || + installingId.value === selectedInstance.value.id || + installing.value), ) const lastPlayed = computed(() => { @@ -111,18 +117,17 @@ async function refreshProcessState() { return } - const processes = await get_by_instance_id(selectedInstance.value.id).catch((error) => { + const id = selectedInstance.value.id + const processes = await get_by_instance_id(id).catch((error) => { handleError(error) return [] }) - running.value = processes.length > 0 + if (selectedInstance.value?.id === id) running.value = processes.length > 0 } async function playInstance() { const instance = selectedInstance.value - if (!instance) return - - loading.value = true + if (!instance || busy.value) return try { await run(instance.id) } catch (error) { @@ -132,7 +137,6 @@ async function playInstance() { }) if (!handled) handleSevereError(error, { instanceId: instance.id }) } finally { - loading.value = false await refreshProcessState() } } @@ -142,14 +146,14 @@ async function stopInstance() { if (!instance) return await kill(instance.id).catch(handleError) - running.value = false + await refreshProcessState() } async function installInstance() { const instance = selectedInstance.value if (!instance) return - loading.value = true + installingId.value = instance.id try { if ( instance.install_stage !== 'pack_installed' && @@ -168,14 +172,14 @@ async function installInstance() { } catch (error) { handleError(error) } finally { - loading.value = false + if (installingId.value === instance.id) installingId.value = undefined } } watch( () => props.selectedInstanceId, () => { - currentEvent.value = null + running.value = false void refreshProcessState() }, ) @@ -184,9 +188,7 @@ await refreshProcessState() const unlistenProcess = await process_listener((event: { instance_id: string; event: string }) => { if (event.instance_id !== selectedInstance.value?.id) return - currentEvent.value = event.event - if (event.event === 'finished') running.value = false - else void refreshProcessState() + void refreshProcessState() }) onUnmounted(() => { @@ -237,13 +239,17 @@ onUnmounted(() => {
- - - + diff --git a/apps/app-frontend/src/components/ui/AppActionBar.vue b/apps/app-frontend/src/components/ui/AppActionBar.vue index 9b17eef..392b7cc 100644 --- a/apps/app-frontend/src/components/ui/AppActionBar.vue +++ b/apps/app-frontend/src/components/ui/AppActionBar.vue @@ -137,6 +137,11 @@ {{ process.instance.name }} + {{ + formatMessage(messages.windowStarted, { + time: new Date(process.start_time), + }) + }} @@ -307,6 +312,7 @@ const notificationCenterShown = ref(false) interface RunningProcess { uuid: string + start_time: string instance_id: string instance: GameInstance } @@ -320,6 +326,10 @@ interface LoadingEventPayload { } const messages = defineMessages({ + windowStarted: { + id: 'app.action-bar.window-started', + defaultMessage: 'Started {time, time, medium}', + }, offline: { id: 'app.action-bar.offline', defaultMessage: 'Offline', @@ -346,7 +356,7 @@ const messages = defineMessages({ }, stopInstance: { id: 'app.action-bar.stop-instance', - defaultMessage: 'Stop instance', + defaultMessage: 'Stop window', }, viewLogs: { id: 'app.action-bar.view-logs', @@ -401,8 +411,9 @@ const refresh = async () => { const processes = ((await getRunningProcesses().catch((error) => { handleError(error) return [] - })) ?? []) as Array<{ uuid: string; instance_id: string }> - const instanceIds = processes.map((process) => process.instance_id) + })) ?? []) as Array<{ uuid: string; instance_id: string; start_time: string }> + processes.sort((a, b) => a.start_time.localeCompare(b.start_time)) + const instanceIds = [...new Set(processes.map((process) => process.instance_id))] const instances: GameInstance[] = await getInstances(instanceIds).catch((error) => { handleError(error) return [] @@ -420,9 +431,9 @@ const refresh = async () => { } }) .filter((process): process is RunningProcess => process !== null) - if (!selectedProcess.value || !currentProcesses.value.includes(selectedProcess.value)) { - selectedProcess.value = currentProcesses.value[0] - } + selectedProcess.value = + currentProcesses.value.find((process) => process.uuid === selectedProcess.value?.uuid) ?? + currentProcesses.value[0] } await refresh() diff --git a/apps/app-frontend/src/components/ui/Instance.vue b/apps/app-frontend/src/components/ui/Instance.vue index 56b5ff3..893ca10 100644 --- a/apps/app-frontend/src/components/ui/Instance.vue +++ b/apps/app-frontend/src/components/ui/Instance.vue @@ -1,12 +1,5 @@