eas-cli 3.18.2 → 3.18.3

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.
@@ -1,11 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUpdateBranch = exports.logChannelDetails = exports.getBranchMapping = void 0;
3
+ exports.getUpdateBranch = exports.getBranchMapping = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const assert_1 = tslib_1.__importDefault(require("assert"));
6
- const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
- const log_1 = tslib_1.__importDefault(require("../log"));
8
- const utils_1 = require("../update/utils");
9
6
  const branch_mapping_1 = require("./branch-mapping");
10
7
  /**
11
8
  * Get the branch mapping and determine whether it is a rollout.
@@ -63,36 +60,6 @@ function getBranchMapping(branchMappingString) {
63
60
  return { branchMapping, isRollout, rolloutPercent };
64
61
  }
65
62
  exports.getBranchMapping = getBranchMapping;
66
- function logChannelDetails(channel) {
67
- const { branchMapping, isRollout, rolloutPercent } = getBranchMapping(channel.branchMapping);
68
- if (branchMapping.data.length > 2) {
69
- throw new Error('Branch Mapping data must have length less than or equal to 2.');
70
- }
71
- const rolloutBranchIds = branchMapping.data.map(data => data.branchId);
72
- const branchDescription = channel.updateBranches.flatMap(branch => {
73
- const updateGroupWithBranchDescriptions = (0, utils_1.getUpdateGroupDescriptionsWithBranch)(branch.updateGroups);
74
- const isRolloutBranch = isRollout && rolloutBranchIds.includes(branch.id);
75
- const isBaseBranch = rolloutBranchIds.length > 0 && rolloutBranchIds[0] === branch.id;
76
- let rolloutPercentNumber = undefined;
77
- if (isRolloutBranch) {
78
- rolloutPercentNumber = isBaseBranch ? rolloutPercent * 100 : (1 - rolloutPercent) * 100;
79
- }
80
- return updateGroupWithBranchDescriptions.map(({ branch, ...updateGroup }) => ({
81
- branch,
82
- branchRolloutPercentage: rolloutPercentNumber,
83
- update: updateGroup,
84
- }));
85
- });
86
- if (branchDescription.length === 0) {
87
- log_1.default.log(chalk_1.default.dim('No branches are pointed to this channel.'));
88
- }
89
- else {
90
- log_1.default.log(branchDescription
91
- .map(description => (0, utils_1.formatBranch)(description))
92
- .join(`\n\n${chalk_1.default.dim('———')}\n\n`));
93
- }
94
- }
95
- exports.logChannelDetails = logChannelDetails;
96
63
  function getUpdateBranchNullable(channel, branchId) {
97
64
  const updateBranches = channel.updateBranches;
98
65
  const updateBranch = updateBranches.find(branch => branch.id === branchId);
@@ -8,6 +8,7 @@ const chalk_1 = tslib_1.__importDefault(require("chalk"));
8
8
  const graphql_1 = require("graphql");
9
9
  const graphql_tag_1 = tslib_1.__importDefault(require("graphql-tag"));
10
10
  const queries_1 = require("../../branch/queries");
11
+ const branch_mapping_1 = require("../../channel/branch-mapping");
11
12
  const queries_2 = require("../../channel/queries");
12
13
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
13
14
  const flags_1 = require("../../commandUtils/flags");
@@ -16,6 +17,7 @@ const BranchQuery_1 = require("../../graphql/queries/BranchQuery");
16
17
  const ChannelQuery_1 = require("../../graphql/queries/ChannelQuery");
17
18
  const UpdateChannelBasicInfo_1 = require("../../graphql/types/UpdateChannelBasicInfo");
18
19
  const log_1 = tslib_1.__importDefault(require("../../log"));
20
+ const branch_mapping_2 = require("../../rollout/branch-mapping");
19
21
  const json_1 = require("../../utils/json");
20
22
  async function updateChannelBranchMappingAsync(graphqlClient, { channelId, branchMapping }) {
21
23
  const data = await (0, client_1.withErrorHandlingAsync)(graphqlClient
@@ -57,9 +59,12 @@ class ChannelEdit extends EasCommand_1.default {
57
59
  selectionPromptTitle: 'Select a channel to edit',
58
60
  paginatedQueryOptions: { json, nonInteractive, offset: 0 },
59
61
  });
60
- if (existingChannel.updateBranches.length > 1) {
62
+ if ((0, branch_mapping_2.isRollout)(existingChannel)) {
61
63
  throw new Error('There is a rollout in progress. Manage it with "channel:rollout" instead.');
62
64
  }
65
+ else if (!(0, branch_mapping_1.hasStandardBranchMap)(existingChannel)) {
66
+ throw new Error('Only standard branch mappings can be edited with this command.');
67
+ }
63
68
  const branch = branchFlag
64
69
  ? await BranchQuery_1.BranchQuery.getBranchByNameAsync(graphqlClient, {
65
70
  appId: projectId,
@@ -83,6 +83,37 @@ ChannelRolloutPreview.flags = {
83
83
  description: 'Rollout action to perform',
84
84
  options: Object.values(ActionRawFlagValue),
85
85
  required: false,
86
+ relationships: [
87
+ {
88
+ type: 'all',
89
+ flags: [
90
+ {
91
+ name: 'percent',
92
+ // eslint-disable-next-line async-protect/async-suffix
93
+ when: async (flags) => {
94
+ return (!!flags['non-interactive'] &&
95
+ (flags['action'] === ActionRawFlagValue.CREATE ||
96
+ flags['action'] === ActionRawFlagValue.EDIT));
97
+ },
98
+ },
99
+ {
100
+ name: 'outcome',
101
+ // eslint-disable-next-line async-protect/async-suffix
102
+ when: async (flags) => !!flags['non-interactive'] && flags['action'] === ActionRawFlagValue.END,
103
+ },
104
+ {
105
+ name: 'branch',
106
+ // eslint-disable-next-line async-protect/async-suffix
107
+ when: async (flags) => !!flags['non-interactive'] && flags['action'] === ActionRawFlagValue.CREATE,
108
+ },
109
+ {
110
+ name: 'runtime-version',
111
+ // eslint-disable-next-line async-protect/async-suffix
112
+ when: async (flags) => !!flags['non-interactive'] && flags['action'] === ActionRawFlagValue.CREATE,
113
+ },
114
+ ],
115
+ },
116
+ ],
86
117
  }),
87
118
  percent: core_1.Flags.integer({
88
119
  description: 'Percent of users to send to the new branch. Use with --action=edit or --action=create',
@@ -1,23 +1,4 @@
1
- import { Platform } from '@expo/config';
2
1
  import EasCommand from '../../commandUtils/EasCommand';
3
- type UpdateRepublishRawFlags = {
4
- branch?: string;
5
- channel?: string;
6
- group?: string;
7
- message?: string;
8
- platform: string;
9
- 'non-interactive': boolean;
10
- json?: boolean;
11
- };
12
- type UpdateRepublishFlags = {
13
- branchName?: string;
14
- channelName?: string;
15
- groupId?: string;
16
- updateMessage?: string;
17
- platform: Platform[];
18
- nonInteractive: boolean;
19
- json: boolean;
20
- };
21
2
  export default class UpdateRepublish extends EasCommand {
22
3
  static description: string;
23
4
  static flags: {
@@ -28,12 +9,12 @@ export default class UpdateRepublish extends EasCommand {
28
9
  group: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
29
10
  message: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
30
11
  platform: import("@oclif/core/lib/interfaces").OptionFlag<string>;
12
+ 'private-key-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
31
13
  };
32
14
  static contextDefinition: {
33
15
  loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
34
16
  privateProjectConfig: import("../../commandUtils/context/PrivateProjectConfigContextField").PrivateProjectConfigContextField;
35
17
  };
36
18
  runAsync(): Promise<void>;
37
- sanitizeFlags(rawFlags: UpdateRepublishRawFlags): UpdateRepublishFlags;
19
+ private sanitizeFlags;
38
20
  }
39
- export {};
@@ -13,6 +13,7 @@ const getBranchNameFromChannelNameAsync_1 = require("../../update/getBranchNameF
13
13
  const queries_1 = require("../../update/queries");
14
14
  const republish_1 = require("../../update/republish");
15
15
  const utils_1 = require("../../update/utils");
16
+ const code_signing_1 = require("../../utils/code-signing");
16
17
  const json_1 = require("../../utils/json");
17
18
  const defaultRepublishPlatforms = ['android', 'ios'];
18
19
  class UpdateRepublish extends EasCommand_1.default {
@@ -25,6 +26,7 @@ class UpdateRepublish extends EasCommand_1.default {
25
26
  if (flags.json) {
26
27
  (0, json_1.enableJsonOutput)();
27
28
  }
29
+ const codeSigningInfo = await (0, code_signing_1.getCodeSigningInfoAsync)(exp, flags.privateKeyPath);
28
30
  const existingUpdates = await getOrAskUpdatesAsync(graphqlClient, projectId, flags);
29
31
  const updatesToPublish = existingUpdates.filter(update => flags.platform.includes(update.platform));
30
32
  if (existingUpdates.length === 0) {
@@ -51,6 +53,7 @@ class UpdateRepublish extends EasCommand_1.default {
51
53
  updatesToPublish,
52
54
  targetBranch: { branchId: arbitraryUpdate.branchId, branchName: arbitraryUpdate.branchName },
53
55
  updateMessage,
56
+ codeSigningInfo,
54
57
  json: flags.json,
55
58
  });
56
59
  }
@@ -60,6 +63,7 @@ class UpdateRepublish extends EasCommand_1.default {
60
63
  const channelName = rawFlags.channel;
61
64
  const groupId = rawFlags.group;
62
65
  const nonInteractive = rawFlags['non-interactive'];
66
+ const privateKeyPath = rawFlags['private-key-path'];
63
67
  if (nonInteractive && !groupId) {
64
68
  throw new Error('Only --group can be used in non-interactive mode');
65
69
  }
@@ -73,6 +77,7 @@ class UpdateRepublish extends EasCommand_1.default {
73
77
  groupId,
74
78
  platform,
75
79
  updateMessage: rawFlags.message,
80
+ privateKeyPath,
76
81
  json: (_b = rawFlags.json) !== null && _b !== void 0 ? _b : false,
77
82
  nonInteractive,
78
83
  };
@@ -105,6 +110,10 @@ UpdateRepublish.flags = {
105
110
  default: 'all',
106
111
  required: false,
107
112
  }),
113
+ 'private-key-path': core_1.Flags.string({
114
+ description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory.`,
115
+ required: false,
116
+ }),
108
117
  ...flags_1.EasNonInteractiveAndJsonFlags,
109
118
  };
110
119
  UpdateRepublish.contextDefinition = {
@@ -1,6 +1,7 @@
1
1
  import { ExpoConfig } from '@expo/config';
2
2
  import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
3
3
  import { Update } from '../graphql/generated';
4
+ import { CodeSigningInfo } from '../utils/code-signing';
4
5
  export type UpdateToRepublish = {
5
6
  groupId: string;
6
7
  branchId: string;
@@ -10,7 +11,7 @@ export type UpdateToRepublish = {
10
11
  * @param updatesToPublish The update group to republish
11
12
  * @param targetBranch The branch to repubish the update group on
12
13
  */
13
- export declare function republishAsync({ graphqlClient, app, updatesToPublish, targetBranch, updateMessage, json, }: {
14
+ export declare function republishAsync({ graphqlClient, app, updatesToPublish, targetBranch, updateMessage, codeSigningInfo, json, }: {
14
15
  graphqlClient: ExpoGraphqlClient;
15
16
  app: {
16
17
  exp: ExpoConfig;
@@ -22,5 +23,6 @@ export declare function republishAsync({ graphqlClient, app, updatesToPublish, t
22
23
  branchId: string;
23
24
  };
24
25
  updateMessage: string;
26
+ codeSigningInfo?: CodeSigningInfo;
25
27
  json?: boolean;
26
28
  }): Promise<void>;
@@ -3,18 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.republishAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const assert_1 = tslib_1.__importDefault(require("assert"));
6
+ const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
6
7
  const url_1 = require("../build/utils/url");
8
+ const fetch_1 = tslib_1.__importDefault(require("../fetch"));
7
9
  const PublishMutation_1 = require("../graphql/mutations/PublishMutation");
8
10
  const log_1 = tslib_1.__importStar(require("../log"));
9
11
  const ora_1 = require("../ora");
10
12
  const projectUtils_1 = require("../project/projectUtils");
13
+ const code_signing_1 = require("../utils/code-signing");
11
14
  const formatFields_1 = tslib_1.__importDefault(require("../utils/formatFields"));
12
15
  const json_1 = require("../utils/json");
13
16
  /**
14
17
  * @param updatesToPublish The update group to republish
15
18
  * @param targetBranch The branch to repubish the update group on
16
19
  */
17
- async function republishAsync({ graphqlClient, app, updatesToPublish, targetBranch, updateMessage, json, }) {
20
+ async function republishAsync({ graphqlClient, app, updatesToPublish, targetBranch, updateMessage, codeSigningInfo, json, }) {
18
21
  const { branchName: targetBranchName, branchId: targetBranchId } = targetBranch;
19
22
  // The update group properties are the same for all updates
20
23
  (0, assert_1.default)(updatesToPublish.length > 0, 'Updates to republish must be provided');
@@ -25,31 +28,51 @@ async function republishAsync({ graphqlClient, app, updatesToPublish, targetBran
25
28
  update.runtimeVersion === arbitraryUpdate.runtimeVersion;
26
29
  (0, assert_1.default)(updatesToPublish.every(isSameGroup), 'All updates must belong to the same update group');
27
30
  const { runtimeVersion } = arbitraryUpdate;
28
- // If codesigning was created for the original update, we need to add it to the republish
31
+ // If codesigning was created for the original update, we need to add it to the republish.
32
+ // If one wishes to not sign the republish or sign with a different key, a normal publish should
33
+ // be performed.
29
34
  const shouldRepublishWithCodesigning = updatesToPublish.some(update => update.codeSigningInfo);
30
35
  if (shouldRepublishWithCodesigning) {
31
- log_1.default.withTick(`The republished update will be signed with the same codesigning as the original update.`);
36
+ if (!codeSigningInfo) {
37
+ throw new Error('Must specify --private-key-path argument to sign republished update for code signing');
38
+ }
39
+ for (const update of updatesToPublish) {
40
+ if ((0, nullthrows_1.default)(update.codeSigningInfo).alg !== codeSigningInfo.codeSigningMetadata.alg ||
41
+ (0, nullthrows_1.default)(update.codeSigningInfo).keyid !== codeSigningInfo.codeSigningMetadata.keyid) {
42
+ throw new Error('Republished updates must use the same code signing key and algorithm as original update');
43
+ }
44
+ }
45
+ log_1.default.withTick(`The republished update will be signed`);
32
46
  }
33
47
  const publishIndicator = (0, ora_1.ora)('Republishing...').start();
34
48
  let updatesRepublished;
35
49
  try {
50
+ const updateInfoGroup = Object.fromEntries(updatesToPublish.map(update => [update.platform, JSON.parse(update.manifestFragment)]));
36
51
  updatesRepublished = await PublishMutation_1.PublishMutation.publishUpdateGroupAsync(graphqlClient, [
37
52
  {
38
53
  branchId: targetBranchId,
39
54
  runtimeVersion,
40
55
  message: updateMessage,
41
- updateInfoGroup: Object.fromEntries(updatesToPublish.map(update => [update.platform, JSON.parse(update.manifestFragment)])),
56
+ updateInfoGroup,
42
57
  gitCommitHash: updatesToPublish[0].gitCommitHash,
43
- awaitingCodeSigningInfo: shouldRepublishWithCodesigning,
58
+ awaitingCodeSigningInfo: !!codeSigningInfo,
44
59
  },
45
60
  ]);
46
- if (shouldRepublishWithCodesigning) {
47
- const codeSigningByPlatform = Object.fromEntries(updatesToPublish.map(update => [update.platform, update.codeSigningInfo]));
48
- await Promise.all(updatesRepublished.map(async (update) => {
49
- const codeSigning = codeSigningByPlatform[update.platform];
50
- if (codeSigning) {
51
- await PublishMutation_1.PublishMutation.setCodeSigningInfoAsync(graphqlClient, update.id, codeSigning);
52
- }
61
+ if (codeSigningInfo) {
62
+ log_1.default.log('🔒 Signing republished update');
63
+ await Promise.all(updatesRepublished.map(async (newUpdate) => {
64
+ const response = await (0, fetch_1.default)(newUpdate.manifestPermalink, {
65
+ method: 'GET',
66
+ headers: { accept: 'multipart/mixed' },
67
+ });
68
+ const manifestBody = (0, nullthrows_1.default)(await (0, code_signing_1.getManifestBodyAsync)(response));
69
+ (0, code_signing_1.checkManifestBodyAgainstUpdateInfoGroup)(manifestBody, (0, nullthrows_1.default)((0, nullthrows_1.default)(updateInfoGroup)[newUpdate.platform]));
70
+ const manifestSignature = (0, code_signing_1.signBody)(manifestBody, codeSigningInfo);
71
+ await PublishMutation_1.PublishMutation.setCodeSigningInfoAsync(graphqlClient, newUpdate.id, {
72
+ alg: codeSigningInfo.codeSigningMetadata.alg,
73
+ keyid: codeSigningInfo.codeSigningMetadata.keyid,
74
+ sig: manifestSignature,
75
+ });
53
76
  }));
54
77
  }
55
78
  publishIndicator.succeed('Republished update');