@stacksjs/browser-extension 0.70.211 → 0.70.213
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 +5 -0
- package/dist/app-store-connect.d.ts +27 -65
- package/dist/app-store-connect.js +30 -234
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -132,6 +132,11 @@ what `xcrun safari-web-extension-converter` generates, so day-to-day work
|
|
|
132
132
|
never needs the converter; `--signed` builds need an Apple Development
|
|
133
133
|
identity selected in Xcode.
|
|
134
134
|
|
|
135
|
+
The provider-neutral App Store Connect client, certificate/profile
|
|
136
|
+
provisioning, version reconciliation, and build attachment live in
|
|
137
|
+
`ts-pantry`. Stacks keeps only the Safari-specific Bundle ID mapping, Xcode
|
|
138
|
+
build orchestration, and listing configuration.
|
|
139
|
+
|
|
135
140
|
When `safariAppStore` is configured, publishing also synchronizes the app
|
|
136
141
|
description, category, content-rights declaration, pricing, age rating,
|
|
137
142
|
territory availability, review contact, export compliance, and required
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { AppStoreConnectClient, provisionMacApp } from 'ts-pantry';
|
|
2
|
+
import type { AppStoreConnectClientOptions, AppStoreVersionPlatform, BundleIdPlatform } from 'ts-pantry';
|
|
2
3
|
import type { ExtensionConfig, SafariPlatform } from './types';
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
export type {
|
|
5
|
+
AppAttributes,
|
|
6
|
+
AppStoreBuildAttachmentResult,
|
|
7
|
+
AppStoreBuildResult,
|
|
8
|
+
AppStoreConnectAuth,
|
|
9
|
+
AppStoreConnectClientOptions,
|
|
10
|
+
AppStoreConnectResource,
|
|
11
|
+
AppStoreVersionAttributes,
|
|
12
|
+
AppStoreVersionPlatform,
|
|
13
|
+
BuildAttributes,
|
|
14
|
+
BundleIdAttributes,
|
|
15
|
+
BundleIdCapabilityAttributes,
|
|
16
|
+
BundleIdPlatform,
|
|
17
|
+
CertificateAttributes,
|
|
18
|
+
MacAppProvisionOptions,
|
|
19
|
+
MacAppProvisionResult,
|
|
20
|
+
MacDistributionCertificateType,
|
|
21
|
+
PreReleaseVersionAttributes,
|
|
22
|
+
ProfileAttributes,
|
|
23
|
+
} from 'ts-pantry';
|
|
5
24
|
/** Wait for the uploaded binaries to process, then select them for their App Store versions. */
|
|
6
25
|
export declare function attachSafariBuilds(client: AppStoreConnectClient, appId: string, versions: SafariAppStoreVersionResult[], buildNumber: string, options?: AttachSafariBuildsOptions): Promise<SafariBuildAttachmentResult[]>;
|
|
7
26
|
/**
|
|
@@ -10,51 +29,6 @@ export declare function attachSafariBuilds(client: AppStoreConnectClient, appId:
|
|
|
10
29
|
* app record. Apple does not expose an official API for creating that record.
|
|
11
30
|
*/
|
|
12
31
|
export declare function provisionSafariApp(config: ExtensionConfig, options?: SafariProvisionOptions): Promise<SafariProvisionResult>;
|
|
13
|
-
export declare interface AppStoreConnectResource<T extends Record<string, unknown>> {
|
|
14
|
-
type: string
|
|
15
|
-
id: string
|
|
16
|
-
attributes: T
|
|
17
|
-
relationships?: Record<string, { data?: { type: string, id: string } | null }>
|
|
18
|
-
}
|
|
19
|
-
export declare interface BundleIdAttributes extends Record<string, unknown> {
|
|
20
|
-
identifier: string
|
|
21
|
-
name: string
|
|
22
|
-
platform: BundleIdPlatform
|
|
23
|
-
seedId?: string
|
|
24
|
-
}
|
|
25
|
-
export declare interface AppAttributes extends Record<string, unknown> {
|
|
26
|
-
bundleId: string
|
|
27
|
-
name: string
|
|
28
|
-
primaryLocale: string
|
|
29
|
-
sku: string
|
|
30
|
-
}
|
|
31
|
-
export declare interface AppStoreVersionAttributes extends Record<string, unknown> {
|
|
32
|
-
platform: AppStoreVersionPlatform
|
|
33
|
-
versionString: string
|
|
34
|
-
appStoreState: string
|
|
35
|
-
}
|
|
36
|
-
export declare interface BuildAttributes extends Record<string, unknown> {
|
|
37
|
-
version: string
|
|
38
|
-
uploadedDate: string
|
|
39
|
-
expired: boolean
|
|
40
|
-
processingState: string
|
|
41
|
-
}
|
|
42
|
-
export declare interface PreReleaseVersionAttributes extends Record<string, unknown> {
|
|
43
|
-
version: string
|
|
44
|
-
platform: AppStoreVersionPlatform
|
|
45
|
-
}
|
|
46
|
-
export declare interface AppStoreBuildResult {
|
|
47
|
-
id: string
|
|
48
|
-
buildNumber: string
|
|
49
|
-
version: string
|
|
50
|
-
platform: AppStoreVersionPlatform
|
|
51
|
-
processingState: string
|
|
52
|
-
}
|
|
53
|
-
export declare interface AppStoreConnectClientOptions extends AppStoreConnectAuth {
|
|
54
|
-
baseUrl?: string
|
|
55
|
-
fetch?: typeof globalThis.fetch
|
|
56
|
-
now?: () => number
|
|
57
|
-
}
|
|
58
32
|
export declare interface SafariAppStoreVersionResult {
|
|
59
33
|
platform: SafariPlatform
|
|
60
34
|
version: string
|
|
@@ -87,20 +61,8 @@ export declare interface SafariProvisionResult {
|
|
|
87
61
|
appRecord: { exists: boolean, id?: string }
|
|
88
62
|
appStoreVersions: SafariAppStoreVersionResult[]
|
|
89
63
|
}
|
|
90
|
-
export
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
request<T>(path: string, init?: RequestInit): Promise<T>;
|
|
96
|
-
upload(url: string, init: RequestInit): Promise<void>;
|
|
97
|
-
findBundleId(identifier: string): Promise<AppStoreConnectResource<BundleIdAttributes> | undefined>;
|
|
98
|
-
registerBundleId(identifier: string, name: string, platform?: BundleIdPlatform): Promise<AppStoreConnectResource<BundleIdAttributes>>;
|
|
99
|
-
ensureBundleId(identifier: string, name: string, options?: { checkOnly?: boolean, platform?: BundleIdPlatform }): Promise<{ bundleId?: AppStoreConnectResource<BundleIdAttributes>, created: boolean }>;
|
|
100
|
-
findApp(bundleId: string): Promise<AppStoreConnectResource<AppAttributes> | undefined>;
|
|
101
|
-
listAppStoreVersions(appId: string): Promise<Array<AppStoreConnectResource<AppStoreVersionAttributes>>>;
|
|
102
|
-
createAppStoreVersion(appId: string, platform: AppStoreVersionPlatform, versionString: string): Promise<AppStoreConnectResource<AppStoreVersionAttributes>>;
|
|
103
|
-
updateAppStoreVersion(versionId: string, versionString: string): Promise<AppStoreConnectResource<AppStoreVersionAttributes>>;
|
|
104
|
-
listBuilds(appId: string, buildNumber: string): Promise<AppStoreBuildResult[]>;
|
|
105
|
-
attachBuild(versionId: string, buildId: string): Promise<void>;
|
|
106
|
-
}
|
|
64
|
+
export {
|
|
65
|
+
AppStoreConnectClient,
|
|
66
|
+
provisionMacApp,
|
|
67
|
+
};
|
|
68
|
+
export { appStoreConnectToken } from 'ts-pantry';
|
|
@@ -1,244 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
for (const associated of Object.values(error.meta?.associatedErrors ?? {}))
|
|
8
|
-
for (const nested of associated)
|
|
9
|
-
messages.push(...apiErrorMessages(nested));
|
|
10
|
-
return messages;
|
|
11
|
-
}
|
|
12
|
-
function base64urlJson(value) {
|
|
13
|
-
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
14
|
-
}
|
|
15
|
-
export function appStoreConnectToken(auth, now = Math.floor(Date.now() / 1000)) {
|
|
16
|
-
const header = { alg: "ES256", kid: auth.keyId, typ: "JWT" }, payload = {
|
|
17
|
-
iss: auth.issuerId,
|
|
18
|
-
iat: now,
|
|
19
|
-
exp: now + 120,
|
|
20
|
-
aud: "appstoreconnect-v1"
|
|
21
|
-
}, input = `${base64urlJson(header)}.${base64urlJson(payload)}`;
|
|
22
|
-
let key;
|
|
23
|
-
try {
|
|
24
|
-
key = createPrivateKey(readFileSync(auth.keyPath, "utf8"));
|
|
25
|
-
} catch (error) {
|
|
26
|
-
throw Error(`[browser-extension] App Store Connect API key could not be parsed: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
-
}
|
|
28
|
-
const signature = sign("sha256", Buffer.from(input), { key, dsaEncoding: "ieee-p1363" });
|
|
29
|
-
return `${input}.${signature.toString("base64url")}`;
|
|
30
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
AppStoreConnectClient,
|
|
3
|
+
ensureAppStoreVersions,
|
|
4
|
+
provisionMacApp,
|
|
5
|
+
waitForAppStoreBuilds
|
|
6
|
+
} from "ts-pantry";
|
|
31
7
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
constructor(options = {}) {
|
|
38
|
-
this.auth = resolveAppStoreConnectAuth(options);
|
|
39
|
-
this.baseUrl = (options.baseUrl ?? appStoreConnectBaseUrl).replace(/\/$/, "");
|
|
40
|
-
this.fetcher = options.fetch ?? globalThis.fetch;
|
|
41
|
-
this.now = options.now ?? (() => Math.floor(Date.now() / 1000));
|
|
42
|
-
}
|
|
43
|
-
async request(path, init = {}) {
|
|
44
|
-
const url = /^https?:\/\//.test(path) ? path : /^\/v\d+\//.test(path) ? `${new URL(this.baseUrl).origin}${path}` : `${this.baseUrl}${path}`, response = await this.fetcher(url, {
|
|
45
|
-
...init,
|
|
46
|
-
headers: {
|
|
47
|
-
Accept: "application/json",
|
|
48
|
-
Authorization: `Bearer ${appStoreConnectToken(this.auth, this.now())}`,
|
|
49
|
-
...init.body ? { "Content-Type": "application/json" } : {},
|
|
50
|
-
...init.headers
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
if (!response.ok) {
|
|
54
|
-
const body = await response.json().catch(() => ({})), details = [...new Set(body.errors?.flatMap(apiErrorMessages) ?? [])].join("; ");
|
|
55
|
-
throw Error(`[browser-extension] App Store Connect ${init.method ?? "GET"} ${path} failed (${response.status})${details ? `: ${details}` : ""}`);
|
|
56
|
-
}
|
|
57
|
-
if (response.status === 204)
|
|
58
|
-
return;
|
|
59
|
-
return await response.json();
|
|
60
|
-
}
|
|
61
|
-
async upload(url, init) {
|
|
62
|
-
const response = await this.fetcher(url, init);
|
|
63
|
-
if (!response.ok)
|
|
64
|
-
throw Error(`[browser-extension] App Store Connect asset upload failed (${response.status})`);
|
|
65
|
-
}
|
|
66
|
-
async findBundleId(identifier) {
|
|
67
|
-
const query = new URLSearchParams({ "filter[identifier]": identifier });
|
|
68
|
-
return (await this.request(`/bundleIds?${query}`)).data.find((bundleId) => bundleId.attributes.identifier === identifier);
|
|
69
|
-
}
|
|
70
|
-
async registerBundleId(identifier, name, platform = "MAC_OS") {
|
|
71
|
-
return (await this.request("/bundleIds", {
|
|
72
|
-
method: "POST",
|
|
73
|
-
body: JSON.stringify({
|
|
74
|
-
data: {
|
|
75
|
-
type: "bundleIds",
|
|
76
|
-
attributes: { identifier, name, platform }
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
|
-
})).data;
|
|
80
|
-
}
|
|
81
|
-
async ensureBundleId(identifier, name, options = {}) {
|
|
82
|
-
const existing = await this.findBundleId(identifier);
|
|
83
|
-
if (existing)
|
|
84
|
-
return { bundleId: existing, created: !1 };
|
|
85
|
-
if (options.checkOnly)
|
|
86
|
-
return { created: !1 };
|
|
87
|
-
return {
|
|
88
|
-
bundleId: await this.registerBundleId(identifier, name, options.platform),
|
|
89
|
-
created: !0
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
async findApp(bundleId) {
|
|
93
|
-
const query = new URLSearchParams({ "filter[bundleId]": bundleId });
|
|
94
|
-
return (await this.request(`/apps?${query}`)).data.find((app) => app.attributes.bundleId === bundleId);
|
|
95
|
-
}
|
|
96
|
-
async listAppStoreVersions(appId) {
|
|
97
|
-
return (await this.request(`/apps/${appId}/appStoreVersions?limit=200`)).data;
|
|
98
|
-
}
|
|
99
|
-
async createAppStoreVersion(appId, platform, versionString) {
|
|
100
|
-
return (await this.request("/appStoreVersions", {
|
|
101
|
-
method: "POST",
|
|
102
|
-
body: JSON.stringify({
|
|
103
|
-
data: {
|
|
104
|
-
type: "appStoreVersions",
|
|
105
|
-
attributes: { platform, versionString },
|
|
106
|
-
relationships: { app: { data: { type: "apps", id: appId } } }
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
})).data;
|
|
110
|
-
}
|
|
111
|
-
async updateAppStoreVersion(versionId, versionString) {
|
|
112
|
-
return (await this.request(`/appStoreVersions/${versionId}`, {
|
|
113
|
-
method: "PATCH",
|
|
114
|
-
body: JSON.stringify({
|
|
115
|
-
data: {
|
|
116
|
-
type: "appStoreVersions",
|
|
117
|
-
id: versionId,
|
|
118
|
-
attributes: { versionString }
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
})).data;
|
|
122
|
-
}
|
|
123
|
-
async listBuilds(appId, buildNumber) {
|
|
124
|
-
const query = new URLSearchParams({
|
|
125
|
-
"filter[app]": appId,
|
|
126
|
-
"filter[version]": buildNumber,
|
|
127
|
-
include: "preReleaseVersion",
|
|
128
|
-
limit: "20",
|
|
129
|
-
"fields[builds]": "version,uploadedDate,expired,processingState,preReleaseVersion",
|
|
130
|
-
"fields[preReleaseVersions]": "version,platform"
|
|
131
|
-
}), response = await this.request(`/builds?${query}`), preReleaseVersions = new Map((response.included ?? []).filter((item) => item.type === "preReleaseVersions").map((item) => [item.id, item.attributes]));
|
|
132
|
-
return response.data.flatMap((build) => {
|
|
133
|
-
const preReleaseVersionId = build.relationships?.preReleaseVersion?.data?.id, preReleaseVersion = preReleaseVersionId ? preReleaseVersions.get(preReleaseVersionId) : void 0;
|
|
134
|
-
if (!preReleaseVersion)
|
|
135
|
-
return [];
|
|
136
|
-
return [{
|
|
137
|
-
id: build.id,
|
|
138
|
-
buildNumber: build.attributes.version,
|
|
139
|
-
version: preReleaseVersion.version,
|
|
140
|
-
platform: preReleaseVersion.platform,
|
|
141
|
-
processingState: build.attributes.processingState
|
|
142
|
-
}];
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
async attachBuild(versionId, buildId) {
|
|
146
|
-
await this.request(`/appStoreVersions/${versionId}/relationships/build`, {
|
|
147
|
-
method: "PATCH",
|
|
148
|
-
body: JSON.stringify({ data: { type: "builds", id: buildId } })
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
const editableVersionStates = new Set([
|
|
153
|
-
"PREPARE_FOR_SUBMISSION",
|
|
154
|
-
"DEVELOPER_REJECTED",
|
|
155
|
-
"REJECTED",
|
|
156
|
-
"METADATA_REJECTED",
|
|
157
|
-
"INVALID_BINARY"
|
|
158
|
-
]), releasedVersionStates = new Set([
|
|
159
|
-
"READY_FOR_DISTRIBUTION",
|
|
160
|
-
"READY_FOR_SALE",
|
|
161
|
-
"DEVELOPER_REMOVED_FROM_SALE",
|
|
162
|
-
"REMOVED_FROM_SALE"
|
|
163
|
-
]);
|
|
8
|
+
export {
|
|
9
|
+
AppStoreConnectClient,
|
|
10
|
+
provisionMacApp
|
|
11
|
+
};
|
|
12
|
+
export { appStoreConnectToken } from "ts-pantry";
|
|
164
13
|
function appStorePlatform(platform) {
|
|
165
14
|
return platform === "ios" ? "IOS" : "MAC_OS";
|
|
166
15
|
}
|
|
167
16
|
export async function attachSafariBuilds(client, appId, versions, buildNumber, options = {}) {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
attachments.push({
|
|
180
|
-
platform: version.platform,
|
|
181
|
-
versionId: version.id,
|
|
182
|
-
buildId: build.id,
|
|
183
|
-
buildNumber
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
if (attachments.length === pending.size) {
|
|
187
|
-
for (const attachment of attachments)
|
|
188
|
-
await client.attachBuild(attachment.versionId, attachment.buildId);
|
|
189
|
-
return attachments;
|
|
190
|
-
}
|
|
191
|
-
if (Date.now() >= deadline) {
|
|
192
|
-
const waiting = [...pending.values()].map((item) => item.platform).join(", ");
|
|
193
|
-
throw Error(`[browser-extension] timed out waiting for Safari ${waiting} build ${buildNumber} to finish App Store Connect processing`);
|
|
194
|
-
}
|
|
195
|
-
await Bun.sleep(pollIntervalMs);
|
|
196
|
-
}
|
|
17
|
+
const pantryVersions = versions.map((version) => ({
|
|
18
|
+
platform: appStorePlatform(version.platform),
|
|
19
|
+
version: version.version,
|
|
20
|
+
id: version.id
|
|
21
|
+
}));
|
|
22
|
+
return (await waitForAppStoreBuilds(client, appId, pantryVersions, buildNumber, options)).map((attachment) => ({
|
|
23
|
+
platform: attachment.platform === "IOS" ? "ios" : "macos",
|
|
24
|
+
versionId: attachment.versionId,
|
|
25
|
+
buildId: attachment.buildId,
|
|
26
|
+
buildNumber: attachment.buildNumber
|
|
27
|
+
}));
|
|
197
28
|
}
|
|
198
29
|
async function ensureConfiguredAppStoreVersions(client, appId, platforms, version) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
created: !1,
|
|
208
|
-
updated: !1,
|
|
209
|
-
id: exact.id,
|
|
210
|
-
status,
|
|
211
|
-
appStoreState,
|
|
212
|
-
...status === "deferred" ? { reason: `Safari ${platform} version ${version} is already ${appStoreState}` } : {}
|
|
213
|
-
});
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
const editable = versions.find((item) => item.attributes.platform === applePlatform && editableVersionStates.has(item.attributes.appStoreState));
|
|
217
|
-
if (editable) {
|
|
218
|
-
const updated = await client.updateAppStoreVersion(editable.id, version);
|
|
219
|
-
editable.attributes.versionString = version;
|
|
220
|
-
results.push({ platform, version, created: !1, updated: !0, id: updated.id, status: "ready", appStoreState: updated.attributes.appStoreState });
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
const blocking = versions.find((item) => item.attributes.platform === applePlatform && !releasedVersionStates.has(item.attributes.appStoreState));
|
|
224
|
-
if (blocking) {
|
|
225
|
-
results.push({
|
|
226
|
-
platform,
|
|
227
|
-
version,
|
|
228
|
-
created: !1,
|
|
229
|
-
updated: !1,
|
|
230
|
-
id: blocking.id,
|
|
231
|
-
status: "deferred",
|
|
232
|
-
appStoreState: blocking.attributes.appStoreState,
|
|
233
|
-
reason: `Safari ${platform} version ${version} is queued behind ${blocking.attributes.versionString} (${blocking.attributes.appStoreState})`
|
|
234
|
-
});
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
const created = await client.createAppStoreVersion(appId, applePlatform, version);
|
|
238
|
-
versions.push(created);
|
|
239
|
-
results.push({ platform, version, created: !0, updated: !1, id: created.id, status: "ready", appStoreState: created.attributes.appStoreState });
|
|
240
|
-
}
|
|
241
|
-
return results;
|
|
30
|
+
return (await ensureAppStoreVersions(client, appId, platforms.map((platform) => ({ platform: appStorePlatform(platform), version })))).map((result) => {
|
|
31
|
+
const platform = result.platform === "IOS" ? "ios" : "macos";
|
|
32
|
+
return {
|
|
33
|
+
...result,
|
|
34
|
+
platform,
|
|
35
|
+
...result.reason ? { reason: result.reason.replace(`${result.platform} version`, `Safari ${platform} version`) } : {}
|
|
36
|
+
};
|
|
37
|
+
});
|
|
242
38
|
}
|
|
243
39
|
export async function provisionSafariApp(config, options = {}) {
|
|
244
40
|
if (!config.safariBundleId)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/browser-extension",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.213",
|
|
5
5
|
"description": "Build MV3 browser extensions (Chrome, Firefox, Safari) the Stacks way — manifest, content/background scripts, DNR rules, packaging, Safari container app, all config-driven.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -52,6 +52,9 @@
|
|
|
52
52
|
"dist",
|
|
53
53
|
"safari-template"
|
|
54
54
|
],
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"ts-pantry": "^0.11.0"
|
|
57
|
+
},
|
|
55
58
|
"scripts": {
|
|
56
59
|
"build": "bun build.ts",
|
|
57
60
|
"typecheck": "bun tsc --noEmit",
|