@absolutejs/deploy 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -123
- package/dist/appStoreConnect.d.ts +182 -0
- package/dist/appStoreConnect.js +585 -0
- package/dist/appStoreConnect.js.map +10 -0
- package/dist/googlePlay.d.ts +10 -2
- package/dist/googlePlay.js +4 -1
- package/dist/googlePlay.js.map +3 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +604 -15
- package/dist/index.js.map +6 -5
- package/dist/nativeRelease.d.ts +17 -1
- package/dist/nativeRelease.js +53 -14
- package/dist/nativeRelease.js.map +3 -3
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -10504,24 +10504,49 @@ var parseMetadata = (value) => {
|
|
|
10504
10504
|
throw new NativeReleaseRegistryError("Native release appId is invalid");
|
|
10505
10505
|
if (!SHA256_PATTERN.test(sha256))
|
|
10506
10506
|
throw new NativeReleaseRegistryError("Native release sha256 is invalid");
|
|
10507
|
-
if (
|
|
10507
|
+
if (value.platform !== "android" && value.platform !== "ios")
|
|
10508
|
+
throw new NativeReleaseRegistryError("Native release metadata is invalid");
|
|
10509
|
+
if (releaseId !== `amobile_${value.platform}_${sha256}`)
|
|
10508
10510
|
throw new NativeReleaseRegistryError("Native release id does not match its artifact digest");
|
|
10509
|
-
|
|
10511
|
+
const commonInvalid = value.engine !== "capacitor" || value.format !== 1 || typeof value.signed !== "boolean" || !Number.isSafeInteger(value.bytes) || Number(value.bytes) < 1;
|
|
10512
|
+
if (commonInvalid)
|
|
10513
|
+
throw new NativeReleaseRegistryError("Native release metadata is invalid");
|
|
10514
|
+
if (value.platform === "android") {
|
|
10515
|
+
if (value.artifact !== "app-release.aab" || value.type !== "aab" || value.versionCode !== undefined && (!Number.isSafeInteger(value.versionCode) || Number(value.versionCode) < 1 || Number(value.versionCode) > 2100000000))
|
|
10516
|
+
throw new NativeReleaseRegistryError("Native release metadata is invalid");
|
|
10517
|
+
return {
|
|
10518
|
+
appBuild: requireString(value.appBuild, "appBuild"),
|
|
10519
|
+
appId,
|
|
10520
|
+
artifact: "app-release.aab",
|
|
10521
|
+
bytes: Number(value.bytes),
|
|
10522
|
+
engine: "capacitor",
|
|
10523
|
+
format: 1,
|
|
10524
|
+
platform: "android",
|
|
10525
|
+
releaseId,
|
|
10526
|
+
runtime: requireString(value.runtime, "runtime"),
|
|
10527
|
+
sha256,
|
|
10528
|
+
signed: value.signed === true,
|
|
10529
|
+
type: "aab",
|
|
10530
|
+
...value.versionCode === undefined ? {} : { versionCode: Number(value.versionCode) }
|
|
10531
|
+
};
|
|
10532
|
+
}
|
|
10533
|
+
if (value.artifact !== "App.ipa" || value.type !== "ipa" || typeof value.marketingVersion !== "string" || !/^\d+(?:\.\d+){0,2}$/.test(value.marketingVersion) || value.buildNumber !== undefined && (!Number.isSafeInteger(value.buildNumber) || Number(value.buildNumber) < 1))
|
|
10510
10534
|
throw new NativeReleaseRegistryError("Native release metadata is invalid");
|
|
10511
10535
|
return {
|
|
10512
10536
|
appBuild: requireString(value.appBuild, "appBuild"),
|
|
10513
10537
|
appId,
|
|
10514
|
-
artifact: "
|
|
10538
|
+
artifact: "App.ipa",
|
|
10539
|
+
...value.buildNumber === undefined ? {} : { buildNumber: Number(value.buildNumber) },
|
|
10515
10540
|
bytes: Number(value.bytes),
|
|
10516
10541
|
engine: "capacitor",
|
|
10517
10542
|
format: 1,
|
|
10518
|
-
|
|
10543
|
+
marketingVersion: value.marketingVersion,
|
|
10544
|
+
platform: "ios",
|
|
10519
10545
|
releaseId,
|
|
10520
10546
|
runtime: requireString(value.runtime, "runtime"),
|
|
10521
10547
|
sha256,
|
|
10522
|
-
signed: value.signed,
|
|
10523
|
-
type: "
|
|
10524
|
-
...value.versionCode === undefined ? {} : { versionCode: Number(value.versionCode) }
|
|
10548
|
+
signed: value.signed === true,
|
|
10549
|
+
type: "ipa"
|
|
10525
10550
|
};
|
|
10526
10551
|
};
|
|
10527
10552
|
var normalizedPrefix = (value) => {
|
|
@@ -10563,7 +10588,7 @@ var parseRecord = (value) => {
|
|
|
10563
10588
|
};
|
|
10564
10589
|
};
|
|
10565
10590
|
var parseChannel = (value) => {
|
|
10566
|
-
if (!isRecord(value) || value.format !== NATIVE_RELEASE_REGISTRY_FORMAT || value.platform !== "android" || !isIsoTimestamp(value.promotedAt))
|
|
10591
|
+
if (!isRecord(value) || value.format !== NATIVE_RELEASE_REGISTRY_FORMAT || value.platform !== "android" && value.platform !== "ios" || !isIsoTimestamp(value.promotedAt))
|
|
10567
10592
|
throw new NativeReleaseRegistryError("Native release channel is invalid");
|
|
10568
10593
|
const appId = requireString(value.appId, "channel appId");
|
|
10569
10594
|
const channel = requireChannel(requireString(value.channel, "channel"));
|
|
@@ -10571,13 +10596,13 @@ var parseChannel = (value) => {
|
|
|
10571
10596
|
const releaseId = requireString(value.releaseId, "channel releaseId");
|
|
10572
10597
|
if (!APP_ID_PATTERN.test(appId) || !SHA256_PATTERN.test(sha256))
|
|
10573
10598
|
throw new NativeReleaseRegistryError("Native release channel is invalid");
|
|
10574
|
-
if (releaseId !== `
|
|
10599
|
+
if (releaseId !== `amobile_${value.platform}_${sha256}`)
|
|
10575
10600
|
throw new NativeReleaseRegistryError("Native release channel identity does not match");
|
|
10576
10601
|
return {
|
|
10577
10602
|
appId,
|
|
10578
10603
|
channel,
|
|
10579
10604
|
format: NATIVE_RELEASE_REGISTRY_FORMAT,
|
|
10580
|
-
platform:
|
|
10605
|
+
platform: value.platform,
|
|
10581
10606
|
promotedAt: value.promotedAt,
|
|
10582
10607
|
releaseId,
|
|
10583
10608
|
sha256
|
|
@@ -10602,10 +10627,10 @@ var createNativeReleaseRegistry = (options) => {
|
|
|
10602
10627
|
const read = async (input) => {
|
|
10603
10628
|
if (!APP_ID_PATTERN.test(input.appId))
|
|
10604
10629
|
throw new NativeReleaseRegistryError("Native release appId is invalid");
|
|
10605
|
-
const digest = input.releaseId.replace(
|
|
10606
|
-
if (!SHA256_PATTERN.test(digest) || input.releaseId !== `
|
|
10630
|
+
const digest = input.releaseId.replace(new RegExp(`^amobile_${input.platform}_`), "");
|
|
10631
|
+
if (!SHA256_PATTERN.test(digest) || input.releaseId !== `amobile_${input.platform}_${digest}`)
|
|
10607
10632
|
throw new NativeReleaseRegistryError("Native release id is invalid");
|
|
10608
|
-
const identity = {
|
|
10633
|
+
const identity = input.platform === "android" ? {
|
|
10609
10634
|
appBuild: "lookup",
|
|
10610
10635
|
appId: input.appId,
|
|
10611
10636
|
artifact: "app-release.aab",
|
|
@@ -10618,6 +10643,20 @@ var createNativeReleaseRegistry = (options) => {
|
|
|
10618
10643
|
sha256: digest,
|
|
10619
10644
|
signed: true,
|
|
10620
10645
|
type: "aab"
|
|
10646
|
+
} : {
|
|
10647
|
+
appBuild: "lookup",
|
|
10648
|
+
appId: input.appId,
|
|
10649
|
+
artifact: "App.ipa",
|
|
10650
|
+
bytes: 1,
|
|
10651
|
+
engine: "capacitor",
|
|
10652
|
+
format: 1,
|
|
10653
|
+
marketingVersion: "1.0.0",
|
|
10654
|
+
platform: "ios",
|
|
10655
|
+
releaseId: input.releaseId,
|
|
10656
|
+
runtime: "lookup",
|
|
10657
|
+
sha256: digest,
|
|
10658
|
+
signed: true,
|
|
10659
|
+
type: "ipa"
|
|
10621
10660
|
};
|
|
10622
10661
|
const key = recordKey(identity);
|
|
10623
10662
|
const bytes = await options.store.get(key);
|
|
@@ -11126,6 +11165,7 @@ var createGooglePlayReleasePublisher = (options) => {
|
|
|
11126
11165
|
updatedAt: clock().toISOString()
|
|
11127
11166
|
});
|
|
11128
11167
|
return {
|
|
11168
|
+
...options.registry,
|
|
11129
11169
|
prepareAndroidRelease: async (input) => {
|
|
11130
11170
|
const target = input.googlePlay ?? options.target;
|
|
11131
11171
|
if (!target)
|
|
@@ -11167,6 +11207,8 @@ var createGooglePlayReleasePublisher = (options) => {
|
|
|
11167
11207
|
return publication;
|
|
11168
11208
|
const intent = normalizedTarget(target);
|
|
11169
11209
|
const metadata = publication.record.metadata;
|
|
11210
|
+
if (metadata.platform !== "android")
|
|
11211
|
+
throw new GooglePlayReleaseError("Google Play publishing requires an Android App Bundle release");
|
|
11170
11212
|
if (!metadata.signed)
|
|
11171
11213
|
throw new GooglePlayReleaseError("Unsigned Android releases cannot be sent to Google Play");
|
|
11172
11214
|
if (!metadata.versionCode)
|
|
@@ -11320,6 +11362,550 @@ var createGooglePlayReleasePublisher = (options) => {
|
|
|
11320
11362
|
};
|
|
11321
11363
|
};
|
|
11322
11364
|
|
|
11365
|
+
// src/appStoreConnect.ts
|
|
11366
|
+
import { createHash as createHash3, createPrivateKey, sign } from "crypto";
|
|
11367
|
+
import path4 from "path";
|
|
11368
|
+
var API_ROOT2 = "https://api.appstoreconnect.apple.com/v1";
|
|
11369
|
+
var DEFAULT_RECEIPT_PREFIX2 = "absolutejs/app-store-connect-receipts";
|
|
11370
|
+
var SHA256_PATTERN3 = /^[a-f0-9]{64}$/;
|
|
11371
|
+
|
|
11372
|
+
class AppStoreConnectReleaseError extends Error {
|
|
11373
|
+
status;
|
|
11374
|
+
constructor(message, status) {
|
|
11375
|
+
super(message);
|
|
11376
|
+
this.name = "AppStoreConnectReleaseError";
|
|
11377
|
+
this.status = status;
|
|
11378
|
+
}
|
|
11379
|
+
}
|
|
11380
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11381
|
+
var encodedJson3 = (value) => new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
|
|
11382
|
+
`);
|
|
11383
|
+
var decodedJson3 = (value) => JSON.parse(new TextDecoder().decode(value));
|
|
11384
|
+
var sha256Bytes3 = (value) => createHash3("sha256").update(value).digest("hex");
|
|
11385
|
+
var hashIdentity2 = (value) => createHash3("sha256").update(value).digest("hex");
|
|
11386
|
+
var isIsoTimestamp3 = (value) => typeof value === "string" && !Number.isNaN(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
11387
|
+
var normalizedPrefix3 = (value) => {
|
|
11388
|
+
const prefix = value.replace(/^\/+|\/+$/g, "");
|
|
11389
|
+
if (!prefix || prefix.split("/").some((part) => part === "." || part === ".."))
|
|
11390
|
+
throw new AppStoreConnectReleaseError("App Store Connect receipt prefix is invalid");
|
|
11391
|
+
return prefix;
|
|
11392
|
+
};
|
|
11393
|
+
var normalizedIntent = (target = {}) => {
|
|
11394
|
+
const groups = [
|
|
11395
|
+
...new Set((target.groups ?? []).map((value) => value.trim()))
|
|
11396
|
+
].sort();
|
|
11397
|
+
if (groups.some((value) => !value))
|
|
11398
|
+
throw new AppStoreConnectReleaseError("TestFlight group names or IDs must not be empty");
|
|
11399
|
+
const locales = new Set;
|
|
11400
|
+
const whatsNew = [...target.whatsNew ?? []].map(({ locale, text }) => ({ locale: locale.trim(), text: text.trim() })).sort((left, right) => left.locale.localeCompare(right.locale));
|
|
11401
|
+
for (const item of whatsNew) {
|
|
11402
|
+
if (!item.locale || !item.text || locales.has(item.locale))
|
|
11403
|
+
throw new AppStoreConnectReleaseError("TestFlight notes require unique locales and non-empty text");
|
|
11404
|
+
locales.add(item.locale);
|
|
11405
|
+
}
|
|
11406
|
+
if (target.submitForReview && groups.length === 0)
|
|
11407
|
+
throw new AppStoreConnectReleaseError("TestFlight beta review requires at least one external group");
|
|
11408
|
+
return { groups, submitForReview: target.submitForReview ?? false, whatsNew };
|
|
11409
|
+
};
|
|
11410
|
+
var parseReceipt2 = (value) => {
|
|
11411
|
+
if (!isRecord3(value) || value.format !== 1 || value.provider !== "app-store-connect" || typeof value.appleAppId !== "string" || typeof value.releaseId !== "string" || typeof value.sha256 !== "string" || !SHA256_PATTERN3.test(value.sha256) || !Number.isSafeInteger(value.buildNumber) || Number(value.buildNumber) < 1 || typeof value.marketingVersion !== "string" || !isIsoTimestamp3(value.updatedAt) || ![
|
|
11412
|
+
"preparing",
|
|
11413
|
+
"uploading",
|
|
11414
|
+
"processing",
|
|
11415
|
+
"distributed",
|
|
11416
|
+
"review-submitted"
|
|
11417
|
+
].includes(String(value.stage)) || !isRecord3(value.intent))
|
|
11418
|
+
throw new AppStoreConnectReleaseError("App Store Connect release receipt is invalid");
|
|
11419
|
+
for (const field of ["buildId", "buildUploadFileId", "buildUploadId"])
|
|
11420
|
+
if (value[field] !== undefined && typeof value[field] !== "string")
|
|
11421
|
+
throw new AppStoreConnectReleaseError("App Store Connect release receipt is invalid");
|
|
11422
|
+
const buildId = typeof value.buildId === "string" ? value.buildId : undefined;
|
|
11423
|
+
const buildUploadFileId = typeof value.buildUploadFileId === "string" ? value.buildUploadFileId : undefined;
|
|
11424
|
+
const buildUploadId = typeof value.buildUploadId === "string" ? value.buildUploadId : undefined;
|
|
11425
|
+
return {
|
|
11426
|
+
appleAppId: value.appleAppId,
|
|
11427
|
+
buildNumber: Number(value.buildNumber),
|
|
11428
|
+
format: 1,
|
|
11429
|
+
intent: normalizedIntent(value.intent),
|
|
11430
|
+
marketingVersion: value.marketingVersion,
|
|
11431
|
+
provider: "app-store-connect",
|
|
11432
|
+
releaseId: value.releaseId,
|
|
11433
|
+
sha256: value.sha256,
|
|
11434
|
+
stage: value.stage,
|
|
11435
|
+
updatedAt: value.updatedAt,
|
|
11436
|
+
...buildId ? { buildId } : {},
|
|
11437
|
+
...buildUploadFileId ? { buildUploadFileId } : {},
|
|
11438
|
+
...buildUploadId ? { buildUploadId } : {}
|
|
11439
|
+
};
|
|
11440
|
+
};
|
|
11441
|
+
var base64url = (value) => Buffer.from(value).toString("base64url");
|
|
11442
|
+
var createTokenProvider = (auth, clock) => {
|
|
11443
|
+
if (!auth.issuerId || !auth.keyId || !auth.privateKey)
|
|
11444
|
+
throw new AppStoreConnectReleaseError("App Store Connect API credentials are incomplete");
|
|
11445
|
+
const key = createPrivateKey(auth.privateKey);
|
|
11446
|
+
return () => {
|
|
11447
|
+
const issuedAt = Math.floor(clock().getTime() / 1000);
|
|
11448
|
+
const header = base64url(JSON.stringify({ alg: "ES256", kid: auth.keyId, typ: "JWT" }));
|
|
11449
|
+
const claims = base64url(JSON.stringify({
|
|
11450
|
+
aud: "appstoreconnect-v1",
|
|
11451
|
+
exp: issuedAt + 1199,
|
|
11452
|
+
iat: issuedAt,
|
|
11453
|
+
iss: auth.issuerId
|
|
11454
|
+
}));
|
|
11455
|
+
const payload = `${header}.${claims}`;
|
|
11456
|
+
return `${payload}.${base64url(sign("sha256", Buffer.from(payload), { dsaEncoding: "ieee-p1363", key }))}`;
|
|
11457
|
+
};
|
|
11458
|
+
};
|
|
11459
|
+
var responseError2 = async (response) => {
|
|
11460
|
+
const body = await response.text().catch(() => "");
|
|
11461
|
+
return new AppStoreConnectReleaseError(`App Store Connect request failed (${response.status})${body ? `: ${body}` : ""}`, response.status);
|
|
11462
|
+
};
|
|
11463
|
+
var createAppStoreConnectClient = (options) => {
|
|
11464
|
+
const requestFetch = options.fetch ?? fetch;
|
|
11465
|
+
const token = createTokenProvider(options.auth, options.clock ?? (() => new Date));
|
|
11466
|
+
const request = async (route, init = {}) => {
|
|
11467
|
+
const response = await requestFetch(`${API_ROOT2}${route}`, {
|
|
11468
|
+
...init,
|
|
11469
|
+
headers: {
|
|
11470
|
+
Accept: "application/json",
|
|
11471
|
+
Authorization: `Bearer ${token()}`,
|
|
11472
|
+
...init.body ? { "Content-Type": "application/json" } : {},
|
|
11473
|
+
...init.headers
|
|
11474
|
+
}
|
|
11475
|
+
});
|
|
11476
|
+
if (!response.ok)
|
|
11477
|
+
throw await responseError2(response);
|
|
11478
|
+
return response.status === 204 ? undefined : await response.json();
|
|
11479
|
+
};
|
|
11480
|
+
const dataList = async (route) => {
|
|
11481
|
+
const result = [];
|
|
11482
|
+
let next = route;
|
|
11483
|
+
while (next) {
|
|
11484
|
+
const page = await request(next.startsWith(API_ROOT2) ? next.slice(API_ROOT2.length) : next);
|
|
11485
|
+
if (Array.isArray(page.data))
|
|
11486
|
+
result.push(...page.data.filter(isRecord3));
|
|
11487
|
+
const links = isRecord3(page.links) ? page.links : undefined;
|
|
11488
|
+
next = typeof links?.next === "string" ? links.next : undefined;
|
|
11489
|
+
}
|
|
11490
|
+
return result;
|
|
11491
|
+
};
|
|
11492
|
+
const buildFrom = (data) => {
|
|
11493
|
+
const attributes = isRecord3(data.attributes) ? data.attributes : {};
|
|
11494
|
+
return {
|
|
11495
|
+
id: String(data.id),
|
|
11496
|
+
processingState: String(attributes.processingState),
|
|
11497
|
+
version: String(attributes.version)
|
|
11498
|
+
};
|
|
11499
|
+
};
|
|
11500
|
+
const uploadFrom = (data) => {
|
|
11501
|
+
const attributes = isRecord3(data.attributes) ? data.attributes : {};
|
|
11502
|
+
const state = isRecord3(attributes.state) ? attributes.state.state : attributes.state;
|
|
11503
|
+
return {
|
|
11504
|
+
id: String(data.id),
|
|
11505
|
+
state: String(state)
|
|
11506
|
+
};
|
|
11507
|
+
};
|
|
11508
|
+
return {
|
|
11509
|
+
findAppId: async ({ bundleId, signal }) => {
|
|
11510
|
+
const items = await dataList(`/apps?filter[bundleId]=${encodeURIComponent(bundleId)}&fields[apps]=bundleId&limit=2`);
|
|
11511
|
+
const exact = items.filter((item) => isRecord3(item.attributes) && item.attributes.bundleId === bundleId);
|
|
11512
|
+
if (exact.length !== 1)
|
|
11513
|
+
throw new AppStoreConnectReleaseError(`Expected one App Store Connect app for bundle ID ${bundleId}`);
|
|
11514
|
+
signal?.throwIfAborted();
|
|
11515
|
+
return String(exact[0].id);
|
|
11516
|
+
},
|
|
11517
|
+
listBuildNumbers: async ({ appId, signal }) => {
|
|
11518
|
+
signal?.throwIfAborted();
|
|
11519
|
+
const items = await dataList(`/builds?filter[app]=${encodeURIComponent(appId)}&fields[builds]=version&limit=200`);
|
|
11520
|
+
return items.map((item) => Number(isRecord3(item.attributes) ? item.attributes.version : NaN)).filter((value) => Number.isSafeInteger(value) && value > 0);
|
|
11521
|
+
},
|
|
11522
|
+
findBuild: async ({ appId, buildNumber, signal }) => {
|
|
11523
|
+
signal?.throwIfAborted();
|
|
11524
|
+
const items = await dataList(`/builds?filter[app]=${encodeURIComponent(appId)}&filter[version]=${buildNumber}&fields[builds]=version,processingState&limit=2`);
|
|
11525
|
+
return items.length ? buildFrom(items[0]) : null;
|
|
11526
|
+
},
|
|
11527
|
+
findBuildUpload: async ({
|
|
11528
|
+
appId,
|
|
11529
|
+
buildNumber,
|
|
11530
|
+
marketingVersion,
|
|
11531
|
+
signal
|
|
11532
|
+
}) => {
|
|
11533
|
+
signal?.throwIfAborted();
|
|
11534
|
+
const items = await dataList(`/apps/${encodeURIComponent(appId)}/buildUploads?filter[cfBundleVersion]=${buildNumber}&filter[cfBundleShortVersionString]=${encodeURIComponent(marketingVersion)}&filter[platform]=IOS&fields[buildUploads]=state,cfBundleVersion,cfBundleShortVersionString&limit=2`);
|
|
11535
|
+
return items.length ? uploadFrom(items[0]) : null;
|
|
11536
|
+
},
|
|
11537
|
+
createBuildUpload: async ({
|
|
11538
|
+
appId,
|
|
11539
|
+
buildNumber,
|
|
11540
|
+
marketingVersion,
|
|
11541
|
+
signal
|
|
11542
|
+
}) => {
|
|
11543
|
+
const response = await request("/buildUploads", {
|
|
11544
|
+
method: "POST",
|
|
11545
|
+
signal,
|
|
11546
|
+
body: JSON.stringify({
|
|
11547
|
+
data: {
|
|
11548
|
+
type: "buildUploads",
|
|
11549
|
+
attributes: {
|
|
11550
|
+
cfBundleVersion: String(buildNumber),
|
|
11551
|
+
cfBundleShortVersionString: marketingVersion,
|
|
11552
|
+
platform: "IOS"
|
|
11553
|
+
},
|
|
11554
|
+
relationships: { app: { data: { type: "apps", id: appId } } }
|
|
11555
|
+
}
|
|
11556
|
+
})
|
|
11557
|
+
});
|
|
11558
|
+
return uploadFrom(response.data);
|
|
11559
|
+
},
|
|
11560
|
+
getBuildUpload: async ({ buildUploadId, signal }) => {
|
|
11561
|
+
const response = await request(`/buildUploads/${encodeURIComponent(buildUploadId)}?fields[buildUploads]=state`, { signal });
|
|
11562
|
+
return uploadFrom(response.data);
|
|
11563
|
+
},
|
|
11564
|
+
createBuildUploadFile: async ({
|
|
11565
|
+
buildUploadId,
|
|
11566
|
+
bytes,
|
|
11567
|
+
fileName,
|
|
11568
|
+
signal
|
|
11569
|
+
}) => {
|
|
11570
|
+
const response = await request("/buildUploadFiles", {
|
|
11571
|
+
method: "POST",
|
|
11572
|
+
signal,
|
|
11573
|
+
body: JSON.stringify({
|
|
11574
|
+
data: {
|
|
11575
|
+
type: "buildUploadFiles",
|
|
11576
|
+
attributes: {
|
|
11577
|
+
assetType: "ASSET",
|
|
11578
|
+
fileName,
|
|
11579
|
+
fileSize: bytes,
|
|
11580
|
+
uti: "com.apple.ipa"
|
|
11581
|
+
},
|
|
11582
|
+
relationships: {
|
|
11583
|
+
buildUpload: {
|
|
11584
|
+
data: { type: "buildUploads", id: buildUploadId }
|
|
11585
|
+
}
|
|
11586
|
+
}
|
|
11587
|
+
}
|
|
11588
|
+
})
|
|
11589
|
+
});
|
|
11590
|
+
return String(response.data.id);
|
|
11591
|
+
},
|
|
11592
|
+
findBuildUploadFile: async ({ buildUploadId, signal }) => {
|
|
11593
|
+
signal?.throwIfAborted();
|
|
11594
|
+
const items = await dataList(`/buildUploads/${encodeURIComponent(buildUploadId)}/buildUploadFiles?fields[buildUploadFiles]=fileName&limit=2`);
|
|
11595
|
+
return items.length ? String(items[0].id) : null;
|
|
11596
|
+
},
|
|
11597
|
+
uploadBuildFile: async ({ artifactPath, fileId, signal }) => {
|
|
11598
|
+
const response = await request(`/buildUploadFiles/${encodeURIComponent(fileId)}?fields[buildUploadFiles]=uploadOperations`, { signal });
|
|
11599
|
+
const data = response.data;
|
|
11600
|
+
const attrs = isRecord3(data.attributes) ? data.attributes : {};
|
|
11601
|
+
const operations = Array.isArray(attrs.uploadOperations) ? attrs.uploadOperations.filter(isRecord3) : [];
|
|
11602
|
+
if (!operations.length)
|
|
11603
|
+
throw new AppStoreConnectReleaseError("App Store Connect returned no IPA upload operations");
|
|
11604
|
+
await Promise.all(operations.map(async (operation) => {
|
|
11605
|
+
const offset = Number(operation.offset);
|
|
11606
|
+
const length = Number(operation.length);
|
|
11607
|
+
const headers = new Headers;
|
|
11608
|
+
if (Array.isArray(operation.requestHeaders)) {
|
|
11609
|
+
for (const entry of operation.requestHeaders)
|
|
11610
|
+
if (isRecord3(entry))
|
|
11611
|
+
headers.set(String(entry.name), String(entry.value));
|
|
11612
|
+
}
|
|
11613
|
+
const upload = await requestFetch(String(operation.url), {
|
|
11614
|
+
method: String(operation.method),
|
|
11615
|
+
headers,
|
|
11616
|
+
body: Bun.file(artifactPath).slice(offset, offset + length),
|
|
11617
|
+
signal
|
|
11618
|
+
});
|
|
11619
|
+
if (!upload.ok)
|
|
11620
|
+
throw await responseError2(upload);
|
|
11621
|
+
}));
|
|
11622
|
+
},
|
|
11623
|
+
commitBuildUploadFile: async ({ fileId, sha256, signal }) => {
|
|
11624
|
+
await request(`/buildUploadFiles/${encodeURIComponent(fileId)}`, {
|
|
11625
|
+
method: "PATCH",
|
|
11626
|
+
signal,
|
|
11627
|
+
body: JSON.stringify({
|
|
11628
|
+
data: {
|
|
11629
|
+
type: "buildUploadFiles",
|
|
11630
|
+
id: fileId,
|
|
11631
|
+
attributes: {
|
|
11632
|
+
uploaded: true,
|
|
11633
|
+
sourceFileChecksums: {
|
|
11634
|
+
file: { algorithm: "SHA_256", hash: sha256 }
|
|
11635
|
+
}
|
|
11636
|
+
}
|
|
11637
|
+
}
|
|
11638
|
+
})
|
|
11639
|
+
});
|
|
11640
|
+
},
|
|
11641
|
+
resolveGroups: async ({ appId, groups, signal }) => {
|
|
11642
|
+
signal?.throwIfAborted();
|
|
11643
|
+
const items = await dataList(`/betaGroups?filter[app]=${encodeURIComponent(appId)}&fields[betaGroups]=name,isInternalGroup&limit=200`);
|
|
11644
|
+
return groups.map((requested) => {
|
|
11645
|
+
const matches = items.filter((item2) => item2.id === requested || isRecord3(item2.attributes) && item2.attributes.name === requested);
|
|
11646
|
+
if (matches.length !== 1)
|
|
11647
|
+
throw new AppStoreConnectReleaseError(`Expected one TestFlight group matching ${requested}`);
|
|
11648
|
+
const item = matches[0];
|
|
11649
|
+
const attributes = item.attributes;
|
|
11650
|
+
return {
|
|
11651
|
+
id: String(item.id),
|
|
11652
|
+
isInternal: attributes.isInternalGroup === true,
|
|
11653
|
+
name: String(attributes.name)
|
|
11654
|
+
};
|
|
11655
|
+
});
|
|
11656
|
+
},
|
|
11657
|
+
addBuildToGroup: async ({ buildId, groupId, signal }) => {
|
|
11658
|
+
await request(`/betaGroups/${encodeURIComponent(groupId)}/relationships/builds`, {
|
|
11659
|
+
method: "POST",
|
|
11660
|
+
signal,
|
|
11661
|
+
body: JSON.stringify({ data: [{ type: "builds", id: buildId }] })
|
|
11662
|
+
});
|
|
11663
|
+
},
|
|
11664
|
+
upsertWhatsNew: async ({ buildId, locale, text, signal }) => {
|
|
11665
|
+
const items = await dataList(`/builds/${encodeURIComponent(buildId)}/betaBuildLocalizations?fields[betaBuildLocalizations]=locale,whatsNew&limit=200`);
|
|
11666
|
+
const existing = items.find((item) => isRecord3(item.attributes) && item.attributes.locale === locale);
|
|
11667
|
+
if (existing) {
|
|
11668
|
+
await request(`/betaBuildLocalizations/${encodeURIComponent(String(existing.id))}`, {
|
|
11669
|
+
method: "PATCH",
|
|
11670
|
+
signal,
|
|
11671
|
+
body: JSON.stringify({
|
|
11672
|
+
data: {
|
|
11673
|
+
type: "betaBuildLocalizations",
|
|
11674
|
+
id: existing.id,
|
|
11675
|
+
attributes: { whatsNew: text }
|
|
11676
|
+
}
|
|
11677
|
+
})
|
|
11678
|
+
});
|
|
11679
|
+
} else {
|
|
11680
|
+
await request("/betaBuildLocalizations", {
|
|
11681
|
+
method: "POST",
|
|
11682
|
+
signal,
|
|
11683
|
+
body: JSON.stringify({
|
|
11684
|
+
data: {
|
|
11685
|
+
type: "betaBuildLocalizations",
|
|
11686
|
+
attributes: { locale, whatsNew: text },
|
|
11687
|
+
relationships: {
|
|
11688
|
+
build: { data: { type: "builds", id: buildId } }
|
|
11689
|
+
}
|
|
11690
|
+
}
|
|
11691
|
+
})
|
|
11692
|
+
});
|
|
11693
|
+
}
|
|
11694
|
+
},
|
|
11695
|
+
submitBetaReview: async ({ buildId, signal }) => {
|
|
11696
|
+
await request("/betaAppReviewSubmissions", {
|
|
11697
|
+
method: "POST",
|
|
11698
|
+
signal,
|
|
11699
|
+
body: JSON.stringify({
|
|
11700
|
+
data: {
|
|
11701
|
+
type: "betaAppReviewSubmissions",
|
|
11702
|
+
relationships: { build: { data: { type: "builds", id: buildId } } }
|
|
11703
|
+
}
|
|
11704
|
+
})
|
|
11705
|
+
});
|
|
11706
|
+
},
|
|
11707
|
+
hasBetaReviewSubmission: async ({ buildId, signal }) => {
|
|
11708
|
+
const response = await request(`/builds/${encodeURIComponent(buildId)}/relationships/betaAppReviewSubmission`, { signal });
|
|
11709
|
+
return isRecord3(response.data) && typeof response.data.id === "string";
|
|
11710
|
+
}
|
|
11711
|
+
};
|
|
11712
|
+
};
|
|
11713
|
+
var createAppStoreConnectReleasePublisher = (options) => {
|
|
11714
|
+
const clock = options.clock ?? (() => new Date);
|
|
11715
|
+
const client = options.client ?? (options.auth ? createAppStoreConnectClient({ auth: options.auth, clock }) : undefined);
|
|
11716
|
+
if (!client)
|
|
11717
|
+
throw new AppStoreConnectReleaseError("App Store Connect auth or client is required");
|
|
11718
|
+
const prefix = normalizedPrefix3(options.receiptPrefix ?? DEFAULT_RECEIPT_PREFIX2);
|
|
11719
|
+
const maxWaitMs = options.maxWaitMs ?? 20 * 60000;
|
|
11720
|
+
const pollIntervalMs = options.pollIntervalMs ?? 5000;
|
|
11721
|
+
const preparationKey = (bundleId, buildIdentity) => `${prefix}/${hashIdentity2(bundleId)}/preparations/${hashIdentity2(buildIdentity)}.json`;
|
|
11722
|
+
const receiptKey = (appId, releaseId, intent) => `${prefix}/${hashIdentity2(appId)}/${releaseId}/${hashIdentity2(JSON.stringify(intent))}.json`;
|
|
11723
|
+
const readStored = async (key) => {
|
|
11724
|
+
const bytes = await options.receiptStore.get(key);
|
|
11725
|
+
if (!bytes)
|
|
11726
|
+
return null;
|
|
11727
|
+
const head = await options.receiptStore.head(key);
|
|
11728
|
+
if (!head || head.size !== bytes.byteLength || head.metadata?.sha256 !== sha256Bytes3(bytes))
|
|
11729
|
+
throw new AppStoreConnectReleaseError("Stored App Store Connect state failed integrity verification");
|
|
11730
|
+
return decodedJson3(bytes);
|
|
11731
|
+
};
|
|
11732
|
+
const writeStored = async (key, value, type) => {
|
|
11733
|
+
const bytes = encodedJson3(value);
|
|
11734
|
+
await options.receiptStore.put(key, bytes, {
|
|
11735
|
+
cacheControl: "no-cache",
|
|
11736
|
+
contentType: "application/json",
|
|
11737
|
+
maxBytes: bytes.byteLength,
|
|
11738
|
+
metadata: { sha256: sha256Bytes3(bytes), type }
|
|
11739
|
+
});
|
|
11740
|
+
};
|
|
11741
|
+
const waitForBuild = async (appId, buildNumber, buildUploadId, signal) => {
|
|
11742
|
+
const deadline = Date.now() + maxWaitMs;
|
|
11743
|
+
while (Date.now() <= deadline) {
|
|
11744
|
+
signal?.throwIfAborted();
|
|
11745
|
+
const build = await client.findBuild({ appId, buildNumber, signal });
|
|
11746
|
+
if (build?.processingState === "VALID")
|
|
11747
|
+
return build;
|
|
11748
|
+
if (build && (build.processingState === "FAILED" || build.processingState === "INVALID"))
|
|
11749
|
+
throw new AppStoreConnectReleaseError(`Apple rejected build ${buildNumber} during processing (${build.processingState})`);
|
|
11750
|
+
const upload = await client.getBuildUpload({ buildUploadId, signal });
|
|
11751
|
+
if (upload.state === "FAILED")
|
|
11752
|
+
throw new AppStoreConnectReleaseError(`Apple rejected build upload ${buildUploadId}`);
|
|
11753
|
+
await Bun.sleep(pollIntervalMs);
|
|
11754
|
+
}
|
|
11755
|
+
throw new AppStoreConnectReleaseError(`Timed out waiting for Apple to process build ${buildNumber}`);
|
|
11756
|
+
};
|
|
11757
|
+
return {
|
|
11758
|
+
...options.registry,
|
|
11759
|
+
prepareIosRelease: async ({ buildIdentity, bundleId, signal }) => {
|
|
11760
|
+
if (!buildIdentity)
|
|
11761
|
+
throw new AppStoreConnectReleaseError("App Store Connect build identity is invalid");
|
|
11762
|
+
const key = preparationKey(bundleId, buildIdentity);
|
|
11763
|
+
const stored = await readStored(key);
|
|
11764
|
+
if (isRecord3(stored) && stored.bundleId === bundleId && stored.buildIdentity === buildIdentity && Number.isSafeInteger(stored.buildNumber) && Number(stored.buildNumber) > 0)
|
|
11765
|
+
return { buildNumber: Number(stored.buildNumber) };
|
|
11766
|
+
const appleAppId = await client.findAppId({ bundleId, signal });
|
|
11767
|
+
const numbers = await client.listBuildNumbers({
|
|
11768
|
+
appId: appleAppId,
|
|
11769
|
+
signal
|
|
11770
|
+
});
|
|
11771
|
+
const highest = numbers.reduce((maximum, value) => Math.max(maximum, value), 0);
|
|
11772
|
+
if (!Number.isSafeInteger(highest) || highest >= Number.MAX_SAFE_INTEGER)
|
|
11773
|
+
throw new AppStoreConnectReleaseError("App Store Connect cannot allocate another iOS build number");
|
|
11774
|
+
const buildNumber = highest + 1;
|
|
11775
|
+
await writeStored(key, {
|
|
11776
|
+
appleAppId,
|
|
11777
|
+
buildIdentity,
|
|
11778
|
+
buildNumber,
|
|
11779
|
+
bundleId,
|
|
11780
|
+
format: 1,
|
|
11781
|
+
preparedAt: clock().toISOString()
|
|
11782
|
+
}, "app-store-connect-build-preparation");
|
|
11783
|
+
return { buildNumber };
|
|
11784
|
+
},
|
|
11785
|
+
publish: async (input) => {
|
|
11786
|
+
const publication = await options.registry.publish(input);
|
|
11787
|
+
const metadata = publication.record.metadata;
|
|
11788
|
+
const target = input.appStoreConnect ?? options.target;
|
|
11789
|
+
if (!target)
|
|
11790
|
+
return publication;
|
|
11791
|
+
if (metadata.platform !== "ios" || metadata.type !== "ipa")
|
|
11792
|
+
throw new AppStoreConnectReleaseError("App Store Connect publishing requires an iOS IPA release");
|
|
11793
|
+
if (!metadata.signed || !metadata.buildNumber)
|
|
11794
|
+
throw new AppStoreConnectReleaseError("App Store Connect publishing requires a signed, versioned IPA");
|
|
11795
|
+
const intent = normalizedIntent(target);
|
|
11796
|
+
const appleAppId = await client.findAppId({
|
|
11797
|
+
bundleId: metadata.appId,
|
|
11798
|
+
signal: input.signal
|
|
11799
|
+
});
|
|
11800
|
+
const key = receiptKey(appleAppId, metadata.releaseId, intent);
|
|
11801
|
+
const existingValue = await readStored(key);
|
|
11802
|
+
let receipt = existingValue ? parseReceipt2(existingValue) : {
|
|
11803
|
+
appleAppId,
|
|
11804
|
+
buildNumber: metadata.buildNumber,
|
|
11805
|
+
format: 1,
|
|
11806
|
+
intent,
|
|
11807
|
+
marketingVersion: metadata.marketingVersion,
|
|
11808
|
+
provider: "app-store-connect",
|
|
11809
|
+
releaseId: metadata.releaseId,
|
|
11810
|
+
sha256: metadata.sha256,
|
|
11811
|
+
stage: "preparing",
|
|
11812
|
+
updatedAt: clock().toISOString()
|
|
11813
|
+
};
|
|
11814
|
+
const writeReceipt = async (values) => {
|
|
11815
|
+
receipt = { ...receipt, ...values, updatedAt: clock().toISOString() };
|
|
11816
|
+
await writeStored(key, receipt, "app-store-connect-release-receipt");
|
|
11817
|
+
};
|
|
11818
|
+
if (receipt.stage === "distributed" || receipt.stage === "review-submitted")
|
|
11819
|
+
return { ...publication, appStoreConnect: { receipt, reused: true } };
|
|
11820
|
+
let upload = receipt.buildUploadId ? await client.getBuildUpload({
|
|
11821
|
+
buildUploadId: receipt.buildUploadId,
|
|
11822
|
+
signal: input.signal
|
|
11823
|
+
}) : await client.findBuildUpload({
|
|
11824
|
+
appId: appleAppId,
|
|
11825
|
+
buildNumber: metadata.buildNumber,
|
|
11826
|
+
marketingVersion: metadata.marketingVersion,
|
|
11827
|
+
signal: input.signal
|
|
11828
|
+
});
|
|
11829
|
+
if (!upload)
|
|
11830
|
+
upload = await client.createBuildUpload({
|
|
11831
|
+
appId: appleAppId,
|
|
11832
|
+
buildNumber: metadata.buildNumber,
|
|
11833
|
+
marketingVersion: metadata.marketingVersion,
|
|
11834
|
+
signal: input.signal
|
|
11835
|
+
});
|
|
11836
|
+
await writeReceipt({ buildUploadId: upload.id, stage: "uploading" });
|
|
11837
|
+
let build = await client.findBuild({
|
|
11838
|
+
appId: appleAppId,
|
|
11839
|
+
buildNumber: metadata.buildNumber,
|
|
11840
|
+
signal: input.signal
|
|
11841
|
+
});
|
|
11842
|
+
if (!build || build.processingState !== "VALID") {
|
|
11843
|
+
let fileId = receipt.buildUploadFileId;
|
|
11844
|
+
if (!fileId) {
|
|
11845
|
+
fileId = await client.findBuildUploadFile({
|
|
11846
|
+
buildUploadId: upload.id,
|
|
11847
|
+
signal: input.signal
|
|
11848
|
+
}) ?? await client.createBuildUploadFile({
|
|
11849
|
+
buildUploadId: upload.id,
|
|
11850
|
+
bytes: metadata.bytes,
|
|
11851
|
+
fileName: metadata.artifact,
|
|
11852
|
+
signal: input.signal
|
|
11853
|
+
});
|
|
11854
|
+
await writeReceipt({ buildUploadFileId: fileId });
|
|
11855
|
+
}
|
|
11856
|
+
if (upload.state === "AWAITING_UPLOAD") {
|
|
11857
|
+
const artifactPath = path4.join(input.releaseRoot, metadata.artifact);
|
|
11858
|
+
await client.uploadBuildFile({
|
|
11859
|
+
artifactPath,
|
|
11860
|
+
fileId,
|
|
11861
|
+
signal: input.signal
|
|
11862
|
+
});
|
|
11863
|
+
await client.commitBuildUploadFile({
|
|
11864
|
+
fileId,
|
|
11865
|
+
sha256: metadata.sha256,
|
|
11866
|
+
signal: input.signal
|
|
11867
|
+
});
|
|
11868
|
+
}
|
|
11869
|
+
await writeReceipt({ stage: "processing" });
|
|
11870
|
+
build = await waitForBuild(appleAppId, metadata.buildNumber, upload.id, input.signal);
|
|
11871
|
+
}
|
|
11872
|
+
await writeReceipt({ buildId: build.id });
|
|
11873
|
+
const groups = await client.resolveGroups({
|
|
11874
|
+
appId: appleAppId,
|
|
11875
|
+
groups: intent.groups,
|
|
11876
|
+
signal: input.signal
|
|
11877
|
+
});
|
|
11878
|
+
if (intent.submitForReview && groups.some((group) => group.isInternal))
|
|
11879
|
+
throw new AppStoreConnectReleaseError("TestFlight beta review may only be requested for external groups");
|
|
11880
|
+
for (const note of intent.whatsNew)
|
|
11881
|
+
await client.upsertWhatsNew({
|
|
11882
|
+
buildId: build.id,
|
|
11883
|
+
locale: note.locale,
|
|
11884
|
+
text: note.text,
|
|
11885
|
+
signal: input.signal
|
|
11886
|
+
});
|
|
11887
|
+
for (const group of groups)
|
|
11888
|
+
await client.addBuildToGroup({
|
|
11889
|
+
buildId: build.id,
|
|
11890
|
+
groupId: group.id,
|
|
11891
|
+
signal: input.signal
|
|
11892
|
+
});
|
|
11893
|
+
if (intent.submitForReview && !await client.hasBetaReviewSubmission({
|
|
11894
|
+
buildId: build.id,
|
|
11895
|
+
signal: input.signal
|
|
11896
|
+
}))
|
|
11897
|
+
await client.submitBetaReview({
|
|
11898
|
+
buildId: build.id,
|
|
11899
|
+
signal: input.signal
|
|
11900
|
+
});
|
|
11901
|
+
await writeReceipt({
|
|
11902
|
+
stage: intent.submitForReview ? "review-submitted" : "distributed"
|
|
11903
|
+
});
|
|
11904
|
+
return { ...publication, appStoreConnect: { receipt, reused: false } };
|
|
11905
|
+
}
|
|
11906
|
+
};
|
|
11907
|
+
};
|
|
11908
|
+
|
|
11323
11909
|
// src/edgeIngress.ts
|
|
11324
11910
|
var validPort = (port) => Number.isInteger(port) && port >= 1 && port <= 65535;
|
|
11325
11911
|
|
|
@@ -12019,13 +12605,16 @@ export {
|
|
|
12019
12605
|
createGooglePlayReleasePublisher,
|
|
12020
12606
|
createGooglePlayClient,
|
|
12021
12607
|
createDeployer,
|
|
12608
|
+
createAppStoreConnectReleasePublisher,
|
|
12609
|
+
createAppStoreConnectClient,
|
|
12022
12610
|
bareManager,
|
|
12023
12611
|
ReleaseArtifactError,
|
|
12024
12612
|
NativeReleaseRegistryError,
|
|
12025
12613
|
NATIVE_RELEASE_REGISTRY_FORMAT,
|
|
12026
12614
|
GooglePlayReleaseError,
|
|
12027
|
-
EdgeIngressValidationError
|
|
12615
|
+
EdgeIngressValidationError,
|
|
12616
|
+
AppStoreConnectReleaseError
|
|
12028
12617
|
};
|
|
12029
12618
|
|
|
12030
|
-
//# debugId=
|
|
12619
|
+
//# debugId=95C261E7DE60029364756E2164756E21
|
|
12031
12620
|
//# sourceMappingURL=index.js.map
|