Skip to content

Commit 990dc35

Browse files
committed
Add upload to Store step to release script.
This doesn't actually work right now, but I'm saving it into a branch against the day when the MSStore CLI/API is able to support uploading files and patching release information *before* submitting. Right now, it all seems to get stuck in limbo until you submit for certification. (Doesn't) fix #116 (yet)
1 parent e5a9ef5 commit 990dc35

File tree

3 files changed

+155
-5
lines changed

3 files changed

+155
-5
lines changed

ci/release.yml

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ parameters:
1919
displayName: "Auto-update users to this release"
2020
type: boolean
2121
default: false
22+
- name: PublishStore
23+
displayName: "Also publish to the Store"
24+
type: boolean
25+
default: false
2226
- name: PreTest
2327
displayName: "Pre test"
2428
type: boolean
@@ -35,6 +39,10 @@ parameters:
3539
displayName: "Test Signed"
3640
type: boolean
3741
default: false
42+
- name: StoreAppId
43+
displayName: "Microsoft Store App Id"
44+
type: string
45+
default: 9NQ7512CXL7T
3846

3947

4048
variables:
@@ -46,7 +54,8 @@ variables:
4654
PIP_VERBOSE: true
4755
PYMSBUILD_VERBOSE: true
4856
PYMSBUILD_TEMP_DIR: $(Build.BinariesDirectory)
49-
DIST_DIR: $(Build.ArtifactStagingDirectory)
57+
DIST_DIR: $(Build.ArtifactStagingDirectory)\dist
58+
STORE_DIST_DIR: $(Build.ArtifactStagingDirectory)\store
5059
LAYOUT_DIR: $(Build.BinariesDirectory)\layout
5160
TEST_MSIX_DIR: $(Build.BinariesDirectory)\test_msix
5261
${{ if ne(parameters.OverrideRef, '(tag)') }}:
@@ -69,6 +78,7 @@ stages:
6978
- ${{ if eq(parameters.TestSign, 'true') }}:
7079
- group: CPythonTestSign
7180
- ${{ if eq(parameters.Publish, 'true') }}:
81+
- group: MSFTStorePublish
7282
- group: PythonOrgPublish
7383

7484

@@ -314,6 +324,13 @@ stages:
314324
workingDirectory: $(Pipeline.Workspace)
315325
displayName: 'Download PuTTY binaries'
316326
327+
- powershell: |
328+
mv "${env:UPLOAD_DIR}\*-store.msix*" (mkdir -Force ${env:STORE_UPLOAD_DIR}) -Verbose
329+
displayName: 'Move Store packages'
330+
env:
331+
UPLOAD_DIR: $(DIST_DIR)
332+
STORE_UPLOAD_DIR: $(STORE_DIST_DIR)
333+
317334
- ${{ if ne(parameters.PublishAppinstaller, 'true') }}:
318335
- powershell: |
319336
"Not uploading these files:"
@@ -323,11 +340,42 @@ stages:
323340
env:
324341
UPLOAD_DIR: $(DIST_DIR)
325342
343+
- ${{ if eq(parameters.PublishStore, 'true') }}:
344+
- task: UseMSStoreCLI@0
345+
displayName: Setup Microsoft Store Developer CLI
346+
347+
- powershell: >
348+
msstore reconfigure
349+
--tenantId $(MSSTORE_TENANT_ID)
350+
--sellerId $(MSSTORE_SELLER_ID)
351+
--clientId $(MSSTORE_CLIENT_ID)
352+
--clientSecret $(MSSTORE_CLIENT_SECRET)
353+
displayName: Authenticate Store CLI
354+
355+
# We begin the submission but do not complete it, so the RM has a chance
356+
# to update metadata before going public. It also means we can do this
357+
# whether signed or not, since a test release can simply be deleted rather
358+
# than published. Existing drafts will be overwritten with new ones.
359+
- powershell: |
360+
$msix = Get-Item "${env:STORE_UPLOAD_DIR}\*.msixupload"
361+
"Uploading $msix"
362+
msstore publish -v -nc -id $env:MSSTORE_APP_ID $msix
363+
"MSIX is uploaded"
364+
"Patching submission details"
365+
python ci\store-publish.py
366+
"Submission details updated"
367+
"Finish publishing at https://partner.microsoft.com/en-us/dashboard/products/${env:MSSTORE_APP_ID}/overview"
368+
displayName: 'Begin Store submission'
369+
env:
370+
STORE_UPLOAD_DIR: $(STORE_DIST_DIR)
371+
MSSTORE_TENANT_ID: $(MSSTORE_TENANT_ID)
372+
MSSTORE_SELLER_ID: $(MSSTORE_SELLER_ID)
373+
MSSTORE_CLIENT_ID: $(MSSTORE_CLIENT_ID)
374+
MSSTORE_CLIENT_SECRET: $(MSSTORE_CLIENT_SECRET)
375+
MSSTORE_APP_ID: ${{ parameters.StoreAppId }}
376+
PATCH_JSON: ci\store-patch.json
377+
326378
- powershell: |
327-
# We don't want the Store MSIX on python.org, so just delete it
328-
# It's already been archived in the earlier publish step, and is bundled
329-
# into the .msixupload file.
330-
del "${env:UPLOAD_DIR}\*-store.msix" -ErrorAction SilentlyContinue
331379
python ci\upload.py
332380
displayName: 'Publish packages'
333381
env:

ci/store-patch.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"#": "This file is used by store-upload.py to modify Store metadata.",
3+
"listings": {
4+
"en-us":{
5+
"baseListing": {
6+
"#": "Update the release notes; 2-3 lines shown to Store users before updating",
7+
"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."
8+
}
9+
}
10+
}
11+
}

ci/store-publish.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import json
2+
import os
3+
import sys
4+
5+
from urllib.request import urlopen, Request
6+
7+
8+
DIRECTORY_ID = os.environ["MSSTORE_TENANT_ID"]
9+
CLIENT_ID = os.environ["MSSTORE_CLIENT_ID"]
10+
CLIENT_SECRET = os.environ["MSSTORE_CLIENT_SECRET"]
11+
SELLER_ID = os.environ["MSSTORE_SELLER_ID"]
12+
APP_ID = os.environ["MSSTORE_APP_ID"]
13+
14+
PATCH_JSON = os.environ["PATCH_JSON"]
15+
with open(PATCH_JSON, "rb") as f:
16+
patch_data = json.load(f)
17+
18+
19+
SERVICE_URL = "https://manage.devcenter.microsoft.com/v1.0/my/"
20+
21+
################################################################################
22+
# Get auth token/header
23+
################################################################################
24+
25+
OAUTH_URL = f"https://login.microsoftonline.com/{DIRECTORY_ID}/oauth2/v2.0/token"
26+
SERVICE_SCOPE = "https://manage.devcenter.microsoft.com/.default"
27+
28+
reqAuth = Request(
29+
OAUTH_URL,
30+
method="POST",
31+
headers={"Content-Type": "application/x-www-form-urlencoded"},
32+
data=(f"grant_type=client_credentials&client_id={CLIENT_ID}&" +
33+
f"client_secret={CLIENT_SECRET}&scope={SERVICE_SCOPE}").encode("utf-8"),
34+
)
35+
36+
with urlopen(reqAuth) as r:
37+
jwt = json.loads(r.read())
38+
39+
auth = {"Authorization": f"Bearer {jwt['access_token']}"}
40+
41+
################################################################################
42+
# Get application data (for current submission)
43+
################################################################################
44+
45+
reqApps = Request(f"{SERVICE_URL}applications/{APP_ID}", method="GET", headers=auth)
46+
print("Getting application data from", reqApps.full_url)
47+
with urlopen(reqApps) as r:
48+
app_data = json.loads(r.read())
49+
50+
submission_url = app_data["pendingApplicationSubmission"]["resourceLocation"]
51+
52+
################################################################################
53+
# Get current submission data
54+
################################################################################
55+
56+
reqSubmission = Request(f"{SERVICE_URL}{submission_url}", method="GET", headers=auth)
57+
print("Getting submission data from", reqSubmission.full_url)
58+
with urlopen(reqSubmission) as r:
59+
sub_data = json.loads(r.read())
60+
61+
################################################################################
62+
# Patch submission data
63+
################################################################################
64+
65+
if patch_data:
66+
def _patch(target, key, src):
67+
if key.startswith("#"):
68+
return
69+
if isinstance(src, dict):
70+
for k, v in src.items():
71+
_patch(target.setdefault(key, {}), k, v)
72+
else:
73+
target[key] = src
74+
75+
for k, v in patch_data.items():
76+
_patch(sub_data, k, v)
77+
78+
################################################################################
79+
# Update submission data
80+
################################################################################
81+
82+
reqUpdate = Request(f"{SERVICE_URL}{submission_url}", method="PUT",
83+
headers={**auth, "Content-Type": "application/json; charset=utf-8"},
84+
data=json.dumps(sub_data).encode("utf-8"))
85+
print("Updating submission data at", reqUpdate.full_url)
86+
with urlopen(reqUpdate) as r:
87+
new_data = r.read()
88+
89+
new_data.pop("fileUploadUrl", None)
90+
print("Current submission metadata:")
91+
print(json.dumps(new_data, indent=2))

0 commit comments

Comments
 (0)