@stacksjs/browser-extension 0.70.147 → 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.
@@ -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
- results.push({ platform, version, created: !1, updated: !1, id: exact.id });
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<{ packagePath: string, upload: ChromeUploadResult, publish?: ChromePublishResult }>;
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
@@ -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), upload = await client.upload(config.chromeWebStore, packagePath);
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 = platforms.includes("ios");
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 platforms) {
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.147",
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": [