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.
- package/README.md +59 -56
- package/build/channel/branch-mapping.d.ts +1 -0
- package/build/channel/branch-mapping.js +11 -1
- package/build/channel/print-utils.d.ts +2 -0
- package/build/channel/print-utils.js +39 -0
- package/build/channel/queries.js +3 -3
- package/build/channel/utils.d.ts +0 -1
- package/build/channel/utils.js +1 -34
- package/build/commands/channel/edit.js +6 -1
- package/build/commands/channel/rollout-preview.js +31 -0
- package/build/commands/update/republish.d.ts +2 -21
- package/build/commands/update/republish.js +9 -0
- package/build/update/republish.d.ts +3 -1
- package/build/update/republish.js +35 -12
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
package/build/channel/utils.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUpdateBranch = exports.
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
56
|
+
updateInfoGroup,
|
|
42
57
|
gitCommitHash: updatesToPublish[0].gitCommitHash,
|
|
43
|
-
awaitingCodeSigningInfo:
|
|
58
|
+
awaitingCodeSigningInfo: !!codeSigningInfo,
|
|
44
59
|
},
|
|
45
60
|
]);
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
await Promise.all(updatesRepublished.map(async (
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
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');
|