@stacksjs/browser-extension 0.70.146 → 0.70.148
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 +26 -4
- package/dist/app-store-connect.d.ts +3 -0
- package/dist/app-store-connect.js +34 -4
- package/dist/chrome-web-store.d.ts +18 -1
- package/dist/chrome-web-store.js +29 -1
- package/dist/safari.d.ts +2 -0
- package/dist/safari.js +23 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,15 +43,34 @@ email to the publisher account. Firefox reads `AMO_JWT_ISSUER` and
|
|
|
43
43
|
`firefoxAddons.license` and `firefoxAddons.categories` are configured.
|
|
44
44
|
|
|
45
45
|
For tag-driven publication, call Stacks' reusable workflow from the extension
|
|
46
|
-
repository instead of duplicating store orchestration
|
|
46
|
+
repository instead of duplicating store orchestration. Expose the optional
|
|
47
|
+
manual inputs so a failed or missing GitHub Release can be repaired without
|
|
48
|
+
resubmitting builds to the browser stores:
|
|
47
49
|
|
|
48
50
|
```yaml
|
|
51
|
+
on:
|
|
52
|
+
workflow_dispatch:
|
|
53
|
+
inputs:
|
|
54
|
+
github-release-only:
|
|
55
|
+
description: Build packages and create or repair the GitHub Release only
|
|
56
|
+
type: boolean
|
|
57
|
+
default: false
|
|
58
|
+
release-tag:
|
|
59
|
+
description: Existing tag to repair; defaults to v<package.json version>
|
|
60
|
+
type: string
|
|
61
|
+
required: false
|
|
62
|
+
push:
|
|
63
|
+
tags:
|
|
64
|
+
- 'v*'
|
|
65
|
+
|
|
49
66
|
jobs:
|
|
50
67
|
publish:
|
|
51
|
-
uses: stacksjs/stacks/.github/workflows/browser-extension-release.yml@v0.70.
|
|
68
|
+
uses: stacksjs/stacks/.github/workflows/browser-extension-release.yml@v0.70.147
|
|
52
69
|
with:
|
|
53
70
|
chrome-publisher-id: ${{ vars.CHROME_WEB_STORE_PUBLISHER_ID }}
|
|
54
71
|
safari-enabled: ${{ vars.ENABLE_SAFARI_PUBLISH == 'true' }}
|
|
72
|
+
publish-stores: ${{ !inputs.github-release-only }}
|
|
73
|
+
release-tag: ${{ inputs.release-tag || '' }}
|
|
55
74
|
secrets:
|
|
56
75
|
CHROME_WEB_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.CHROME_WEB_STORE_SERVICE_ACCOUNT_JSON }}
|
|
57
76
|
AMO_JWT_ISSUER: ${{ secrets.AMO_JWT_ISSUER }}
|
|
@@ -62,8 +81,11 @@ jobs:
|
|
|
62
81
|
```
|
|
63
82
|
|
|
64
83
|
It packages every configured target, publishes Chrome and Firefox in
|
|
65
|
-
independent jobs, optionally uploads Safari with stable Xcode, and creates
|
|
66
|
-
GitHub Release
|
|
84
|
+
independent jobs, optionally uploads Safari with stable Xcode, and creates an
|
|
85
|
+
idempotent GitHub Release with checksummed packages as soon as validation
|
|
86
|
+
succeeds. Store failures cannot suppress the GitHub Release. A manual run with
|
|
87
|
+
`github-release-only` skips every store and creates or repairs the release for
|
|
88
|
+
the requested existing tag.
|
|
67
89
|
|
|
68
90
|
## Safari
|
|
69
91
|
|
|
@@ -61,6 +61,9 @@ export declare interface SafariAppStoreVersionResult {
|
|
|
61
61
|
created: boolean
|
|
62
62
|
updated: boolean
|
|
63
63
|
id: string
|
|
64
|
+
status?: 'ready' | 'deferred' | 'published'
|
|
65
|
+
appStoreState?: string
|
|
66
|
+
reason?: string
|
|
64
67
|
}
|
|
65
68
|
export declare interface SafariBuildAttachmentResult {
|
|
66
69
|
platform: SafariPlatform
|
|
@@ -153,7 +153,13 @@ const editableVersionStates = new Set([
|
|
|
153
153
|
"PREPARE_FOR_SUBMISSION",
|
|
154
154
|
"DEVELOPER_REJECTED",
|
|
155
155
|
"REJECTED",
|
|
156
|
-
"METADATA_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"
|
|
157
163
|
]);
|
|
158
164
|
function appStorePlatform(platform) {
|
|
159
165
|
return platform === "ios" ? "IOS" : "MAC_OS";
|
|
@@ -194,19 +200,43 @@ async function ensureConfiguredAppStoreVersions(client, appId, platforms, versio
|
|
|
194
200
|
for (const platform of [...new Set(platforms)]) {
|
|
195
201
|
const applePlatform = appStorePlatform(platform), exact = versions.find((item) => item.attributes.platform === applePlatform && item.attributes.versionString === version);
|
|
196
202
|
if (exact) {
|
|
197
|
-
|
|
203
|
+
const appStoreState = exact.attributes.appStoreState, status = editableVersionStates.has(appStoreState) ? "ready" : releasedVersionStates.has(appStoreState) ? "published" : "deferred";
|
|
204
|
+
results.push({
|
|
205
|
+
platform,
|
|
206
|
+
version,
|
|
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
|
+
});
|
|
198
214
|
continue;
|
|
199
215
|
}
|
|
200
216
|
const editable = versions.find((item) => item.attributes.platform === applePlatform && editableVersionStates.has(item.attributes.appStoreState));
|
|
201
217
|
if (editable) {
|
|
202
218
|
const updated = await client.updateAppStoreVersion(editable.id, version);
|
|
203
219
|
editable.attributes.versionString = version;
|
|
204
|
-
results.push({ platform, version, created: !1, updated: !0, id: updated.id });
|
|
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
|
+
});
|
|
205
235
|
continue;
|
|
206
236
|
}
|
|
207
237
|
const created = await client.createAppStoreVersion(appId, applePlatform, version);
|
|
208
238
|
versions.push(created);
|
|
209
|
-
results.push({ platform, version, created: !0, updated: !1, id: created.id });
|
|
239
|
+
results.push({ platform, version, created: !0, updated: !1, id: created.id, status: "ready", appStoreState: created.attributes.appStoreState });
|
|
210
240
|
}
|
|
211
241
|
return results;
|
|
212
242
|
}
|
|
@@ -2,7 +2,7 @@ import type { ChromeWebStoreConfig, ExtensionConfig } from './types';
|
|
|
2
2
|
/** Build the RS256 assertion Google exchanges for a short-lived OAuth token. */
|
|
3
3
|
export declare function chromeWebStoreServiceAccountAssertion(serviceAccount: GoogleServiceAccount, now?: unknown): string;
|
|
4
4
|
/** Build, package, upload, and optionally submit an existing Chrome Web Store item. */
|
|
5
|
-
export declare function publishChromeExtension(config: ExtensionConfig, options: ChromeWebStorePublishOptions): Promise<
|
|
5
|
+
export declare function publishChromeExtension(config: ExtensionConfig, options: ChromeWebStorePublishOptions): Promise<ChromeWebStorePublishResult>;
|
|
6
6
|
export declare interface GoogleServiceAccount {
|
|
7
7
|
client_email: string
|
|
8
8
|
private_key: string
|
|
@@ -39,6 +39,23 @@ export declare interface ChromePublishResult {
|
|
|
39
39
|
state: string
|
|
40
40
|
warningInfo?: { warnings?: Array<{ reason: string, description: string }> }
|
|
41
41
|
}
|
|
42
|
+
export declare interface ChromeDeferredPublishResult {
|
|
43
|
+
state: string
|
|
44
|
+
submittedVersion?: string
|
|
45
|
+
reason: string
|
|
46
|
+
}
|
|
47
|
+
export declare interface ChromePublishedVersionResult {
|
|
48
|
+
state: string
|
|
49
|
+
version: string
|
|
50
|
+
reason: string
|
|
51
|
+
}
|
|
52
|
+
export declare interface ChromeWebStorePublishResult {
|
|
53
|
+
packagePath: string
|
|
54
|
+
upload?: ChromeUploadResult
|
|
55
|
+
publish?: ChromePublishResult
|
|
56
|
+
deferred?: ChromeDeferredPublishResult
|
|
57
|
+
alreadyPublished?: ChromePublishedVersionResult
|
|
58
|
+
}
|
|
42
59
|
export declare interface ChromeWebStorePublishOptions extends ChromeWebStoreClientOptions {
|
|
43
60
|
version: string
|
|
44
61
|
cwd?: string
|
package/dist/chrome-web-store.js
CHANGED
|
@@ -121,10 +121,38 @@ export class ChromeWebStoreClient {
|
|
|
121
121
|
throw Error("[browser-extension] Chrome Web Store package processing timed out");
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
function pendingReview(status) {
|
|
125
|
+
if (status.submittedItemRevisionStatus?.state !== "PENDING_REVIEW")
|
|
126
|
+
return;
|
|
127
|
+
const submittedVersion = status.submittedItemRevisionStatus.distributionChannels?.[0]?.crxVersion;
|
|
128
|
+
return {
|
|
129
|
+
state: status.submittedItemRevisionStatus.state,
|
|
130
|
+
submittedVersion,
|
|
131
|
+
reason: submittedVersion ? `Chrome Web Store version ${submittedVersion} is already pending review` : "A Chrome Web Store revision is already pending review"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function publishedVersion(status, version) {
|
|
135
|
+
const published = status.publishedItemRevisionStatus;
|
|
136
|
+
if (!published || !["PUBLISHED", "PUBLISHED_TO_TESTERS"].includes(published.state))
|
|
137
|
+
return;
|
|
138
|
+
if (!published.distributionChannels?.some((channel) => channel.crxVersion === version))
|
|
139
|
+
return;
|
|
140
|
+
return {
|
|
141
|
+
state: published.state,
|
|
142
|
+
version,
|
|
143
|
+
reason: `Chrome Web Store version ${version} is already ${published.state.toLowerCase().replaceAll("_", " ")}`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
124
146
|
export async function publishChromeExtension(config, options) {
|
|
125
147
|
if (!config.chromeWebStore)
|
|
126
148
|
throw Error("[browser-extension] Chrome publishing needs chromeWebStore.publisherId and chromeWebStore.itemId in config/extension.ts");
|
|
127
|
-
const cwd = options.cwd ?? process.cwd(), packagePath = options.packagePath ?? await packageExtension(config, { target: "chrome", version: options.version, cwd }), client = new ChromeWebStoreClient(options),
|
|
149
|
+
const cwd = options.cwd ?? process.cwd(), packagePath = options.packagePath ?? await packageExtension(config, { target: "chrome", version: options.version, cwd }), client = new ChromeWebStoreClient(options), status = await client.fetchStatus(config.chromeWebStore), alreadyPublished = publishedVersion(status, options.version);
|
|
150
|
+
if (alreadyPublished)
|
|
151
|
+
return { packagePath, alreadyPublished };
|
|
152
|
+
const deferred = pendingReview(status);
|
|
153
|
+
if (deferred)
|
|
154
|
+
return { packagePath, deferred };
|
|
155
|
+
const upload = await client.upload(config.chromeWebStore, packagePath);
|
|
128
156
|
if (upload.uploadState === "IN_PROGRESS")
|
|
129
157
|
await client.waitForUpload(config.chromeWebStore);
|
|
130
158
|
else if (upload.uploadState !== "SUCCEEDED")
|
package/dist/safari.d.ts
CHANGED
|
@@ -120,6 +120,8 @@ export declare interface SafariPublishResult {
|
|
|
120
120
|
exportPath: string
|
|
121
121
|
buildNumber: string
|
|
122
122
|
artifacts: SafariPublishedArtifact[]
|
|
123
|
+
deferred: Array<{ platform: SafariPlatform, version: string, state?: string, reason: string }>
|
|
124
|
+
alreadyPublished: Array<{ platform: SafariPlatform, version: string, state?: string }>
|
|
123
125
|
attachments: Array<{ platform: SafariPlatform, versionId: string, buildId: string, buildNumber: string }>
|
|
124
126
|
appStoreSubmission?: {
|
|
125
127
|
versions: Array<{ platform: SafariPlatform, versionId: string, localizationId: string }>
|
package/dist/safari.js
CHANGED
|
@@ -329,20 +329,36 @@ export async function publishSafariApp(config, options) {
|
|
|
329
329
|
throw Error("[browser-extension] Safari publishing needs at least one platform");
|
|
330
330
|
const { AppStoreConnectClient, attachSafariBuilds, provisionSafariApp } = await import("./app-store-connect"), provisioned = await provisionSafariApp(config, {
|
|
331
331
|
...auth,
|
|
332
|
-
version: options.version,
|
|
332
|
+
version: options.validateOnly ? void 0 : options.version,
|
|
333
333
|
platforms
|
|
334
334
|
});
|
|
335
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
|
+
const publishablePlatforms = options.validateOnly ? platforms : platforms.filter((platform) => provisioned.appStoreVersions.some((version) => version.platform === platform && version.status !== "deferred" && version.status !== "published")), deferred = provisioned.appStoreVersions.filter((version) => version.status === "deferred").map((version) => ({
|
|
338
|
+
platform: version.platform,
|
|
339
|
+
version: version.version,
|
|
340
|
+
state: version.appStoreState,
|
|
341
|
+
reason: version.reason ?? `Safari ${version.platform} publication is deferred`
|
|
342
|
+
})), alreadyPublished = provisioned.appStoreVersions.filter((version) => version.status === "published").map((version) => ({ platform: version.platform, version: version.version, state: version.appStoreState }));
|
|
343
|
+
if (!publishablePlatforms.length)
|
|
344
|
+
return {
|
|
345
|
+
archivePath: "",
|
|
346
|
+
exportPath: "",
|
|
347
|
+
buildNumber,
|
|
348
|
+
artifacts: [],
|
|
349
|
+
attachments: [],
|
|
350
|
+
deferred,
|
|
351
|
+
alreadyPublished
|
|
352
|
+
};
|
|
337
353
|
if (options.build !== !1)
|
|
338
354
|
await buildExtension(config, { target: "safari", version: options.version, cwd });
|
|
339
|
-
const appName = safariAppName(config), dir = safariProjectDir(cwd, options.dir), buildDir = join(dir, "build"), universal =
|
|
355
|
+
const appName = safariAppName(config), dir = safariProjectDir(cwd, options.dir), buildDir = join(dir, "build"), universal = publishablePlatforms.includes("ios");
|
|
340
356
|
let project, projectCwd;
|
|
341
357
|
if (universal) {
|
|
342
358
|
const generated = await createSafariUniversalProject(config, {
|
|
343
359
|
...options,
|
|
344
360
|
build: !1,
|
|
345
|
-
platforms
|
|
361
|
+
platforms: publishablePlatforms
|
|
346
362
|
});
|
|
347
363
|
project = generated.project;
|
|
348
364
|
projectCwd = generated.dir;
|
|
@@ -354,7 +370,7 @@ export async function publishSafariApp(config, options) {
|
|
|
354
370
|
const authArgs = xcodeAuthArgs(auth);
|
|
355
371
|
await mkdir(buildDir, { recursive: !0 });
|
|
356
372
|
const method = options.validateOnly ? "validation" : "app-store-connect", artifacts = [];
|
|
357
|
-
for (const platform of
|
|
373
|
+
for (const platform of publishablePlatforms) {
|
|
358
374
|
const suffix = universal ? `-${platform}` : "", archivePath = join(buildDir, `${appName}${suffix}.xcarchive`), exportPath = join(buildDir, `${options.validateOnly ? "validation" : "upload"}${suffix}`), plistPath = join(buildDir, `ExportOptions.${method}${suffix}.plist`), scheme = universal ? `${appName} (${platform === "ios" ? "iOS" : "macOS"})` : appName, destination = platform === "ios" ? "generic/platform=iOS" : "generic/platform=macOS";
|
|
359
375
|
await rm(archivePath, { recursive: !0, force: !0 });
|
|
360
376
|
await rm(exportPath, { recursive: !0, force: !0 });
|
|
@@ -391,7 +407,7 @@ export async function publishSafariApp(config, options) {
|
|
|
391
407
|
], projectCwd, `Safari ${platform} export`);
|
|
392
408
|
artifacts.push({ platform, archivePath, exportPath });
|
|
393
409
|
}
|
|
394
|
-
const attachments = options.validateOnly ? [] : await attachSafariBuilds(new AppStoreConnectClient(auth), provisioned.appRecord.id, provisioned.appStoreVersions, buildNumber, {
|
|
410
|
+
const attachments = options.validateOnly ? [] : await attachSafariBuilds(new AppStoreConnectClient(auth), provisioned.appRecord.id, provisioned.appStoreVersions.filter((version) => version.status !== "deferred" && version.status !== "published"), buildNumber, {
|
|
395
411
|
timeoutMs: options.processingTimeoutMs,
|
|
396
412
|
pollIntervalMs: options.processingPollIntervalMs
|
|
397
413
|
}), appStoreSubmission = !options.validateOnly && config.safariAppStore ? await (await import("./app-store-submission")).prepareSafariAppStoreSubmission(new AppStoreConnectClient(auth), config, provisioned.appRecord.id, attachments.map((attachment) => ({
|
|
@@ -404,6 +420,8 @@ export async function publishSafariApp(config, options) {
|
|
|
404
420
|
exportPath: firstArtifact.exportPath,
|
|
405
421
|
buildNumber,
|
|
406
422
|
artifacts,
|
|
423
|
+
deferred,
|
|
424
|
+
alreadyPublished,
|
|
407
425
|
attachments,
|
|
408
426
|
appStoreSubmission: appStoreSubmission ? { versions: appStoreSubmission.versions, reviewSubmissionIds: appStoreSubmission.reviewSubmissionIds } : void 0
|
|
409
427
|
};
|
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.148",
|
|
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": [
|