eas-cli 14.5.0 → 14.7.0
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 +130 -76
- package/build/build/android/graphql.js +2 -0
- package/build/build/android/prepareJob.js +1 -0
- package/build/build/android/version.d.ts +1 -0
- package/build/build/android/version.js +5 -1
- package/build/build/configure.d.ts +11 -0
- package/build/build/configure.js +46 -1
- package/build/build/evaluateConfigWithEnvVarsAsync.js +2 -4
- package/build/build/ios/build.js +2 -0
- package/build/build/ios/graphql.js +2 -0
- package/build/build/ios/prepareJob.js +1 -0
- package/build/build/ios/version.js +4 -1
- package/build/build/local.js +1 -1
- package/build/build/metadata.js +0 -1
- package/build/build/runBuildAndSubmit.d.ts +12 -1
- package/build/build/runBuildAndSubmit.js +16 -13
- package/build/build/utils/environment.d.ts +4 -0
- package/build/build/utils/environment.js +20 -0
- package/build/commands/build/dev.d.ts +23 -0
- package/build/commands/build/dev.js +225 -0
- package/build/commands/build/index.js +11 -1
- package/build/commands/build/inspect.js +26 -18
- package/build/commands/build/internal.js +22 -14
- package/build/commands/fingerprint/compare.d.ts +14 -1
- package/build/commands/fingerprint/compare.js +192 -53
- package/build/commands/project/onboarding.js +23 -14
- package/build/credentials/ios/appstore/ensureAppExists.d.ts +22 -1
- package/build/credentials/ios/appstore/ensureAppExists.js +73 -2
- package/build/credentials/ios/appstore/ensureTestFlightGroup.d.ts +7 -0
- package/build/credentials/ios/appstore/ensureTestFlightGroup.js +158 -0
- package/build/credentials/ios/appstore/provisioningProfile.js +1 -0
- package/build/graphql/generated.d.ts +193 -3
- package/build/graphql/generated.js +11 -2
- package/build/graphql/queries/FingerprintQuery.d.ts +16 -0
- package/build/graphql/queries/FingerprintQuery.js +61 -0
- package/build/graphql/types/Fingerprint.js +18 -0
- package/build/project/ios/exemptEncryption.d.ts +7 -0
- package/build/project/ios/exemptEncryption.js +81 -0
- package/build/submit/ios/AppProduce.js +12 -23
- package/build/utils/fingerprintCli.d.ts +2 -1
- package/build/utils/fingerprintCli.js +11 -3
- package/oclif.manifest.json +64 -4
- package/package.json +4 -4
|
@@ -58,9 +58,11 @@ class Build extends EasCommand_1.default {
|
|
|
58
58
|
description: 'Clear cache before the build',
|
|
59
59
|
}),
|
|
60
60
|
'auto-submit': core_1.Flags.boolean({
|
|
61
|
+
char: 's',
|
|
61
62
|
default: false,
|
|
62
63
|
description: 'Submit on build complete using the submit profile with the same name as the build profile',
|
|
63
64
|
exclusive: ['auto-submit-with-profile'],
|
|
65
|
+
aliases: ['submit'],
|
|
64
66
|
}),
|
|
65
67
|
'auto-submit-with-profile': core_1.Flags.string({
|
|
66
68
|
description: 'Submit on build complete using the submit profile with provided name',
|
|
@@ -118,7 +120,15 @@ class Build extends EasCommand_1.default {
|
|
|
118
120
|
: [generated_1.StatuspageServiceName.EasBuild]);
|
|
119
121
|
}
|
|
120
122
|
const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
|
|
121
|
-
await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)(
|
|
123
|
+
await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)({
|
|
124
|
+
graphqlClient,
|
|
125
|
+
analytics,
|
|
126
|
+
vcsClient,
|
|
127
|
+
projectDir,
|
|
128
|
+
flags: flagsWithPlatform,
|
|
129
|
+
actor,
|
|
130
|
+
getDynamicPrivateProjectConfigAsync,
|
|
131
|
+
});
|
|
122
132
|
}
|
|
123
133
|
sanitizeFlags(flags) {
|
|
124
134
|
const nonInteractive = flags['non-interactive'];
|
|
@@ -83,25 +83,33 @@ class BuildInspect extends EasCommand_1.default {
|
|
|
83
83
|
}
|
|
84
84
|
else {
|
|
85
85
|
try {
|
|
86
|
-
await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
86
|
+
await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)({
|
|
87
|
+
graphqlClient,
|
|
88
|
+
analytics,
|
|
89
|
+
vcsClient,
|
|
90
|
+
projectDir,
|
|
91
|
+
flags: {
|
|
92
|
+
nonInteractive: false,
|
|
93
|
+
freezeCredentials: false,
|
|
94
|
+
wait: true,
|
|
95
|
+
clearCache: false,
|
|
96
|
+
json: false,
|
|
97
|
+
autoSubmit: false,
|
|
98
|
+
requestedPlatform: flags.platform,
|
|
99
|
+
profile: flags.profile,
|
|
100
|
+
localBuildOptions: {
|
|
101
|
+
localBuildMode: local_1.LocalBuildMode.LOCAL_BUILD_PLUGIN,
|
|
102
|
+
...(flags.stage === InspectStage.PRE_BUILD ? { skipNativeBuild: true } : {}),
|
|
103
|
+
...(flags.stage === InspectStage.POST_BUILD ? { skipCleanup: true } : {}),
|
|
104
|
+
verbose: flags.verbose,
|
|
105
|
+
workingdir: tmpWorkingdir,
|
|
106
|
+
artifactsDir: path_1.default.join(tmpWorkingdir, 'artifacts'),
|
|
107
|
+
},
|
|
108
|
+
repack: false,
|
|
102
109
|
},
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
actor,
|
|
111
|
+
getDynamicPrivateProjectConfigAsync,
|
|
112
|
+
});
|
|
105
113
|
if (!flags.verbose) {
|
|
106
114
|
log_1.default.log(chalk_1.default.green('Build successful'));
|
|
107
115
|
}
|
|
@@ -56,21 +56,29 @@ class BuildInternal extends EasCommand_1.default {
|
|
|
56
56
|
withServerSideEnvironment: null,
|
|
57
57
|
});
|
|
58
58
|
await (0, _1.handleDeprecatedEasJsonAsync)(projectDir, flags.nonInteractive);
|
|
59
|
-
await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)({
|
|
60
|
+
graphqlClient,
|
|
61
|
+
analytics,
|
|
62
|
+
vcsClient,
|
|
63
|
+
projectDir,
|
|
64
|
+
flags: {
|
|
65
|
+
requestedPlatform: flags.platform,
|
|
66
|
+
profile: flags.profile,
|
|
67
|
+
nonInteractive: true,
|
|
68
|
+
freezeCredentials: false,
|
|
69
|
+
wait: false,
|
|
70
|
+
clearCache: false,
|
|
71
|
+
json: true,
|
|
72
|
+
autoSubmit: flags['auto-submit'] || flags['auto-submit-with-profile'] !== undefined,
|
|
73
|
+
localBuildOptions: {
|
|
74
|
+
localBuildMode: local_1.LocalBuildMode.INTERNAL,
|
|
75
|
+
},
|
|
76
|
+
submitProfile: flags['auto-submit-with-profile'] ?? flags.profile,
|
|
77
|
+
repack: false,
|
|
70
78
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
79
|
+
actor,
|
|
80
|
+
getDynamicPrivateProjectConfigAsync,
|
|
81
|
+
});
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
exports.default = BuildInternal;
|
|
@@ -2,9 +2,22 @@ import EasCommand from '../../commandUtils/EasCommand';
|
|
|
2
2
|
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
|
3
3
|
import { BuildStatus } from '../../graphql/generated';
|
|
4
4
|
import { RequestedPlatform } from '../../platform';
|
|
5
|
+
export interface FingerprintCompareFlags {
|
|
6
|
+
buildId?: string;
|
|
7
|
+
hash1?: string;
|
|
8
|
+
hash2?: string;
|
|
9
|
+
nonInteractive: boolean;
|
|
10
|
+
json: boolean;
|
|
11
|
+
}
|
|
5
12
|
export default class FingerprintCompare extends EasCommand {
|
|
6
13
|
static description: string;
|
|
7
|
-
static
|
|
14
|
+
static strict: boolean;
|
|
15
|
+
static examples: string[];
|
|
16
|
+
static args: {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
required: boolean;
|
|
20
|
+
}[];
|
|
8
21
|
static flags: {
|
|
9
22
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
23
|
'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
@@ -11,6 +11,7 @@ const flags_1 = require("../../commandUtils/flags");
|
|
|
11
11
|
const generated_1 = require("../../graphql/generated");
|
|
12
12
|
const FingerprintMutation_1 = require("../../graphql/mutations/FingerprintMutation");
|
|
13
13
|
const BuildQuery_1 = require("../../graphql/queries/BuildQuery");
|
|
14
|
+
const FingerprintQuery_1 = require("../../graphql/queries/FingerprintQuery");
|
|
14
15
|
const log_1 = tslib_1.__importDefault(require("../../log"));
|
|
15
16
|
const ora_1 = require("../../ora");
|
|
16
17
|
const maybeUploadFingerprintAsync_1 = require("../../project/maybeUploadFingerprintAsync");
|
|
@@ -21,9 +22,33 @@ const fingerprintCli_1 = require("../../utils/fingerprintCli");
|
|
|
21
22
|
const fingerprintDiff_1 = require("../../utils/fingerprintDiff");
|
|
22
23
|
const formatFields_1 = tslib_1.__importDefault(require("../../utils/formatFields"));
|
|
23
24
|
const json_1 = require("../../utils/json");
|
|
25
|
+
var FingerprintOriginType;
|
|
26
|
+
(function (FingerprintOriginType) {
|
|
27
|
+
FingerprintOriginType["Build"] = "build";
|
|
28
|
+
FingerprintOriginType["Hash"] = "hash";
|
|
29
|
+
FingerprintOriginType["Project"] = "project";
|
|
30
|
+
})(FingerprintOriginType || (FingerprintOriginType = {}));
|
|
24
31
|
class FingerprintCompare extends EasCommand_1.default {
|
|
25
32
|
static description = 'compare fingerprints of the current project, builds and updates';
|
|
26
|
-
static
|
|
33
|
+
static strict = false;
|
|
34
|
+
static examples = [
|
|
35
|
+
'$ eas fingerprint:compare \t # Compare fingerprints in interactive mode',
|
|
36
|
+
'$ eas fingerprint:compare c71a7d475aa6f75291bc93cd74aef395c3c94eee \t # Compare fingerprint against local directory',
|
|
37
|
+
'$ eas fingerprint:compare c71a7d475aa6f75291bc93cd74aef395c3c94eee f0d6a916e73f401d428e6e006e07b12453317ba2 \t # Compare provided fingerprints',
|
|
38
|
+
'$ eas fingerprint:compare --build-id 82bc6456-611a-48cb-8db4-5f9eb2ca1003 \t # Compare fingerprint from build against local directory',
|
|
39
|
+
];
|
|
40
|
+
static args = [
|
|
41
|
+
{
|
|
42
|
+
name: 'hash1',
|
|
43
|
+
description: "If provided alone, HASH1 is compared against the current project's fingerprint.",
|
|
44
|
+
required: false,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'hash2',
|
|
48
|
+
description: 'If two hashes are provided, HASH1 is compared against HASH2.',
|
|
49
|
+
required: false,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
27
52
|
static flags = {
|
|
28
53
|
'build-id': core_1.Flags.string({
|
|
29
54
|
aliases: ['buildId'],
|
|
@@ -38,70 +63,33 @@ class FingerprintCompare extends EasCommand_1.default {
|
|
|
38
63
|
...this.ContextOptions.Vcs,
|
|
39
64
|
};
|
|
40
65
|
async runAsync() {
|
|
41
|
-
const { flags } = await this.parse(FingerprintCompare);
|
|
42
|
-
const {
|
|
66
|
+
const { args, flags } = await this.parse(FingerprintCompare);
|
|
67
|
+
const { hash1, hash2 } = args;
|
|
68
|
+
const { json, 'non-interactive': nonInteractive, 'build-id': buildId } = flags;
|
|
69
|
+
const sanitizedFlagsAndArgs = { json, nonInteractive, buildId, hash1, hash2 };
|
|
43
70
|
const { projectId, privateProjectConfig: { projectDir }, loggedIn: { graphqlClient }, vcsClient, } = await this.getContextAsync(FingerprintCompare, {
|
|
44
71
|
nonInteractive,
|
|
45
72
|
withServerSideEnvironment: null,
|
|
46
73
|
});
|
|
47
|
-
if (
|
|
74
|
+
if (json) {
|
|
48
75
|
(0, json_1.enableJsonOutput)();
|
|
49
76
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
|
|
57
|
-
filters: { hasFingerprint: true },
|
|
58
|
-
});
|
|
59
|
-
if (!buildId) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
log_1.default.log(`Comparing fingerprints of the current project and build ${buildId}…`);
|
|
64
|
-
const buildWithFingerprint = await BuildQuery_1.BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
|
|
65
|
-
const fingerprintDebugUrl = buildWithFingerprint.fingerprint?.debugInfoUrl;
|
|
66
|
-
if (!fingerprintDebugUrl) {
|
|
67
|
-
log_1.default.error('A fingerprint for the build could not be found.');
|
|
77
|
+
const firstFingerprintInfo = await getFirstFingerprintInfoAsync(graphqlClient, projectId, sanitizedFlagsAndArgs);
|
|
78
|
+
const { fingerprint: firstFingerprint, origin: firstFingerprintOrigin } = firstFingerprintInfo;
|
|
79
|
+
const secondFingerprintInfo = await getSecondFingerprintInfoAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo, sanitizedFlagsAndArgs);
|
|
80
|
+
const { fingerprint: secondFingerprint, origin: secondFingerprintOrigin } = secondFingerprintInfo;
|
|
81
|
+
if (json) {
|
|
82
|
+
(0, json_1.printJsonOnlyOutput)({ fingerprint1: firstFingerprint, fingerprint2: secondFingerprint });
|
|
68
83
|
return;
|
|
69
84
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const workflows = await (0, workflow_1.resolveWorkflowPerPlatformAsync)(projectDir, vcsClient);
|
|
73
|
-
const buildPlatform = buildWithFingerprint.platform;
|
|
74
|
-
const workflow = workflows[appPlatformToPlatform(buildPlatform)];
|
|
75
|
-
const projectFingerprint = await (0, fingerprintCli_1.createFingerprintAsync)(projectDir, {
|
|
76
|
-
workflow,
|
|
77
|
-
platforms: [appPlatformToString(buildPlatform)],
|
|
78
|
-
debug: true,
|
|
79
|
-
env: undefined,
|
|
80
|
-
});
|
|
81
|
-
if (!projectFingerprint) {
|
|
82
|
-
log_1.default.error('Project fingerprints can only be computed for projects with SDK 52 or higher');
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const uploadedFingerprint = await (0, maybeUploadFingerprintAsync_1.maybeUploadFingerprintAsync)({
|
|
86
|
-
hash: fingerprint.hash,
|
|
87
|
-
fingerprint: {
|
|
88
|
-
fingerprintSources: fingerprint.sources,
|
|
89
|
-
isDebugFingerprintSource: log_1.default.isDebug,
|
|
90
|
-
},
|
|
91
|
-
graphqlClient,
|
|
92
|
-
});
|
|
93
|
-
await FingerprintMutation_1.FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, {
|
|
94
|
-
hash: uploadedFingerprint.hash,
|
|
95
|
-
source: uploadedFingerprint.fingerprintSource,
|
|
96
|
-
});
|
|
97
|
-
if (fingerprint.hash === projectFingerprint.hash) {
|
|
98
|
-
log_1.default.log(`✅ Project fingerprint matches build`);
|
|
85
|
+
if (firstFingerprint.hash === secondFingerprint.hash) {
|
|
86
|
+
log_1.default.log(`✅ ${capitalizeFirstLetter(prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin))} matches fingerprint from ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`);
|
|
99
87
|
return;
|
|
100
88
|
}
|
|
101
89
|
else {
|
|
102
|
-
log_1.default.log(`🔄
|
|
90
|
+
log_1.default.log(`🔄 ${capitalizeFirstLetter(prettyPrintFingerprint(firstFingerprint, firstFingerprintOrigin))} differs from ${prettyPrintFingerprint(secondFingerprint, secondFingerprintOrigin)}`);
|
|
103
91
|
}
|
|
104
|
-
const fingerprintDiffs = (0, fingerprintCli_1.diffFingerprint)(projectDir,
|
|
92
|
+
const fingerprintDiffs = (0, fingerprintCli_1.diffFingerprint)(projectDir, firstFingerprint, secondFingerprint);
|
|
105
93
|
if (!fingerprintDiffs) {
|
|
106
94
|
log_1.default.error('Fingerprint diffs can only be computed for projects with SDK 52 or higher');
|
|
107
95
|
return;
|
|
@@ -153,6 +141,147 @@ class FingerprintCompare extends EasCommand_1.default {
|
|
|
153
141
|
}
|
|
154
142
|
}
|
|
155
143
|
exports.default = FingerprintCompare;
|
|
144
|
+
function prettyPrintFingerprint(fingerprint, origin) {
|
|
145
|
+
if (origin.type === FingerprintOriginType.Hash) {
|
|
146
|
+
return `fingerprint ${fingerprint.hash} from hash`;
|
|
147
|
+
}
|
|
148
|
+
return `fingerprint ${fingerprint.hash} from ${origin.type}`;
|
|
149
|
+
}
|
|
150
|
+
function capitalizeFirstLetter(string) {
|
|
151
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
152
|
+
}
|
|
153
|
+
async function getFirstFingerprintInfoAsync(graphqlClient, projectId, { buildId: buildIdFromArg, hash1, nonInteractive }) {
|
|
154
|
+
if (hash1) {
|
|
155
|
+
const fingerprintFragment = await getFingerprintFragmentFromHashAsync(graphqlClient, projectId, hash1);
|
|
156
|
+
const fingerprint = await getFingerprintFromFingerprintFragmentAsync(fingerprintFragment);
|
|
157
|
+
let platforms;
|
|
158
|
+
const fingerprintBuilds = fingerprintFragment.builds?.edges.map(edge => edge.node) ?? [];
|
|
159
|
+
const fingerprintUpdates = fingerprintFragment.updates?.edges.map(edge => edge.node) ?? [];
|
|
160
|
+
if (fingerprintBuilds.length > 0) {
|
|
161
|
+
platforms = [fingerprintBuilds[0].platform];
|
|
162
|
+
}
|
|
163
|
+
else if (fingerprintUpdates.length > 0) {
|
|
164
|
+
platforms = [stringToAppPlatform(fingerprintUpdates[0].platform)];
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
fingerprint,
|
|
168
|
+
platforms,
|
|
169
|
+
origin: {
|
|
170
|
+
type: FingerprintOriginType.Hash,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
let buildId = buildIdFromArg ?? null;
|
|
175
|
+
if (!buildId) {
|
|
176
|
+
if (nonInteractive) {
|
|
177
|
+
throw new Error('Build ID must be provided in non-interactive mode');
|
|
178
|
+
}
|
|
179
|
+
const displayName = await (0, projectUtils_1.getDisplayNameForProjectIdAsync)(graphqlClient, projectId);
|
|
180
|
+
buildId = await selectBuildToCompareAsync(graphqlClient, projectId, displayName, {
|
|
181
|
+
filters: { hasFingerprint: true },
|
|
182
|
+
});
|
|
183
|
+
if (!buildId) {
|
|
184
|
+
throw new Error('Must select build with fingerprint for comparison.');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
log_1.default.log(`Comparing fingerprints of the current project and build ${buildId}…`);
|
|
188
|
+
const buildWithFingerprint = await BuildQuery_1.BuildQuery.withFingerprintByIdAsync(graphqlClient, buildId);
|
|
189
|
+
if (!buildWithFingerprint.fingerprint) {
|
|
190
|
+
throw new Error(`Fingerprint for build ${buildId} was not computed.`);
|
|
191
|
+
}
|
|
192
|
+
else if (!buildWithFingerprint.fingerprint.debugInfoUrl) {
|
|
193
|
+
throw new Error(`Fingerprint source for build ${buildId} was not computed.`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
fingerprint: await getFingerprintFromFingerprintFragmentAsync(buildWithFingerprint.fingerprint),
|
|
197
|
+
platforms: [buildWithFingerprint.platform],
|
|
198
|
+
origin: {
|
|
199
|
+
type: FingerprintOriginType.Build,
|
|
200
|
+
build: buildWithFingerprint,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function getSecondFingerprintInfoAsync(graphqlClient, projectDir, projectId, vcsClient, firstFingerprintInfo, { hash2 }) {
|
|
205
|
+
if (hash2) {
|
|
206
|
+
const fingerprintFragment = await getFingerprintFragmentFromHashAsync(graphqlClient, projectId, hash2);
|
|
207
|
+
if (!fingerprintFragment) {
|
|
208
|
+
throw new Error(`Fingerprint with hash ${hash2} was not uploaded.`);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
fingerprint: await getFingerprintFromFingerprintFragmentAsync(fingerprintFragment),
|
|
212
|
+
origin: { type: FingerprintOriginType.Hash },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const firstFingerprintPlatforms = firstFingerprintInfo.platforms;
|
|
216
|
+
if (!firstFingerprintPlatforms) {
|
|
217
|
+
throw new Error(`Cannot compare the local directory against the provided fingerprint hash "${firstFingerprintInfo.fingerprint.hash}" because the associated platform could not be determined. Ensure the fingerprint is linked to a build or update to identify the platform.`);
|
|
218
|
+
}
|
|
219
|
+
const workflows = await (0, workflow_1.resolveWorkflowPerPlatformAsync)(projectDir, vcsClient);
|
|
220
|
+
const optionsFromWorkflow = getFingerprintOptionsFromWorkflow(firstFingerprintPlatforms, workflows);
|
|
221
|
+
const projectFingerprint = await (0, fingerprintCli_1.createFingerprintAsync)(projectDir, {
|
|
222
|
+
...optionsFromWorkflow,
|
|
223
|
+
platforms: firstFingerprintPlatforms.map(appPlatformToString),
|
|
224
|
+
debug: true,
|
|
225
|
+
env: undefined,
|
|
226
|
+
});
|
|
227
|
+
if (!projectFingerprint) {
|
|
228
|
+
throw new Error('Project fingerprints can only be computed for projects with SDK 52 or higher');
|
|
229
|
+
}
|
|
230
|
+
const uploadedFingerprint = await (0, maybeUploadFingerprintAsync_1.maybeUploadFingerprintAsync)({
|
|
231
|
+
hash: projectFingerprint.hash,
|
|
232
|
+
fingerprint: {
|
|
233
|
+
fingerprintSources: projectFingerprint.sources,
|
|
234
|
+
isDebugFingerprintSource: log_1.default.isDebug,
|
|
235
|
+
},
|
|
236
|
+
graphqlClient,
|
|
237
|
+
});
|
|
238
|
+
await FingerprintMutation_1.FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, {
|
|
239
|
+
hash: uploadedFingerprint.hash,
|
|
240
|
+
source: uploadedFingerprint.fingerprintSource,
|
|
241
|
+
});
|
|
242
|
+
return { fingerprint: projectFingerprint, origin: { type: FingerprintOriginType.Project } };
|
|
243
|
+
}
|
|
244
|
+
async function getFingerprintFragmentFromHashAsync(graphqlClient, projectId, hash) {
|
|
245
|
+
const fingerprint = await FingerprintQuery_1.FingerprintQuery.byHashAsync(graphqlClient, {
|
|
246
|
+
appId: projectId,
|
|
247
|
+
hash,
|
|
248
|
+
});
|
|
249
|
+
if (!fingerprint) {
|
|
250
|
+
const displayName = await (0, projectUtils_1.getDisplayNameForProjectIdAsync)(graphqlClient, projectId);
|
|
251
|
+
throw new Error(`Fingerprint with hash ${hash} was not uploaded for ${displayName}.`);
|
|
252
|
+
}
|
|
253
|
+
return fingerprint;
|
|
254
|
+
}
|
|
255
|
+
async function getFingerprintFromFingerprintFragmentAsync(fingerprintFragment) {
|
|
256
|
+
const fingerprintDebugUrl = fingerprintFragment.debugInfoUrl;
|
|
257
|
+
if (!fingerprintDebugUrl) {
|
|
258
|
+
throw new Error(`The source for fingerprint hash ${fingerprintFragment.hash} was not computed.`);
|
|
259
|
+
}
|
|
260
|
+
const fingerprintResponse = await fetch(fingerprintDebugUrl);
|
|
261
|
+
return (await fingerprintResponse.json());
|
|
262
|
+
}
|
|
263
|
+
function getFingerprintOptionsFromWorkflow(platforms, workflowsByPlatform) {
|
|
264
|
+
if (platforms.length === 0) {
|
|
265
|
+
throw new Error('Could not determine platform from fingerprint sources');
|
|
266
|
+
}
|
|
267
|
+
// Single platform case
|
|
268
|
+
if (platforms.length === 1) {
|
|
269
|
+
const platform = platforms[0];
|
|
270
|
+
return { workflow: workflowsByPlatform[appPlatformToPlatform(platform)] };
|
|
271
|
+
}
|
|
272
|
+
// Multiple platforms case
|
|
273
|
+
const workflows = platforms.map(platform => workflowsByPlatform[appPlatformToPlatform(platform)]);
|
|
274
|
+
// If all workflows are the same, return the common workflow
|
|
275
|
+
const [firstWorkflow, ...restWorkflows] = workflows;
|
|
276
|
+
if (restWorkflows.every(workflow => workflow === firstWorkflow)) {
|
|
277
|
+
return { workflow: firstWorkflow };
|
|
278
|
+
}
|
|
279
|
+
// Generate ignorePaths for mixed workflows
|
|
280
|
+
const ignorePaths = platforms
|
|
281
|
+
.filter(platform => workflowsByPlatform[appPlatformToPlatform(platform)] === eas_build_job_1.Workflow.MANAGED)
|
|
282
|
+
.map(platform => `${appPlatformToString(platform)}/**/*`);
|
|
283
|
+
return { ignorePaths };
|
|
284
|
+
}
|
|
156
285
|
function printContentDiff(diff) {
|
|
157
286
|
if (diff.op === 'added') {
|
|
158
287
|
const sourceType = diff.addedSource.type;
|
|
@@ -312,6 +441,16 @@ function appPlatformToString(platform) {
|
|
|
312
441
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
313
442
|
}
|
|
314
443
|
}
|
|
444
|
+
function stringToAppPlatform(platform) {
|
|
445
|
+
switch (platform) {
|
|
446
|
+
case 'android':
|
|
447
|
+
return generated_1.AppPlatform.Android;
|
|
448
|
+
case 'ios':
|
|
449
|
+
return generated_1.AppPlatform.Ios;
|
|
450
|
+
default:
|
|
451
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
315
454
|
async function selectBuildToCompareAsync(graphqlClient, projectId, projectDisplayName, { filters, } = {}) {
|
|
316
455
|
const spinner = (0, ora_1.ora)().start('Fetching builds…');
|
|
317
456
|
let builds;
|
|
@@ -204,20 +204,29 @@ class Onboarding extends EasCommand_1.default {
|
|
|
204
204
|
else {
|
|
205
205
|
log_1.default.log('🚀 Now we are going to trigger your first build');
|
|
206
206
|
log_1.default.log();
|
|
207
|
-
const { buildIds } = await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
207
|
+
const { buildIds } = await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)({
|
|
208
|
+
graphqlClient,
|
|
209
|
+
analytics,
|
|
210
|
+
vcsClient,
|
|
211
|
+
projectDir: finalTargetProjectDirectory,
|
|
212
|
+
flags: {
|
|
213
|
+
nonInteractive: true,
|
|
214
|
+
requestedPlatform: platform === eas_build_job_1.Platform.ANDROID ? platform_1.RequestedPlatform.Android : platform_1.RequestedPlatform.Ios,
|
|
215
|
+
profile: actor.preferences.onboarding.deviceType === generated_1.OnboardingDeviceType.Simulator
|
|
216
|
+
? 'development-simulator'
|
|
217
|
+
: 'development',
|
|
218
|
+
wait: false,
|
|
219
|
+
clearCache: false,
|
|
220
|
+
json: false,
|
|
221
|
+
autoSubmit: false,
|
|
222
|
+
localBuildOptions: {},
|
|
223
|
+
freezeCredentials: false,
|
|
224
|
+
repack: true,
|
|
225
|
+
},
|
|
226
|
+
actor,
|
|
227
|
+
// eslint-disable-next-line async-protect/async-suffix
|
|
228
|
+
getDynamicPrivateProjectConfigAsync: getDynamicProjectConfigFn,
|
|
229
|
+
});
|
|
221
230
|
const buildId = buildIds[0];
|
|
222
231
|
log_1.default.log();
|
|
223
232
|
log_1.default.log('🚀 You can now go back to the website to continue:');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="@expo/apple-utils/ts-declarations/expo__app-store" />
|
|
2
|
-
import { App, BundleId } from '@expo/apple-utils';
|
|
2
|
+
import { App, BundleId, RequestContext } from '@expo/apple-utils';
|
|
3
3
|
import { JSONObject } from '@expo/json-file';
|
|
4
4
|
import { AuthCtx, UserAuthCtx } from './authenticateTypes';
|
|
5
5
|
export interface IosCapabilitiesOptions {
|
|
@@ -25,3 +25,24 @@ export declare function ensureAppExistsAsync(userAuthCtx: UserAuthCtx, { name, l
|
|
|
25
25
|
bundleIdentifier: string;
|
|
26
26
|
sku?: string;
|
|
27
27
|
}): Promise<App>;
|
|
28
|
+
export declare function createAppAsync(context: RequestContext, props: {
|
|
29
|
+
bundleId: string;
|
|
30
|
+
name: string;
|
|
31
|
+
primaryLocale?: string;
|
|
32
|
+
companyName?: string;
|
|
33
|
+
sku?: string;
|
|
34
|
+
}, retryCount?: number): Promise<App>;
|
|
35
|
+
export declare function isAppleError(error: any): error is {
|
|
36
|
+
data: {
|
|
37
|
+
errors: {
|
|
38
|
+
id: string;
|
|
39
|
+
status: string;
|
|
40
|
+
/** 'ENTITY_ERROR.ATTRIBUTE.INVALID.INVALID_CHARACTERS' */
|
|
41
|
+
code: string;
|
|
42
|
+
/** 'An attribute value has invalid characters.' */
|
|
43
|
+
title: string;
|
|
44
|
+
/** 'App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.' */
|
|
45
|
+
detail: string;
|
|
46
|
+
}[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureAppExistsAsync = exports.syncCapabilityIdentifiersAsync = exports.syncCapabilitiesAsync = exports.ensureBundleIdExistsWithNameAsync = exports.ensureBundleIdExistsAsync = void 0;
|
|
3
|
+
exports.isAppleError = exports.createAppAsync = exports.ensureAppExistsAsync = exports.syncCapabilityIdentifiersAsync = exports.syncCapabilitiesAsync = exports.ensureBundleIdExistsWithNameAsync = exports.ensureBundleIdExistsAsync = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const apple_utils_1 = require("@expo/apple-utils");
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
8
|
const authenticate_1 = require("./authenticate");
|
|
8
9
|
const bundleIdCapabilities_1 = require("./bundleIdCapabilities");
|
|
9
10
|
const capabilityIdentifiers_1 = require("./capabilityIdentifiers");
|
|
@@ -112,7 +113,7 @@ async function ensureAppExistsAsync(userAuthCtx, { name, language, companyName,
|
|
|
112
113
|
/**
|
|
113
114
|
* **Does not support App Store Connect API (CI).**
|
|
114
115
|
*/
|
|
115
|
-
app = await
|
|
116
|
+
app = await createAppAsync(context, {
|
|
116
117
|
bundleId: bundleIdentifier,
|
|
117
118
|
name,
|
|
118
119
|
primaryLocale: language,
|
|
@@ -137,3 +138,73 @@ async function ensureAppExistsAsync(userAuthCtx, { name, language, companyName,
|
|
|
137
138
|
return app;
|
|
138
139
|
}
|
|
139
140
|
exports.ensureAppExistsAsync = ensureAppExistsAsync;
|
|
141
|
+
function sanitizeName(name) {
|
|
142
|
+
return (name
|
|
143
|
+
// Replace emojis with a `-`
|
|
144
|
+
.replace(/[\p{Emoji}]/gu, '-')
|
|
145
|
+
.normalize('NFD')
|
|
146
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
147
|
+
.trim());
|
|
148
|
+
}
|
|
149
|
+
async function createAppAsync(context, props, retryCount = 0) {
|
|
150
|
+
try {
|
|
151
|
+
/**
|
|
152
|
+
* **Does not support App Store Connect API (CI).**
|
|
153
|
+
*/
|
|
154
|
+
return await apple_utils_1.App.createAsync(context, props);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (retryCount >= 5) {
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
if (error instanceof Error) {
|
|
161
|
+
const handleDuplicateNameErrorAsync = async () => {
|
|
162
|
+
const generatedName = props.name + ` (${(0, node_crypto_1.randomBytes)(3).toString('hex')})`;
|
|
163
|
+
log_1.default.warn(`App name "${props.name}" is already taken. Using generated name "${generatedName}" which can be changed later from https://appstoreconnect.apple.com.`);
|
|
164
|
+
// Sanitize the name and try again.
|
|
165
|
+
return await createAppAsync(context, {
|
|
166
|
+
...props,
|
|
167
|
+
name: generatedName,
|
|
168
|
+
}, retryCount + 1);
|
|
169
|
+
};
|
|
170
|
+
if (isAppleError(error)) {
|
|
171
|
+
// New error class that is thrown when the name is already taken but belongs to you.
|
|
172
|
+
if (error.data.errors.some(e => e.code === 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT' ||
|
|
173
|
+
e.code === 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.DIFFERENT_ACCOUNT')) {
|
|
174
|
+
return await handleDuplicateNameErrorAsync();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if ('code' in error && typeof error.code === 'string') {
|
|
178
|
+
if (
|
|
179
|
+
// Name is invalid
|
|
180
|
+
error.code === 'APP_CREATE_NAME_INVALID'
|
|
181
|
+
// UnexpectedAppleResponse: An attribute value has invalid characters. - App Name contains certain Unicode symbols, emoticons, diacritics, special characters, or private use characters that are not permitted.
|
|
182
|
+
// Name is taken
|
|
183
|
+
) {
|
|
184
|
+
const sanitizedName = sanitizeName(props.name);
|
|
185
|
+
if (sanitizedName === props.name) {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
log_1.default.warn(`App name "${props.name}" contains invalid characters. Using sanitized name "${sanitizedName}" which can be changed later from https://appstoreconnect.apple.com.`);
|
|
189
|
+
// Sanitize the name and try again.
|
|
190
|
+
return await createAppAsync(context, {
|
|
191
|
+
...props,
|
|
192
|
+
name: sanitizedName,
|
|
193
|
+
}, retryCount + 1);
|
|
194
|
+
}
|
|
195
|
+
if (
|
|
196
|
+
// UnexpectedAppleResponse: The provided entity includes an attribute with a value that has already been used on a different account. - The App Name you entered is already being used. If you have trademark rights to
|
|
197
|
+
// this name and would like it released for your use, submit a claim.
|
|
198
|
+
error.code === 'APP_CREATE_NAME_UNAVAILABLE') {
|
|
199
|
+
return await handleDuplicateNameErrorAsync();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
exports.createAppAsync = createAppAsync;
|
|
207
|
+
function isAppleError(error) {
|
|
208
|
+
return 'data' in error && 'errors' in error.data && Array.isArray(error.data.errors);
|
|
209
|
+
}
|
|
210
|
+
exports.isAppleError = isAppleError;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="@expo/apple-utils/ts-declarations/expo__app-store" />
|
|
2
|
+
import { App } from '@expo/apple-utils';
|
|
3
|
+
/**
|
|
4
|
+
* Ensure a TestFlight internal group with access to all builds exists for the app and has all admin users invited to it.
|
|
5
|
+
* This allows users to instantly access their builds from TestFlight after it finishes processing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ensureTestFlightGroupExistsAsync(app: App): Promise<void>;
|