eoas 3.0.0-beta.6 → 3.0.0-beta.8

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.
@@ -13,6 +13,7 @@ export default class Publish extends Command {
13
13
  packageRunner: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
14
  message: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
15
  dumpSourcemap: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
+ 'rollout-percentage': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
16
17
  };
17
18
  private sanitizeFlags;
18
19
  run(): Promise<void>;
@@ -69,6 +69,11 @@ class Publish extends core_1.Command {
69
69
  description: 'Emit Hermes source maps alongside the bundle so the published artifact can be symbolicated by tools like Sentry or PostHog.',
70
70
  default: false,
71
71
  }),
72
+ 'rollout-percentage': core_1.Flags.integer({
73
+ min: 1,
74
+ max: 99,
75
+ description: 'Publish this update as a progressive rollout served to N% of devices (1-99). The remaining devices keep receiving the previous update of each branch/runtime version. With --platform all, the rollout applies independently to each platform. Progression (increase, end, revert) is managed from the dashboard.',
76
+ }),
72
77
  };
73
78
  sanitizeFlags(flags) {
74
79
  return {
@@ -81,6 +86,7 @@ class Publish extends core_1.Command {
81
86
  providedDeprecatedChannel: flags.channel,
82
87
  message: flags.message,
83
88
  dumpSourcemap: flags.dumpSourcemap,
89
+ rolloutPercentage: flags['rollout-percentage'],
84
90
  };
85
91
  }
86
92
  async run() {
@@ -90,7 +96,7 @@ class Publish extends core_1.Command {
90
96
  process.exit(1);
91
97
  }
92
98
  const { flags } = await this.parse(Publish);
93
- const { platform, nonInteractive, branch, outputDir, packageRunner, providedDeprecatedChannel, disableRepositoryCheck, message, dumpSourcemap, } = this.sanitizeFlags(flags);
99
+ const { platform, nonInteractive, branch, outputDir, packageRunner, providedDeprecatedChannel, disableRepositoryCheck, message, dumpSourcemap, rolloutPercentage, } = this.sanitizeFlags(flags);
94
100
  if (!branch) {
95
101
  log_1.default.error('Branch name is required');
96
102
  process.exit(1);
@@ -253,6 +259,8 @@ class Publish extends core_1.Command {
253
259
  platform,
254
260
  commitHash,
255
261
  message: resolvedMessage,
262
+ rolloutPercentage,
263
+ branch,
256
264
  })),
257
265
  runtimeVersion,
258
266
  platform,
@@ -318,7 +326,7 @@ class Publish extends core_1.Command {
318
326
  process.exit(1);
319
327
  }
320
328
  const markAsFinishedSpinner = (0, ora_1.ora)('🔗 Marking the updates as finished...').start();
321
- const results = await Promise.all(uploadUrls.map(async ({ updateId, platform, runtimeVersion }) => {
329
+ const results = await Promise.all(uploadUrls.map(async ({ updateId, platform, runtimeVersion, rolloutPercentage: echoedRolloutPercentage, }) => {
322
330
  const markAsUploadedUrl = new URL(`${serverUrl}/${appId}/markUpdateAsUploaded/${branch}`);
323
331
  markAsUploadedUrl.searchParams.set('platform', platform);
324
332
  markAsUploadedUrl.searchParams.set('updateId', updateId);
@@ -333,13 +341,27 @@ class Publish extends core_1.Command {
333
341
  // If success and status code = 200
334
342
  if (response.ok) {
335
343
  log_1.default.withInfo(`✅ Update ready for ${platform}`);
344
+ // Announce only when the server echoed the percentage back: an old server
345
+ // silently ignores the param and ships the update to 100% of devices.
346
+ if (rolloutPercentage !== undefined && echoedRolloutPercentage !== undefined) {
347
+ log_1.default.withInfo(`Progressive rollout started at ${rolloutPercentage}% for ${platform}. Manage it from the dashboard.`);
348
+ }
336
349
  return 'deployed';
337
350
  }
338
351
  // If response.status === 406 duplicate update
339
352
  if (response.status === 406) {
340
353
  log_1.default.withInfo(`⚠️ There is no change in the update for ${platform}, ignored...`);
354
+ if (rolloutPercentage !== undefined) {
355
+ log_1.default.withInfo(`No changes detected for ${platform}, no rollout was started.`);
356
+ }
341
357
  return 'identical';
342
358
  }
359
+ // The partial unique index can activate a rollout on this (branch, rtv) between
360
+ // requestUploadUrl and markUpdateAsUploaded, closing the publish race with a 409.
361
+ if (response.status === 409) {
362
+ log_1.default.error((0, assets_1.activeRolloutConflictMessage)(branch));
363
+ return 'error';
364
+ }
343
365
  log_1.default.error('❌ Failed to mark the update as finished for platform', platform);
344
366
  log_1.default.newLine();
345
367
  log_1.default.error(await response.text());
@@ -13,7 +13,8 @@ export interface RequestUploadUrlItem {
13
13
  fileName: string;
14
14
  filePath: string;
15
15
  }
16
- export declare function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion, platform, commitHash, message, }: {
16
+ export declare function activeRolloutConflictMessage(branch: string): string;
17
+ export declare function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion, platform, commitHash, message, rolloutPercentage, branch, }: {
17
18
  body: {
18
19
  fileNames: string[];
19
20
  };
@@ -23,8 +24,11 @@ export declare function requestUploadUrls({ body, requestUploadUrl, auth, runtim
23
24
  platform: string;
24
25
  commitHash?: string;
25
26
  message?: string;
27
+ rolloutPercentage?: number;
28
+ branch: string;
26
29
  }): Promise<{
27
30
  uploadRequests: RequestUploadUrlItem[];
28
31
  updateId: string;
32
+ rolloutPercentage?: number;
29
33
  }>;
30
34
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.requestUploadUrls = exports.computeFilesRequests = exports.MetadataJoi = void 0;
3
+ exports.requestUploadUrls = exports.activeRolloutConflictMessage = exports.computeFilesRequests = exports.MetadataJoi = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
6
6
  const joi_1 = tslib_1.__importDefault(require("joi"));
@@ -73,11 +73,18 @@ function computeFilesRequests(projectDir, outputDir, requestedPlatform) {
73
73
  return assets;
74
74
  }
75
75
  exports.computeFilesRequests = computeFilesRequests;
76
- async function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion, platform, commitHash, message, }) {
76
+ function activeRolloutConflictMessage(branch) {
77
+ return `A progressive rollout is already active for branch "${branch}" on this runtime version. End or revert it from the dashboard before publishing a new update.`;
78
+ }
79
+ exports.activeRolloutConflictMessage = activeRolloutConflictMessage;
80
+ async function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion, platform, commitHash, message, rolloutPercentage, branch, }) {
77
81
  const uploadUrl = new URL(requestUploadUrl);
78
82
  uploadUrl.searchParams.set('runtimeVersion', runtimeVersion);
79
83
  uploadUrl.searchParams.set('platform', platform);
80
84
  uploadUrl.searchParams.set('commitHash', commitHash ?? '');
85
+ if (rolloutPercentage !== undefined) {
86
+ uploadUrl.searchParams.set('rolloutPercentage', String(rolloutPercentage));
87
+ }
81
88
  const requestBody = { ...body };
82
89
  if (message) {
83
90
  requestBody.message = message;
@@ -90,10 +97,20 @@ async function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion,
90
97
  },
91
98
  body: JSON.stringify(requestBody),
92
99
  });
100
+ if (response.status === 409) {
101
+ throw new Error(activeRolloutConflictMessage(branch));
102
+ }
93
103
  if (!response.ok) {
94
104
  const text = await response.text();
95
105
  throw new Error(`Failed to request upload URL: ${text}`);
96
106
  }
97
- return await response.json();
107
+ const json = await response.json();
108
+ // An old server silently ignores unknown query params, so a missing echo means
109
+ // the rollout was not applied even though the flag was set. Abort before any
110
+ // file is uploaded: continuing would finalize a full 100% publish.
111
+ if (rolloutPercentage !== undefined && json.rolloutPercentage === undefined) {
112
+ throw new Error('The server ignored --rollout-percentage and would publish to 100% of devices. Update the server to a version that supports progressive rollouts, or publish without --rollout-percentage.');
113
+ }
114
+ return json;
98
115
  }
99
116
  exports.requestUploadUrls = requestUploadUrls;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eoas",
3
- "version": "3.0.0-beta.6",
3
+ "version": "3.0.0-beta.8",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "build": "tsc --project tsconfig.json",