Skip to content
Merged
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
99 changes: 90 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Release

# Manual release: all three platform packages build concurrently, followed by
# one GitHub Release and concurrent ProGet publication for the Linux packages
# and the Windows Chocolatey package.
# Manual release: the Windows installer, the Microsoft Store package and the
# two Linux packages build concurrently, followed by one GitHub Release, then
# concurrent ProGet publication for the Linux packages and the Windows
# Chocolatey package, and submission of the Store package to Partner Center.
on:
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -97,18 +98,60 @@ jobs:
path: releases/*.exe
if-no-files-found: error

# Built from the same stamped checkout, unsigned: Partner Center signs it.
# It is uploaded to Partner Center by hand, not attached to the release
# (docs/windows/MICROSOFT_STORE_RELEASE.md). Skipped until
# packaging/windows/store/identity.json is filled in.

build-windows-store:
needs: authorize-release
name: Build Microsoft Store package
runs-on: windows-latest

steps:
- uses: actions/checkout@v4
# Recheck on every job: rerunning failed jobs can reuse an earlier authorization.
- name: Verify release owner
run: node scripts/check-release-access.js

- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm

- name: Install dependencies
run: npm ci

- name: Configure StepForge Google sign-in
env:
STEPFORGE_GOOGLE_CLIENT_ID: ${{ vars.STEPFORGE_GOOGLE_CLIENT_ID }}
STEPFORGE_GOOGLE_CLIENT_SECRET: ${{ secrets.STEPFORGE_GOOGLE_CLIENT_SECRET }}
run: node scripts/configure-google-oauth.js

- name: Configure StepForge GitHub sign-in
env:
STEPFORGE_GITHUB_CLIENT_ID: ${{ vars.STEPFORGE_GITHUB_CLIENT_ID }}
STEPFORGE_GITHUB_APP_SLUG: ${{ vars.STEPFORGE_GITHUB_APP_SLUG }}
run: node scripts/configure-github-app.js

- name: Configure StepForge OneDrive and Dropbox sign-in
env:
STEPFORGE_ONEDRIVE_CLIENT_ID: ${{ vars.STEPFORGE_ONEDRIVE_CLIENT_ID }}
STEPFORGE_DROPBOX_APP_KEY: ${{ vars.STEPFORGE_DROPBOX_APP_KEY }}
run: node scripts/configure-cloud-apps.js

- name: Set version from input
shell: bash
env:
VERSION: ${{ inputs.version }}
run: node scripts/stamp-version.js "${VERSION#v}"

# Unsigned: Partner Center signs it. It isn't attached to the GitHub
# Release; publish-microsoft-store submits it to Partner Center.
- name: Package Microsoft Store .appx
run: node scripts/package-windows-store.js --if-configured
run: npm run package:windows:store

- uses: actions/upload-artifact@v4
with:
name: microsoft-store-package
path: releases/*.appx
if-no-files-found: ignore
if-no-files-found: error


build-linux:
Expand Down Expand Up @@ -239,6 +282,7 @@ jobs:
name: Publish GitHub Release
needs:
- build-windows
- build-windows-store
- build-linux
- build-fedora

Expand Down Expand Up @@ -723,3 +767,40 @@ jobs:

choco push $package.FullName --source "$base/nuget/$env:PROGET_CHOCO_FEED/" --api-key $env:PROGET_API_KEY --execution-timeout 1800
if ($LASTEXITCODE -ne 0) { throw "choco push failed with exit code $LASTEXITCODE" }


publish-microsoft-store:
name: Submit to the Microsoft Store
needs: release
if: inputs.prerelease == false
runs-on: windows-latest
# Uploading and Partner Center's checks take minutes; certification
# itself happens after this job, within about three days.
timeout-minutes: 60

steps:
- uses: actions/checkout@v4
# Recheck on every job: rerunning failed jobs can reuse an earlier authorization.
- name: Verify release owner
run: node scripts/check-release-access.js

- uses: actions/download-artifact@v4
with:
name: microsoft-store-package
path: dist/microsoft-store

- name: Set up the Microsoft Store Developer CLI
uses: microsoft/microsoft-store-apppublisher@v1.4
with:
version: v0.4.3

# Signs in as an Entra ID app with the Manager role in Partner Center,
# replaces the live package with this one and commits the submission.
# A rerun skips a version that is already in certification or live.
- name: Submit the package for certification
env:
MICROSOFT_STORE_TENANT_ID: ${{ vars.MICROSOFT_STORE_TENANT_ID }}
MICROSOFT_STORE_SELLER_ID: ${{ vars.MICROSOFT_STORE_SELLER_ID }}
MICROSOFT_STORE_CLIENT_ID: ${{ vars.MICROSOFT_STORE_CLIENT_ID }}
MICROSOFT_STORE_CLIENT_SECRET: ${{ secrets.MICROSOFT_STORE_CLIENT_SECRET }}
run: node scripts/publish-microsoft-store.js dist/microsoft-store
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ Chocolatey, APT or DNF keeps StepForge up to date with the rest of your system,
and with Chocolatey, **Settings → About → Update now** installs a new version
in one click.

**Windows:** open [StepForge in the Microsoft Store](https://apps.microsoft.com/search?query=StepForge)
**Windows:** open [StepForge in the Microsoft Store](https://apps.microsoft.com/detail/9NDH81CF1HGV)
and click **Get**.

<a href="https://apps.microsoft.com/detail/9NDH81CF1HGV"><img src="https://get.microsoft.com/images/en-us%20dark.svg" alt="Get it from Microsoft" width="200"></a>

<details>
<summary><strong>Windows (Chocolatey)</strong> commands</summary>

Expand Down
4 changes: 2 additions & 2 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,14 @@ Chinese and Japanese. `core/i18n.js` explains how it works. In short:
To add a language, add it to `LANGUAGES` in `core/i18n.js`, add its catalog
to `core/locales/` and `core/locales/index.js`, and load it with a script tag
in `app/renderer/index.html`. The Microsoft Store package lists every language
in `LANGUAGES` (see `docs/windows/MICROSOFT_STORE_RELEASE.md`).
in `LANGUAGES`, so the Store shows StepForge as available in it.

## Build installable packages

| Target | Command | Output |
| --- | --- | --- |
| Windows installer | `npm run package:windows` (or `pwsh scripts/package-windows.ps1`) | `releases/` |
| Microsoft Store `.appx` (Windows only) | `npm run package:windows:store` (or `npm run package:windows:store:test` before the app is reserved); see [the release guide](windows/MICROSOFT_STORE_RELEASE.md) | `releases/` |
| Microsoft Store `.appx` (Windows only) | `npm run package:windows:store` (or `npm run package:windows:store:test` for a placeholder identity). The Release workflow builds it and submits it to the Store | `releases/` |
| Ubuntu `.deb` + portable `.tar.gz` | `npm run package:linux:deb` | `build/artifacts/` |
| Fedora `.rpm` | `npm run package:linux:rpm` | `build/artifacts/x86_64/` |
| Unpacked app folder | `bash scripts/build-release.sh` | `build/` |
Expand Down
6 changes: 1 addition & 5 deletions docs/windows/microsoft-store.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Install StepForge from the Microsoft Store


Note that support for this installation method is coming in the next couple of days. Microsoft is currently verifying the app package.


The Microsoft Store is the recommended way to install StepForge on
**Windows 10 (version 1809 or later) and Windows 11, 64-bit**. Microsoft
checks and signs the package, so Windows shows no **Windows protected your
Expand All @@ -15,7 +11,7 @@ Prefer a package manager or a regular installer? See

## Install

1. Open [StepForge in the Microsoft Store](https://apps.microsoft.com/search?query=StepForge),
1. Open [StepForge in the Microsoft Store](https://apps.microsoft.com/detail/9NDH81CF1HGV),
or open the **Microsoft Store** app from the Start menu and search for
**StepForge**.
2. Click **Get** (or **Install**).
Expand Down
2 changes: 1 addition & 1 deletion docs/windows_installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ to install it:

## Recommended: the Microsoft Store

1. Open [StepForge in the Microsoft Store](https://apps.microsoft.com/search?query=StepForge),
1. Open [StepForge in the Microsoft Store](https://apps.microsoft.com/detail/9NDH81CF1HGV),
or search for **StepForge** in the **Microsoft Store** app.
2. Click **Get**, then **Open**.

Expand Down
26 changes: 5 additions & 21 deletions scripts/package-windows-store.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env node
'use strict';

// Builds the Microsoft Store package (.appx) that is uploaded to Partner
// Center. The Store signs it, so it is never signed here. Needs Windows (the
// Windows SDK's makeappx), so it runs in CI; see docs/windows/MICROSOFT_STORE_RELEASE.md.
// Builds the Microsoft Store package (.appx) that the release workflow submits
// to Partner Center (scripts/publish-microsoft-store.js). The Store signs it,
// so it is never signed here. Needs Windows (the Windows SDK's makeappx), so it
// runs in CI.
//
// npm run package:windows:store # the identity in packaging/windows/store/identity.json
// node scripts/package-windows-store.js --if-configured # the same, skipped while identity.json is unfilled
Expand All @@ -19,6 +20,7 @@ const path = require('node:path');
const { build, Platform, Arch } = require('electron-builder');
const { APP_ID, BUILD_VERSION, createWindowsInstallerConfig } = require('./package-windows');
const { LANGUAGES } = require('../core/i18n');
const { storeVersion } = require('./store-version');

const ROOT_DIR = path.resolve(__dirname, '..');
const STORE_DIR = path.join(ROOT_DIR, 'packaging', 'windows', 'store');
Expand Down Expand Up @@ -60,24 +62,6 @@ function validateStoreIdentity(identity) {
};
}

/**
* The Store version for a StepForge build version. The Store reserves the
* fourth part and needs it to be 0, and every submission must be newer than
* the last, so a.b.c.d becomes a.b.(c*100+d).0: 1.5.3 → 1.5.300.0 and
* 1.5.3.1 → 1.5.301.0. Each part must fit in 16 bits.
*/
function storeVersion(buildVersion) {
const parts = String(buildVersion || '').split('.');
if (parts.length < 1 || parts.length > 4 || !parts.every((part) => /^(0|[1-9]\d{0,4})$/.test(part))) {
throw new Error(`Build version ${JSON.stringify(buildVersion)} is not a numeric version.`);
}
const [major, minor = 0, patch = 0, revision = 0] = parts.map(Number);
if (revision > 99) throw new Error(`Build version ${buildVersion}: the fourth part must be below 100 for the Store version.`);
const build = patch * 100 + revision;
if ([major, minor, build].some((n) => n > 65535)) throw new Error(`Build version ${buildVersion} does not fit a Store version.`);
return `${major}.${minor}.${build}.0`;
}

/** The manifest template with the Store version filled in. */
function renderStoreManifest(version, template = fs.readFileSync(MANIFEST_TEMPLATE, 'utf8')) {
if (!/^\d+\.\d+\.\d+\.0$/.test(version)) throw new Error(`invalid Store version ${version}`);
Expand Down
Loading
Loading