diff --git a/ci/release.yml b/ci/release.yml index 4c3bdf8..5189077 100644 --- a/ci/release.yml +++ b/ci/release.yml @@ -19,6 +19,10 @@ parameters: displayName: "Auto-update users to this release" type: boolean default: false +- name: PublishStore + displayName: "Also publish to the Store" + type: boolean + default: false - name: PreTest displayName: "Pre test" type: boolean @@ -35,6 +39,10 @@ parameters: displayName: "Test Signed" type: boolean default: false +- name: StoreAppId + displayName: "Microsoft Store App Id" + type: string + default: 9NQ7512CXL7T variables: @@ -46,7 +54,8 @@ variables: PIP_VERBOSE: true PYMSBUILD_VERBOSE: true PYMSBUILD_TEMP_DIR: $(Build.BinariesDirectory) - DIST_DIR: $(Build.ArtifactStagingDirectory) + DIST_DIR: $(Build.ArtifactStagingDirectory)\dist + STORE_DIST_DIR: $(Build.ArtifactStagingDirectory)\store LAYOUT_DIR: $(Build.BinariesDirectory)\layout TEST_MSIX_DIR: $(Build.BinariesDirectory)\test_msix ${{ if ne(parameters.OverrideRef, '(tag)') }}: @@ -69,6 +78,7 @@ stages: - ${{ if eq(parameters.TestSign, 'true') }}: - group: CPythonTestSign - ${{ if eq(parameters.Publish, 'true') }}: + - group: MSFTStorePublish - group: PythonOrgPublish @@ -314,6 +324,13 @@ stages: workingDirectory: $(Pipeline.Workspace) displayName: 'Download PuTTY binaries' + - powershell: | + mv "${env:UPLOAD_DIR}\*-store.msix*" (mkdir -Force ${env:STORE_UPLOAD_DIR}) -Verbose + displayName: 'Move Store packages' + env: + UPLOAD_DIR: $(DIST_DIR) + STORE_UPLOAD_DIR: $(STORE_DIST_DIR) + - ${{ if ne(parameters.PublishAppinstaller, 'true') }}: - powershell: | "Not uploading these files:" @@ -323,11 +340,42 @@ stages: env: UPLOAD_DIR: $(DIST_DIR) + - ${{ if eq(parameters.PublishStore, 'true') }}: + - task: UseMSStoreCLI@0 + displayName: Setup Microsoft Store Developer CLI + + - powershell: > + msstore reconfigure + --tenantId $(MSSTORE_TENANT_ID) + --sellerId $(MSSTORE_SELLER_ID) + --clientId $(MSSTORE_CLIENT_ID) + --clientSecret $(MSSTORE_CLIENT_SECRET) + displayName: Authenticate Store CLI + + # We begin the submission but do not complete it, so the RM has a chance + # to update metadata before going public. It also means we can do this + # whether signed or not, since a test release can simply be deleted rather + # than published. Existing drafts will be overwritten with new ones. + - powershell: | + $msix = Get-Item "${env:STORE_UPLOAD_DIR}\*.msixupload" + "Uploading $msix" + msstore publish -v -nc -id $env:MSSTORE_APP_ID $msix + "MSIX is uploaded" + "Patching submission details" + python ci\store-publish.py + "Submission details updated" + "Finish publishing at https://partner.microsoft.com/en-us/dashboard/products/${env:MSSTORE_APP_ID}/overview" + displayName: 'Begin Store submission' + env: + STORE_UPLOAD_DIR: $(STORE_DIST_DIR) + MSSTORE_TENANT_ID: $(MSSTORE_TENANT_ID) + MSSTORE_SELLER_ID: $(MSSTORE_SELLER_ID) + MSSTORE_CLIENT_ID: $(MSSTORE_CLIENT_ID) + MSSTORE_CLIENT_SECRET: $(MSSTORE_CLIENT_SECRET) + MSSTORE_APP_ID: ${{ parameters.StoreAppId }} + PATCH_JSON: ci\store-patch.json + - powershell: | - # We don't want the Store MSIX on python.org, so just delete it - # It's already been archived in the earlier publish step, and is bundled - # into the .msixupload file. - del "${env:UPLOAD_DIR}\*-store.msix" -ErrorAction SilentlyContinue python ci\upload.py displayName: 'Publish packages' env: diff --git a/ci/store-patch.json b/ci/store-patch.json new file mode 100644 index 0000000..5f489fb --- /dev/null +++ b/ci/store-patch.json @@ -0,0 +1,11 @@ +{ + "#": "This file is used by store-upload.py to modify Store metadata.", + "listings": { + "en-us":{ + "baseListing": { + "#": "Update the release notes; 2-3 lines shown to Store users before updating", + "releaseNotes": "This update has a new \"first launch\" experience to help configure your system. If you don't see it, run \"py install --configure\" to start it manually.\n\nVisit https://github.com/python/pymanager for information and to provide feedback during beta releases." + } + } + } +} \ No newline at end of file diff --git a/ci/store-publish.py b/ci/store-publish.py new file mode 100644 index 0000000..779263a --- /dev/null +++ b/ci/store-publish.py @@ -0,0 +1,91 @@ +import json +import os +import sys + +from urllib.request import urlopen, Request + + +DIRECTORY_ID = os.environ["MSSTORE_TENANT_ID"] +CLIENT_ID = os.environ["MSSTORE_CLIENT_ID"] +CLIENT_SECRET = os.environ["MSSTORE_CLIENT_SECRET"] +SELLER_ID = os.environ["MSSTORE_SELLER_ID"] +APP_ID = os.environ["MSSTORE_APP_ID"] + +PATCH_JSON = os.environ["PATCH_JSON"] +with open(PATCH_JSON, "rb") as f: + patch_data = json.load(f) + + +SERVICE_URL = "https://manage.devcenter.microsoft.com/v1.0/my/" + +################################################################################ +# Get auth token/header +################################################################################ + +OAUTH_URL = f"https://login.microsoftonline.com/{DIRECTORY_ID}/oauth2/v2.0/token" +SERVICE_SCOPE = "https://manage.devcenter.microsoft.com/.default" + +reqAuth = Request( + OAUTH_URL, + method="POST", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data=(f"grant_type=client_credentials&client_id={CLIENT_ID}&" + + f"client_secret={CLIENT_SECRET}&scope={SERVICE_SCOPE}").encode("utf-8"), +) + +with urlopen(reqAuth) as r: + jwt = json.loads(r.read()) + +auth = {"Authorization": f"Bearer {jwt['access_token']}"} + +################################################################################ +# Get application data (for current submission) +################################################################################ + +reqApps = Request(f"{SERVICE_URL}applications/{APP_ID}", method="GET", headers=auth) +print("Getting application data from", reqApps.full_url) +with urlopen(reqApps) as r: + app_data = json.loads(r.read()) + +submission_url = app_data["pendingApplicationSubmission"]["resourceLocation"] + +################################################################################ +# Get current submission data +################################################################################ + +reqSubmission = Request(f"{SERVICE_URL}{submission_url}", method="GET", headers=auth) +print("Getting submission data from", reqSubmission.full_url) +with urlopen(reqSubmission) as r: + sub_data = json.loads(r.read()) + +################################################################################ +# Patch submission data +################################################################################ + +if patch_data: + def _patch(target, key, src): + if key.startswith("#"): + return + if isinstance(src, dict): + for k, v in src.items(): + _patch(target.setdefault(key, {}), k, v) + else: + target[key] = src + + for k, v in patch_data.items(): + _patch(sub_data, k, v) + +################################################################################ +# Update submission data +################################################################################ + +reqUpdate = Request(f"{SERVICE_URL}{submission_url}", method="PUT", + headers={**auth, "Content-Type": "application/json; charset=utf-8"}, + data=json.dumps(sub_data).encode("utf-8")) +print("Updating submission data at", reqUpdate.full_url) +with urlopen(reqUpdate) as r: + new_data = r.read() + +new_data.pop("fileUploadUrl", None) +print("Current submission metadata:") +print(json.dumps(new_data, indent=2)) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy