@stacksjs/browser-extension 0.70.143 → 0.70.145
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 +3 -2
- package/dist/app-store-connect.d.ts +32 -0
- package/dist/app-store-connect.js +61 -0
- package/dist/firefox-addons.d.ts +9 -0
- package/dist/firefox-addons.js +22 -13
- package/dist/safari.d.ts +3 -0
- package/dist/safari.js +11 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,8 +85,9 @@ Set `safariBundleId` in the config (the appex gets `<safariBundleId>.Extension`)
|
|
|
85
85
|
and `safariTeamId` to the Apple Developer team used for signing. Set
|
|
86
86
|
`safariPlatforms: ['macos', 'ios']` for a universal extension; iOS covers both
|
|
87
87
|
iPhone and iPad. Stacks generates the current Apple-supported universal Xcode
|
|
88
|
-
project from the same built extension, archives both platforms,
|
|
89
|
-
|
|
88
|
+
project from the same built extension, archives both platforms, uploads them
|
|
89
|
+
to the same App Store Connect app record, waits for Apple processing, and
|
|
90
|
+
selects each processed build on its matching App Store version. Publishing
|
|
90
91
|
reads `APP_STORE_CONNECT_API_KEY_ID`, `APP_STORE_CONNECT_API_ISSUER_ID`, and
|
|
91
92
|
`APP_STORE_CONNECT_API_KEY_PATH` from the environment. Run with
|
|
92
93
|
`--validate-only` to exercise Apple's validation without uploading a build.
|
|
@@ -2,6 +2,8 @@ import type { AppStoreConnectAuth } from './safari';
|
|
|
2
2
|
import type { ExtensionConfig, SafariPlatform } from './types';
|
|
3
3
|
/** Generate the short-lived ES256 team token required by App Store Connect. */
|
|
4
4
|
export declare function appStoreConnectToken(auth: Required<AppStoreConnectAuth>, now?: unknown): string;
|
|
5
|
+
/** Wait for the uploaded binaries to process, then select them for their App Store versions. */
|
|
6
|
+
export declare function attachSafariBuilds(client: AppStoreConnectClient, appId: string, versions: SafariAppStoreVersionResult[], buildNumber: string, options?: AttachSafariBuildsOptions): Promise<SafariBuildAttachmentResult[]>;
|
|
5
7
|
/**
|
|
6
8
|
* Register the explicit container and extension Bundle IDs required by a
|
|
7
9
|
* Safari Web Extension, then check for the manually-created App Store Connect
|
|
@@ -12,6 +14,7 @@ export declare interface AppStoreConnectResource<T extends Record<string, unknow
|
|
|
12
14
|
type: string
|
|
13
15
|
id: string
|
|
14
16
|
attributes: T
|
|
17
|
+
relationships?: Record<string, { data?: { type: string, id: string } | null }>
|
|
15
18
|
}
|
|
16
19
|
export declare interface BundleIdAttributes extends Record<string, unknown> {
|
|
17
20
|
identifier: string
|
|
@@ -30,6 +33,23 @@ export declare interface AppStoreVersionAttributes extends Record<string, unknow
|
|
|
30
33
|
versionString: string
|
|
31
34
|
appStoreState: string
|
|
32
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
|
+
}
|
|
33
53
|
export declare interface AppStoreConnectClientOptions extends AppStoreConnectAuth {
|
|
34
54
|
baseUrl?: string
|
|
35
55
|
fetch?: typeof globalThis.fetch
|
|
@@ -42,6 +62,16 @@ export declare interface SafariAppStoreVersionResult {
|
|
|
42
62
|
updated: boolean
|
|
43
63
|
id: string
|
|
44
64
|
}
|
|
65
|
+
export declare interface SafariBuildAttachmentResult {
|
|
66
|
+
platform: SafariPlatform
|
|
67
|
+
versionId: string
|
|
68
|
+
buildId: string
|
|
69
|
+
buildNumber: string
|
|
70
|
+
}
|
|
71
|
+
export declare interface AttachSafariBuildsOptions {
|
|
72
|
+
timeoutMs?: number
|
|
73
|
+
pollIntervalMs?: number
|
|
74
|
+
}
|
|
45
75
|
export declare interface SafariProvisionOptions extends AppStoreConnectClientOptions {
|
|
46
76
|
checkOnly?: boolean
|
|
47
77
|
platform?: BundleIdPlatform
|
|
@@ -66,4 +96,6 @@ export declare class AppStoreConnectClient {
|
|
|
66
96
|
listAppStoreVersions(appId: string): Promise<Array<AppStoreConnectResource<AppStoreVersionAttributes>>>;
|
|
67
97
|
createAppStoreVersion(appId: string, platform: AppStoreVersionPlatform, versionString: string): Promise<AppStoreConnectResource<AppStoreVersionAttributes>>;
|
|
68
98
|
updateAppStoreVersion(versionId: string, versionString: string): Promise<AppStoreConnectResource<AppStoreVersionAttributes>>;
|
|
99
|
+
listBuilds(appId: string, buildNumber: string): Promise<AppStoreBuildResult[]>;
|
|
100
|
+
attachBuild(versionId: string, buildId: string): Promise<void>;
|
|
69
101
|
}
|
|
@@ -47,6 +47,8 @@ export class AppStoreConnectClient {
|
|
|
47
47
|
const details = (await response.json().catch(() => ({}))).errors?.map((error) => error.detail ?? error.title ?? error.code).filter(Boolean).join("; ");
|
|
48
48
|
throw Error(`[browser-extension] App Store Connect ${init.method ?? "GET"} ${path} failed (${response.status})${details ? `: ${details}` : ""}`);
|
|
49
49
|
}
|
|
50
|
+
if (response.status === 204)
|
|
51
|
+
return;
|
|
50
52
|
return await response.json();
|
|
51
53
|
}
|
|
52
54
|
async findBundleId(identifier) {
|
|
@@ -106,6 +108,34 @@ export class AppStoreConnectClient {
|
|
|
106
108
|
})
|
|
107
109
|
})).data;
|
|
108
110
|
}
|
|
111
|
+
async listBuilds(appId, buildNumber) {
|
|
112
|
+
const query = new URLSearchParams({
|
|
113
|
+
"filter[app]": appId,
|
|
114
|
+
"filter[version]": buildNumber,
|
|
115
|
+
include: "preReleaseVersion",
|
|
116
|
+
limit: "20",
|
|
117
|
+
"fields[builds]": "version,uploadedDate,expired,processingState,preReleaseVersion",
|
|
118
|
+
"fields[preReleaseVersions]": "version,platform"
|
|
119
|
+
}), response = await this.request(`/builds?${query}`), preReleaseVersions = new Map((response.included ?? []).filter((item) => item.type === "preReleaseVersions").map((item) => [item.id, item.attributes]));
|
|
120
|
+
return response.data.flatMap((build) => {
|
|
121
|
+
const preReleaseVersionId = build.relationships?.preReleaseVersion?.data?.id, preReleaseVersion = preReleaseVersionId ? preReleaseVersions.get(preReleaseVersionId) : void 0;
|
|
122
|
+
if (!preReleaseVersion)
|
|
123
|
+
return [];
|
|
124
|
+
return [{
|
|
125
|
+
id: build.id,
|
|
126
|
+
buildNumber: build.attributes.version,
|
|
127
|
+
version: preReleaseVersion.version,
|
|
128
|
+
platform: preReleaseVersion.platform,
|
|
129
|
+
processingState: build.attributes.processingState
|
|
130
|
+
}];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async attachBuild(versionId, buildId) {
|
|
134
|
+
await this.request(`/appStoreVersions/${versionId}/relationships/build`, {
|
|
135
|
+
method: "PATCH",
|
|
136
|
+
body: JSON.stringify({ data: { type: "builds", id: buildId } })
|
|
137
|
+
});
|
|
138
|
+
}
|
|
109
139
|
}
|
|
110
140
|
const editableVersionStates = new Set([
|
|
111
141
|
"PREPARE_FOR_SUBMISSION",
|
|
@@ -116,6 +146,37 @@ const editableVersionStates = new Set([
|
|
|
116
146
|
function appStorePlatform(platform) {
|
|
117
147
|
return platform === "ios" ? "IOS" : "MAC_OS";
|
|
118
148
|
}
|
|
149
|
+
export async function attachSafariBuilds(client, appId, versions, buildNumber, options = {}) {
|
|
150
|
+
const timeoutMs = options.timeoutMs ?? 1200000, pollIntervalMs = options.pollIntervalMs ?? 15000, deadline = Date.now() + timeoutMs, pending = new Map(versions.map((version) => [appStorePlatform(version.platform), version]));
|
|
151
|
+
while (!0) {
|
|
152
|
+
const builds = await client.listBuilds(appId, buildNumber), attachments = [];
|
|
153
|
+
for (const [platform, version] of pending) {
|
|
154
|
+
const build = builds.find((item) => item.platform === platform && item.version === version.version);
|
|
155
|
+
if (!build)
|
|
156
|
+
continue;
|
|
157
|
+
if (build.processingState === "FAILED" || build.processingState === "INVALID")
|
|
158
|
+
throw Error(`[browser-extension] Safari ${version.platform} build ${buildNumber} failed App Store Connect processing (${build.processingState})`);
|
|
159
|
+
if (build.processingState !== "VALID")
|
|
160
|
+
continue;
|
|
161
|
+
attachments.push({
|
|
162
|
+
platform: version.platform,
|
|
163
|
+
versionId: version.id,
|
|
164
|
+
buildId: build.id,
|
|
165
|
+
buildNumber
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (attachments.length === pending.size) {
|
|
169
|
+
for (const attachment of attachments)
|
|
170
|
+
await client.attachBuild(attachment.versionId, attachment.buildId);
|
|
171
|
+
return attachments;
|
|
172
|
+
}
|
|
173
|
+
if (Date.now() >= deadline) {
|
|
174
|
+
const waiting = [...pending.values()].map((item) => item.platform).join(", ");
|
|
175
|
+
throw Error(`[browser-extension] timed out waiting for Safari ${waiting} build ${buildNumber} to finish App Store Connect processing`);
|
|
176
|
+
}
|
|
177
|
+
await Bun.sleep(pollIntervalMs);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
119
180
|
async function ensureConfiguredAppStoreVersions(client, appId, platforms, version) {
|
|
120
181
|
const versions = await client.listAppStoreVersions(appId), results = [];
|
|
121
182
|
for (const platform of [...new Set(platforms)]) {
|
package/dist/firefox-addons.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionConfig, FirefoxAddonsConfig } from './types';
|
|
2
|
+
export declare function firefoxSignArgs(options: FirefoxSignArgsOptions): string[];
|
|
2
3
|
/** Metadata accepted by AMO v5 when web-ext creates the first listed version. */
|
|
3
4
|
export declare function firefoxListingMetadata(config: ExtensionConfig, store: FirefoxAddonsConfig): Record<string, unknown> | undefined;
|
|
4
5
|
/** Resolve web-ext from PATH or from this package's declared dependency. */
|
|
@@ -22,3 +23,11 @@ export declare interface FirefoxPublishResult {
|
|
|
22
23
|
artifacts: string[]
|
|
23
24
|
channel: 'listed' | 'unlisted'
|
|
24
25
|
}
|
|
26
|
+
declare interface FirefoxSignArgsOptions {
|
|
27
|
+
executable: string
|
|
28
|
+
sourceDir: string
|
|
29
|
+
artifactsDir: string
|
|
30
|
+
channel: 'listed' | 'unlisted'
|
|
31
|
+
timeout: number
|
|
32
|
+
approvalTimeout: number
|
|
33
|
+
}
|
package/dist/firefox-addons.js
CHANGED
|
@@ -4,6 +4,23 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { buildExtension, resolveOutdir } from "./build";
|
|
7
|
+
export function firefoxSignArgs(options) {
|
|
8
|
+
return [
|
|
9
|
+
options.executable,
|
|
10
|
+
"sign",
|
|
11
|
+
"--source-dir",
|
|
12
|
+
options.sourceDir,
|
|
13
|
+
"--artifacts-dir",
|
|
14
|
+
options.artifactsDir,
|
|
15
|
+
"--channel",
|
|
16
|
+
options.channel,
|
|
17
|
+
"--timeout",
|
|
18
|
+
String(options.timeout),
|
|
19
|
+
"--approval-timeout",
|
|
20
|
+
String(options.approvalTimeout),
|
|
21
|
+
"--no-input"
|
|
22
|
+
];
|
|
23
|
+
}
|
|
7
24
|
function resolveFirefoxAuth(options) {
|
|
8
25
|
const issuer = options.issuer ?? process.env.AMO_JWT_ISSUER ?? process.env.WEB_EXT_API_KEY, secret = options.secret ?? process.env.AMO_JWT_SECRET ?? process.env.WEB_EXT_API_SECRET, missing = [!issuer && "AMO_JWT_ISSUER", !secret && "AMO_JWT_SECRET"].filter(Boolean);
|
|
9
26
|
if (missing.length)
|
|
@@ -50,22 +67,14 @@ export async function publishFirefoxExtension(config, options) {
|
|
|
50
67
|
await mkdir(artifactsDir, { recursive: !0 });
|
|
51
68
|
const before = new Set(existsSync(artifactsDir) ? readdirSync(artifactsDir) : []), tempDir = await mkdtemp(join(tmpdir(), "stacks-firefox-publish-"));
|
|
52
69
|
try {
|
|
53
|
-
const args =
|
|
70
|
+
const args = firefoxSignArgs({
|
|
54
71
|
executable,
|
|
55
|
-
"sign",
|
|
56
|
-
"--source-dir",
|
|
57
72
|
sourceDir,
|
|
58
|
-
"--artifacts-dir",
|
|
59
73
|
artifactsDir,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"--approval-timeout",
|
|
65
|
-
String(options.approvalTimeout ?? 0),
|
|
66
|
-
"--no-input",
|
|
67
|
-
"--boring"
|
|
68
|
-
], metadata = firefoxListingMetadata(config, store);
|
|
74
|
+
channel: store.channel ?? "listed",
|
|
75
|
+
timeout: options.timeout ?? 300000,
|
|
76
|
+
approvalTimeout: options.approvalTimeout ?? 0
|
|
77
|
+
}), metadata = firefoxListingMetadata(config, store);
|
|
69
78
|
if (metadata) {
|
|
70
79
|
const metadataPath = join(tempDir, "amo-metadata.json");
|
|
71
80
|
await Bun.write(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
package/dist/safari.d.ts
CHANGED
|
@@ -107,6 +107,8 @@ export declare interface SafariPublishOptions extends SafariSyncOptions, AppStor
|
|
|
107
107
|
validateOnly?: boolean
|
|
108
108
|
build?: boolean
|
|
109
109
|
platforms?: SafariPlatform[]
|
|
110
|
+
processingTimeoutMs?: number
|
|
111
|
+
processingPollIntervalMs?: number
|
|
110
112
|
}
|
|
111
113
|
export declare interface SafariPublishedArtifact {
|
|
112
114
|
platform: SafariPlatform
|
|
@@ -118,4 +120,5 @@ export declare interface SafariPublishResult {
|
|
|
118
120
|
exportPath: string
|
|
119
121
|
buildNumber: string
|
|
120
122
|
artifacts: SafariPublishedArtifact[]
|
|
123
|
+
attachments: Array<{ platform: SafariPlatform, versionId: string, buildId: string, buildNumber: string }>
|
|
121
124
|
}
|
package/dist/safari.js
CHANGED
|
@@ -327,12 +327,12 @@ export async function publishSafariApp(config, options) {
|
|
|
327
327
|
const platforms = resolveSafariPlatforms(config, options.platforms);
|
|
328
328
|
if (!platforms.length)
|
|
329
329
|
throw Error("[browser-extension] Safari publishing needs at least one platform");
|
|
330
|
-
const { provisionSafariApp } = await import("./app-store-connect")
|
|
331
|
-
if (!(await provisionSafariApp(config, {
|
|
330
|
+
const { AppStoreConnectClient, attachSafariBuilds, provisionSafariApp } = await import("./app-store-connect"), provisioned = await provisionSafariApp(config, {
|
|
332
331
|
...auth,
|
|
333
332
|
version: options.version,
|
|
334
333
|
platforms
|
|
335
|
-
})
|
|
334
|
+
});
|
|
335
|
+
if (!provisioned.appRecord.exists)
|
|
336
336
|
throw Error("[browser-extension] the App Store Connect app record is missing; create it once in App Store Connect before publishing");
|
|
337
337
|
if (options.build !== !1)
|
|
338
338
|
await buildExtension(config, { target: "safari", version: options.version, cwd });
|
|
@@ -390,10 +390,15 @@ export async function publishSafariApp(config, options) {
|
|
|
390
390
|
], projectCwd, `Safari ${platform} export`);
|
|
391
391
|
artifacts.push({ platform, archivePath, exportPath });
|
|
392
392
|
}
|
|
393
|
+
const attachments = options.validateOnly ? [] : await attachSafariBuilds(new AppStoreConnectClient(auth), provisioned.appRecord.id, provisioned.appStoreVersions, buildNumber, {
|
|
394
|
+
timeoutMs: options.processingTimeoutMs,
|
|
395
|
+
pollIntervalMs: options.processingPollIntervalMs
|
|
396
|
+
}), firstArtifact = artifacts[0];
|
|
393
397
|
return {
|
|
394
|
-
archivePath:
|
|
395
|
-
exportPath:
|
|
398
|
+
archivePath: firstArtifact.archivePath,
|
|
399
|
+
exportPath: firstArtifact.exportPath,
|
|
396
400
|
buildNumber,
|
|
397
|
-
artifacts
|
|
401
|
+
artifacts,
|
|
402
|
+
attachments
|
|
398
403
|
};
|
|
399
404
|
}
|
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.145",
|
|
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": [
|