@expo/eas-json 0.26.0 → 0.29.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.
@@ -1,3 +1,4 @@
1
+ import { Platform } from '@expo/eas-build-job';
1
2
  import { AndroidBuildProfile, CommonBuildProfile, IosBuildProfile } from './EasBuild.types';
2
3
  import { AndroidSubmitProfile, IosSubmitProfile } from './EasSubmit.types';
3
4
  export declare enum CredentialsSource {
@@ -6,12 +7,12 @@ export declare enum CredentialsSource {
6
7
  }
7
8
  export interface RawBuildProfile extends Partial<CommonBuildProfile> {
8
9
  extends?: string;
9
- android?: Partial<AndroidBuildProfile>;
10
- ios?: Partial<IosBuildProfile>;
10
+ [Platform.ANDROID]?: Partial<AndroidBuildProfile>;
11
+ [Platform.IOS]?: Partial<IosBuildProfile>;
11
12
  }
12
13
  export interface EasSubmitConfiguration {
13
- android?: AndroidSubmitProfile;
14
- ios?: IosSubmitProfile;
14
+ [Platform.ANDROID]?: AndroidSubmitProfile;
15
+ [Platform.IOS]?: IosSubmitProfile;
15
16
  }
16
17
  export interface EasJson {
17
18
  build: {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CredentialsSource = void 0;
4
+ const eas_build_job_1 = require("@expo/eas-build-job");
4
5
  var CredentialsSource;
5
6
  (function (CredentialsSource) {
6
7
  CredentialsSource["LOCAL"] = "local";
@@ -24,6 +24,7 @@ export declare class EasJsonReader {
24
24
  readRawAsync(): Promise<EasJsonPreValidation>;
25
25
  private resolveBuildProfile;
26
26
  private ensureBuildProfileExists;
27
+ private evaluateFields;
27
28
  }
28
29
  export declare function profileMerge(base: RawBuildProfile, update: RawBuildProfile): RawBuildProfile;
29
30
  export {};
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.profileMerge = exports.EasJsonReader = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const eas_build_job_1 = require("@expo/eas-build-job");
6
- const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
6
+ const json_file_1 = (0, tslib_1.__importDefault)(require("@expo/json-file"));
7
+ const env_string_1 = (0, tslib_1.__importDefault)(require("env-string"));
7
8
  const path_1 = (0, tslib_1.__importDefault)(require("path"));
8
9
  const EasJson_types_1 = require("./EasJson.types");
9
10
  const EasJsonSchema_1 = require("./EasJsonSchema");
11
+ const EasSubmit_types_1 = require("./EasSubmit.types");
10
12
  const defaults = {
11
13
  distribution: 'store',
12
14
  credentialsSource: EasJson_types_1.CredentialsSource.REMOTE,
@@ -55,7 +57,7 @@ class EasJsonReader {
55
57
  }
56
58
  }
57
59
  async readSubmitProfileAsync(platform, profileName) {
58
- var _a, _b;
60
+ var _a;
59
61
  if (!profileName) {
60
62
  const profileNames = await this.getSubmitProfileNamesAsync({
61
63
  throwIfEasJsonDoesNotExist: false,
@@ -68,11 +70,17 @@ class EasJsonReader {
68
70
  }
69
71
  }
70
72
  const easJson = await this.readAndValidateAsync();
71
- const profile = (_b = (_a = easJson === null || easJson === void 0 ? void 0 : easJson.submit) === null || _a === void 0 ? void 0 : _a[profileName]) === null || _b === void 0 ? void 0 : _b[platform];
73
+ const profile = (_a = easJson === null || easJson === void 0 ? void 0 : easJson.submit) === null || _a === void 0 ? void 0 : _a[profileName];
72
74
  if (!profile) {
73
- throw new Error(`There is no profile named ${profileName} in eas.json for ${platform}.`);
75
+ throw new Error(`There is no profile named ${profileName} in eas.json`);
76
+ }
77
+ const platformProfile = profile[platform];
78
+ if (platformProfile) {
79
+ return this.evaluateFields(platform, platformProfile);
80
+ }
81
+ else {
82
+ return getDefaultSubmitProfile(platform);
74
83
  }
75
- return profile;
76
84
  }
77
85
  async readAndValidateAsync() {
78
86
  const easJson = await this.readRawAsync();
@@ -87,13 +95,21 @@ class EasJsonReader {
87
95
  return value;
88
96
  }
89
97
  async readRawAsync() {
90
- const rawFile = await fs_extra_1.default.readFile(EasJsonReader.formatEasJsonPath(this.projectDir), 'utf8');
91
- const json = JSON.parse(rawFile);
92
- const { value, error } = EasJsonSchema_1.MinimalEasJsonSchema.validate(json, { abortEarly: false });
93
- if (error) {
94
- throw new Error(`eas.json is not valid [${error.toString()}]`);
98
+ try {
99
+ const easJsonPath = EasJsonReader.formatEasJsonPath(this.projectDir);
100
+ const rawEasJson = json_file_1.default.read(easJsonPath);
101
+ const { value, error } = EasJsonSchema_1.MinimalEasJsonSchema.validate(rawEasJson, { abortEarly: false });
102
+ if (error) {
103
+ throw new Error(`eas.json is not valid [${error.toString()}]`);
104
+ }
105
+ return value;
106
+ }
107
+ catch (err) {
108
+ if (err.code === 'EJSONPARSE') {
109
+ err.message = `Found invalid JSON in eas.json. ${err.message}`;
110
+ }
111
+ throw err;
95
112
  }
96
- return value;
97
113
  }
98
114
  resolveBuildProfile(easJson, profileName, depth = 0) {
99
115
  if (depth >= 2) {
@@ -101,7 +117,7 @@ class EasJsonReader {
101
117
  }
102
118
  const buildProfile = easJson.build[profileName];
103
119
  if (!buildProfile) {
104
- throw new Error(`There is no profile named ${profileName}`);
120
+ throw new Error(`There is no profile named ${profileName} in eas.json`);
105
121
  }
106
122
  const { extends: baseProfileName, ...buildProfileRest } = buildProfile;
107
123
  if (baseProfileName) {
@@ -113,8 +129,21 @@ class EasJsonReader {
113
129
  }
114
130
  ensureBuildProfileExists(easJson, profileName) {
115
131
  if (!easJson.build || !easJson.build[profileName]) {
116
- throw new Error(`There is no profile named ${profileName} in eas.json.`);
132
+ throw new Error(`There is no profile named ${profileName} in eas.json`);
133
+ }
134
+ }
135
+ evaluateFields(platform, profile) {
136
+ const fields = platform === eas_build_job_1.Platform.ANDROID
137
+ ? EasSubmit_types_1.AndroidSubmitProfileFieldsToEvaluate
138
+ : EasSubmit_types_1.IosSubmitProfileFieldsToEvaluate;
139
+ const evaluatedProfile = { ...profile };
140
+ for (const field of fields) {
141
+ if (field in evaluatedProfile) {
142
+ // @ts-ignore
143
+ evaluatedProfile[field] = (0, env_string_1.default)(evaluatedProfile[field], process.env);
144
+ }
117
145
  }
146
+ return evaluatedProfile;
118
147
  }
119
148
  }
120
149
  exports.EasJsonReader = EasJsonReader;
@@ -17,6 +17,7 @@ export interface AndroidSubmitProfile {
17
17
  releaseStatus: AndroidReleaseStatus;
18
18
  changesNotSentForReview: boolean;
19
19
  }
20
+ export declare const AndroidSubmitProfileFieldsToEvaluate: (keyof AndroidSubmitProfile)[];
20
21
  export interface IosSubmitProfile {
21
22
  appleId?: string;
22
23
  ascAppId?: string;
@@ -26,4 +27,5 @@ export interface IosSubmitProfile {
26
27
  companyName?: string;
27
28
  appName?: string;
28
29
  }
30
+ export declare const IosSubmitProfileFieldsToEvaluate: (keyof IosSubmitProfile)[];
29
31
  export declare type SubmitProfile<TPlatform extends Platform = Platform> = TPlatform extends Platform.ANDROID ? AndroidSubmitProfile : TPlatform extends Platform.IOS ? IosSubmitProfile : TPlatform extends Platform ? AndroidSubmitProfile | IosSubmitProfile : never;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AndroidReleaseTrack = exports.AndroidReleaseStatus = void 0;
3
+ exports.IosSubmitProfileFieldsToEvaluate = exports.AndroidSubmitProfileFieldsToEvaluate = exports.AndroidReleaseTrack = exports.AndroidReleaseStatus = void 0;
4
4
  var AndroidReleaseStatus;
5
5
  (function (AndroidReleaseStatus) {
6
6
  AndroidReleaseStatus["completed"] = "completed";
@@ -15,3 +15,7 @@ var AndroidReleaseTrack;
15
15
  AndroidReleaseTrack["alpha"] = "alpha";
16
16
  AndroidReleaseTrack["internal"] = "internal";
17
17
  })(AndroidReleaseTrack = exports.AndroidReleaseTrack || (exports.AndroidReleaseTrack = {}));
18
+ exports.AndroidSubmitProfileFieldsToEvaluate = [
19
+ 'serviceAccountKeyPath',
20
+ ];
21
+ exports.IosSubmitProfileFieldsToEvaluate = [];
package/build/migrate.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.migrateProfile = exports.migrateAsync = exports.hasMismatchedExtendsAsync = exports.isUsingDeprecatedFormatAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const eas_build_job_1 = require("@expo/eas-build-job");
6
+ const json_file_1 = (0, tslib_1.__importDefault)(require("@expo/json-file"));
6
7
  const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
7
8
  const path_1 = (0, tslib_1.__importDefault)(require("path"));
8
9
  const DeprecatedEasJsonReader_1 = require("./DeprecatedEasJsonReader");
@@ -11,15 +12,13 @@ async function isUsingDeprecatedFormatAsync(projectDir) {
11
12
  if (!(await fs_extra_1.default.pathExists(easJsonPath))) {
12
13
  return false;
13
14
  }
14
- const rawFile = await fs_extra_1.default.readFile(easJsonPath, 'utf8');
15
- const json = JSON.parse(rawFile);
15
+ const json = await readEasJsonAsync(projectDir);
16
16
  return !!(json === null || json === void 0 ? void 0 : json.builds);
17
17
  }
18
18
  exports.isUsingDeprecatedFormatAsync = isUsingDeprecatedFormatAsync;
19
19
  async function hasMismatchedExtendsAsync(projectDir) {
20
20
  var _a, _b;
21
- const rawFile = await fs_extra_1.default.readFile(path_1.default.join(projectDir, 'eas.json'), 'utf8');
22
- const rawEasJson = JSON.parse(rawFile);
21
+ const rawEasJson = (await readEasJsonAsync(projectDir));
23
22
  const profiles = new Set();
24
23
  Object.keys((_a = rawEasJson.builds.android) !== null && _a !== void 0 ? _a : {}).forEach(profile => profiles.add(profile));
25
24
  Object.keys((_b = rawEasJson.builds.ios) !== null && _b !== void 0 ? _b : {}).forEach(profile => profiles.add(profile));
@@ -43,8 +42,7 @@ async function migrateAsync(projectDir) {
43
42
  catch (err) {
44
43
  throw new Error(`Valid eas.json is required to migrate to the new format\n${err.message}`);
45
44
  }
46
- const rawFile = await fs_extra_1.default.readFile(path_1.default.join(projectDir, 'eas.json'), 'utf8');
47
- const rawEasJson = JSON.parse(rawFile);
45
+ const rawEasJson = (await readEasJsonAsync(projectDir));
48
46
  const profiles = new Set();
49
47
  Object.keys((_a = rawEasJson.builds.android) !== null && _a !== void 0 ? _a : {}).forEach(profile => profiles.add(profile));
50
48
  Object.keys((_b = rawEasJson.builds.ios) !== null && _b !== void 0 ? _b : {}).forEach(profile => profiles.add(profile));
@@ -57,6 +55,18 @@ async function migrateAsync(projectDir) {
57
55
  await fs_extra_1.default.writeFile(path_1.default.join(projectDir, 'eas.json'), `${JSON.stringify(result, null, 2)}\n`);
58
56
  }
59
57
  exports.migrateAsync = migrateAsync;
58
+ async function readEasJsonAsync(projectDir) {
59
+ try {
60
+ const easJsonPath = path_1.default.join(projectDir, 'eas.json');
61
+ return json_file_1.default.read(easJsonPath);
62
+ }
63
+ catch (err) {
64
+ if (err.code === 'EJSONPARSE') {
65
+ err.message = `Found invalid JSON in eas.json. ${err.message}`;
66
+ }
67
+ throw err;
68
+ }
69
+ }
60
70
  function migrateProfile(rawEasJson, profileName) {
61
71
  var _a, _b, _c, _d, _e, _f, _g, _h;
62
72
  const androidProfile = ((_c = (_b = (_a = rawEasJson === null || rawEasJson === void 0 ? void 0 : rawEasJson.builds) === null || _a === void 0 ? void 0 : _a.android) === null || _b === void 0 ? void 0 : _b[profileName]) !== null && _c !== void 0 ? _c : {});
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@expo/eas-json",
3
3
  "description": "A library for interacting with the eas.json",
4
- "version": "0.26.0",
4
+ "version": "0.29.0",
5
5
  "author": "Expo <support@expo.dev>",
6
6
  "bugs": "https://github.com/expo/eas-cli/issues",
7
7
  "dependencies": {
8
- "@expo/eas-build-job": "0.2.46",
8
+ "@expo/eas-build-job": "0.2.48",
9
+ "@expo/json-file": "8.2.33",
10
+ "env-string": "1.0.1",
9
11
  "fs-extra": "10.0.0",
10
12
  "joi": "17.4.2",
11
13
  "tslib": "2.3.1"
@@ -31,5 +33,5 @@
31
33
  "publishConfig": {
32
34
  "access": "public"
33
35
  },
34
- "gitHead": "0c94f80f68700d6fb0dbe40206afac0c62d2b5ad"
36
+ "gitHead": "6e1c7dd91e4d6ba45dca2744bd9c95fc32bed62d"
35
37
  }
@@ -1,7 +1,7 @@
1
1
  import { Android, Ios } from '@expo/eas-build-job';
2
2
  import Joi, { CustomHelpers } from 'joi';
3
3
 
4
- const semverSchemaCheck = (value: any, helpers: CustomHelpers) => {
4
+ const semverSchemaCheck = (value: any, helpers: CustomHelpers): any => {
5
5
  if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(value)) {
6
6
  return value;
7
7
  } else {
@@ -1,3 +1,5 @@
1
+ import { Platform } from '@expo/eas-build-job';
2
+
1
3
  import { AndroidBuildProfile, CommonBuildProfile, IosBuildProfile } from './EasBuild.types';
2
4
  import { AndroidSubmitProfile, IosSubmitProfile } from './EasSubmit.types';
3
5
 
@@ -8,13 +10,13 @@ export enum CredentialsSource {
8
10
 
9
11
  export interface RawBuildProfile extends Partial<CommonBuildProfile> {
10
12
  extends?: string;
11
- android?: Partial<AndroidBuildProfile>;
12
- ios?: Partial<IosBuildProfile>;
13
+ [Platform.ANDROID]?: Partial<AndroidBuildProfile>;
14
+ [Platform.IOS]?: Partial<IosBuildProfile>;
13
15
  }
14
16
 
15
17
  export interface EasSubmitConfiguration {
16
- android?: AndroidSubmitProfile;
17
- ios?: IosSubmitProfile;
18
+ [Platform.ANDROID]?: AndroidSubmitProfile;
19
+ [Platform.IOS]?: IosSubmitProfile;
18
20
  }
19
21
 
20
22
  export interface EasJson {
@@ -1,5 +1,6 @@
1
1
  import { Platform } from '@expo/eas-build-job';
2
- import fs from 'fs-extra';
2
+ import JsonFile from '@expo/json-file';
3
+ import envString from 'env-string';
3
4
  import path from 'path';
4
5
 
5
6
  import { BuildProfile } from './EasBuild.types';
@@ -10,7 +11,11 @@ import {
10
11
  IosSubmitProfileSchema,
11
12
  MinimalEasJsonSchema,
12
13
  } from './EasJsonSchema';
13
- import { SubmitProfile } from './EasSubmit.types';
14
+ import {
15
+ AndroidSubmitProfileFieldsToEvaluate,
16
+ IosSubmitProfileFieldsToEvaluate,
17
+ SubmitProfile,
18
+ } from './EasSubmit.types';
14
19
 
15
20
  interface EasJsonPreValidation {
16
21
  build: { [profile: string]: object };
@@ -23,7 +28,7 @@ const defaults = {
23
28
  } as const;
24
29
 
25
30
  export class EasJsonReader {
26
- public static formatEasJsonPath(projectDir: string) {
31
+ public static formatEasJsonPath(projectDir: string): string {
27
32
  return path.join(projectDir, 'eas.json');
28
33
  }
29
34
 
@@ -89,11 +94,16 @@ export class EasJsonReader {
89
94
  }
90
95
  }
91
96
  const easJson = await this.readAndValidateAsync();
92
- const profile = easJson?.submit?.[profileName]?.[platform];
97
+ const profile = easJson?.submit?.[profileName];
93
98
  if (!profile) {
94
- throw new Error(`There is no profile named ${profileName} in eas.json for ${platform}.`);
99
+ throw new Error(`There is no profile named ${profileName} in eas.json`);
100
+ }
101
+ const platformProfile = profile[platform];
102
+ if (platformProfile) {
103
+ return this.evaluateFields(platform, platformProfile as SubmitProfile<T>);
104
+ } else {
105
+ return getDefaultSubmitProfile(platform);
95
106
  }
96
- return profile as SubmitProfile<T>;
97
107
  }
98
108
 
99
109
  public async readAndValidateAsync(): Promise<EasJson> {
@@ -111,14 +121,20 @@ export class EasJsonReader {
111
121
  }
112
122
 
113
123
  public async readRawAsync(): Promise<EasJsonPreValidation> {
114
- const rawFile = await fs.readFile(EasJsonReader.formatEasJsonPath(this.projectDir), 'utf8');
115
- const json = JSON.parse(rawFile);
116
-
117
- const { value, error } = MinimalEasJsonSchema.validate(json, { abortEarly: false });
118
- if (error) {
119
- throw new Error(`eas.json is not valid [${error.toString()}]`);
124
+ try {
125
+ const easJsonPath = EasJsonReader.formatEasJsonPath(this.projectDir);
126
+ const rawEasJson = JsonFile.read(easJsonPath);
127
+ const { value, error } = MinimalEasJsonSchema.validate(rawEasJson, { abortEarly: false });
128
+ if (error) {
129
+ throw new Error(`eas.json is not valid [${error.toString()}]`);
130
+ }
131
+ return value;
132
+ } catch (err: any) {
133
+ if (err.code === 'EJSONPARSE') {
134
+ err.message = `Found invalid JSON in eas.json. ${err.message}`;
135
+ }
136
+ throw err;
120
137
  }
121
- return value;
122
138
  }
123
139
 
124
140
  private resolveBuildProfile(
@@ -133,7 +149,7 @@ export class EasJsonReader {
133
149
  }
134
150
  const buildProfile = easJson.build[profileName];
135
151
  if (!buildProfile) {
136
- throw new Error(`There is no profile named ${profileName}`);
152
+ throw new Error(`There is no profile named ${profileName} in eas.json`);
137
153
  }
138
154
  const { extends: baseProfileName, ...buildProfileRest } = buildProfile;
139
155
  if (baseProfileName) {
@@ -146,10 +162,28 @@ export class EasJsonReader {
146
162
  }
147
163
  }
148
164
 
149
- private ensureBuildProfileExists(easJson: EasJson, profileName: string) {
165
+ private ensureBuildProfileExists(easJson: EasJson, profileName: string): void {
150
166
  if (!easJson.build || !easJson.build[profileName]) {
151
- throw new Error(`There is no profile named ${profileName} in eas.json.`);
167
+ throw new Error(`There is no profile named ${profileName} in eas.json`);
168
+ }
169
+ }
170
+
171
+ private evaluateFields<T extends Platform>(
172
+ platform: T,
173
+ profile: SubmitProfile<T>
174
+ ): SubmitProfile<T> {
175
+ const fields =
176
+ platform === Platform.ANDROID
177
+ ? AndroidSubmitProfileFieldsToEvaluate
178
+ : IosSubmitProfileFieldsToEvaluate;
179
+ const evaluatedProfile = { ...profile };
180
+ for (const field of fields) {
181
+ if (field in evaluatedProfile) {
182
+ // @ts-ignore
183
+ evaluatedProfile[field] = envString(evaluatedProfile[field], process.env);
184
+ }
152
185
  }
186
+ return evaluatedProfile;
153
187
  }
154
188
  }
155
189
 
@@ -3,7 +3,7 @@ import Joi from 'joi';
3
3
 
4
4
  import { AndroidReleaseStatus, AndroidReleaseTrack } from './EasSubmit.types';
5
5
 
6
- const semverSchemaCheck = (value: any) => {
6
+ const semverSchemaCheck = (value: any): any => {
7
7
  if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(value)) {
8
8
  return value;
9
9
  } else {
@@ -21,6 +21,10 @@ export interface AndroidSubmitProfile {
21
21
  changesNotSentForReview: boolean;
22
22
  }
23
23
 
24
+ export const AndroidSubmitProfileFieldsToEvaluate: (keyof AndroidSubmitProfile)[] = [
25
+ 'serviceAccountKeyPath',
26
+ ];
27
+
24
28
  export interface IosSubmitProfile {
25
29
  appleId?: string;
26
30
  ascAppId?: string;
@@ -31,6 +35,8 @@ export interface IosSubmitProfile {
31
35
  appName?: string;
32
36
  }
33
37
 
38
+ export const IosSubmitProfileFieldsToEvaluate: (keyof IosSubmitProfile)[] = [];
39
+
34
40
  export type SubmitProfile<TPlatform extends Platform = Platform> =
35
41
  TPlatform extends Platform.ANDROID
36
42
  ? AndroidSubmitProfile
@@ -225,7 +225,7 @@ test('valid eas.json with missing profile', async () => {
225
225
 
226
226
  const reader = new EasJsonReader('/project');
227
227
  const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'debug');
228
- await expect(promise).rejects.toThrowError('There is no profile named debug in eas.json.');
228
+ await expect(promise).rejects.toThrowError('There is no profile named debug in eas.json');
229
229
  });
230
230
 
231
231
  test('invalid eas.json when using wrong buildType', async () => {
@@ -247,7 +247,7 @@ test('empty json', async () => {
247
247
 
248
248
  const reader = new EasJsonReader('/project');
249
249
  const promise = reader.readBuildProfileAsync(Platform.ANDROID, 'release');
250
- await expect(promise).rejects.toThrowError('There is no profile named release in eas.json.');
250
+ await expect(promise).rejects.toThrowError('There is no profile named release in eas.json');
251
251
  });
252
252
 
253
253
  test('invalid semver value', async () => {
@@ -14,10 +14,7 @@ beforeEach(async () => {
14
14
  test('minimal allowed eas.json for both platforms', async () => {
15
15
  await fs.writeJson('/project/eas.json', {
16
16
  submit: {
17
- release: {
18
- android: {},
19
- ios: {},
20
- },
17
+ release: {},
21
18
  },
22
19
  });
23
20
 
@@ -59,6 +56,35 @@ test('android config with all required values', async () => {
59
56
  });
60
57
  });
61
58
 
59
+ test('android config with serviceAccountKeyPath set to env var', async () => {
60
+ await fs.writeJson('/project/eas.json', {
61
+ submit: {
62
+ release: {
63
+ android: {
64
+ serviceAccountKeyPath: '$GOOGLE_SERVICE_ACCOUNT',
65
+ track: 'beta',
66
+ releaseStatus: 'completed',
67
+ },
68
+ },
69
+ },
70
+ });
71
+
72
+ try {
73
+ process.env.GOOGLE_SERVICE_ACCOUNT = './path.json';
74
+ const reader = new EasJsonReader('/project');
75
+ const androidProfile = await reader.readSubmitProfileAsync(Platform.ANDROID, 'release');
76
+
77
+ expect(androidProfile).toEqual({
78
+ serviceAccountKeyPath: './path.json',
79
+ track: 'beta',
80
+ releaseStatus: 'completed',
81
+ changesNotSentForReview: false,
82
+ });
83
+ } finally {
84
+ process.env.GOOGLE_SERVICE_ACCOUNT = undefined;
85
+ }
86
+ });
87
+
62
88
  test('ios config with all required values', async () => {
63
89
  await fs.writeJson('/project/eas.json', {
64
90
  submit: {
@@ -82,22 +108,3 @@ test('ios config with all required values', async () => {
82
108
  language: 'en-US',
83
109
  });
84
110
  });
85
-
86
- test('missing ios profile', async () => {
87
- await fs.writeJson('/project/eas.json', {
88
- submit: {
89
- release: {
90
- android: {
91
- serviceAccountKeyPath: './path.json',
92
- track: 'beta',
93
- releaseStatus: 'completed',
94
- },
95
- },
96
- },
97
- });
98
-
99
- const reader = new EasJsonReader('/project');
100
- const promise = reader.readSubmitProfileAsync(Platform.IOS, 'release');
101
-
102
- expect(promise).rejects.toThrow('There is no profile named release in eas.json for ios.');
103
- });
package/src/migrate.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Android, Ios } from '@expo/eas-build-job';
2
+ import JsonFile, { JSONObject } from '@expo/json-file';
2
3
  import fs from 'fs-extra';
3
4
  import path from 'path';
4
5
 
@@ -11,14 +12,12 @@ export async function isUsingDeprecatedFormatAsync(projectDir: string): Promise<
11
12
  if (!(await fs.pathExists(easJsonPath))) {
12
13
  return false;
13
14
  }
14
- const rawFile = await fs.readFile(easJsonPath, 'utf8');
15
- const json = JSON.parse(rawFile);
15
+ const json = await readEasJsonAsync(projectDir);
16
16
  return !!json?.builds;
17
17
  }
18
18
 
19
19
  export async function hasMismatchedExtendsAsync(projectDir: string): Promise<boolean> {
20
- const rawFile = await fs.readFile(path.join(projectDir, 'eas.json'), 'utf8');
21
- const rawEasJson = JSON.parse(rawFile) as DeprecatedEasJson;
20
+ const rawEasJson = (await readEasJsonAsync(projectDir)) as unknown as DeprecatedEasJson;
22
21
  const profiles = new Set<string>();
23
22
  Object.keys(rawEasJson.builds.android ?? {}).forEach(profile => profiles.add(profile));
24
23
  Object.keys(rawEasJson.builds.ios ?? {}).forEach(profile => profiles.add(profile));
@@ -42,8 +41,7 @@ export async function migrateAsync(projectDir: string): Promise<void> {
42
41
  } catch (err: any) {
43
42
  throw new Error(`Valid eas.json is required to migrate to the new format\n${err.message}`);
44
43
  }
45
- const rawFile = await fs.readFile(path.join(projectDir, 'eas.json'), 'utf8');
46
- const rawEasJson = JSON.parse(rawFile) as DeprecatedEasJson;
44
+ const rawEasJson = (await readEasJsonAsync(projectDir)) as unknown as DeprecatedEasJson;
47
45
  const profiles = new Set<string>();
48
46
  Object.keys(rawEasJson.builds.android ?? {}).forEach(profile => profiles.add(profile));
49
47
  Object.keys(rawEasJson.builds.ios ?? {}).forEach(profile => profiles.add(profile));
@@ -57,6 +55,18 @@ export async function migrateAsync(projectDir: string): Promise<void> {
57
55
  await fs.writeFile(path.join(projectDir, 'eas.json'), `${JSON.stringify(result, null, 2)}\n`);
58
56
  }
59
57
 
58
+ async function readEasJsonAsync(projectDir: string): Promise<JSONObject> {
59
+ try {
60
+ const easJsonPath = path.join(projectDir, 'eas.json');
61
+ return JsonFile.read(easJsonPath);
62
+ } catch (err: any) {
63
+ if (err.code === 'EJSONPARSE') {
64
+ err.message = `Found invalid JSON in eas.json. ${err.message}`;
65
+ }
66
+ throw err;
67
+ }
68
+ }
69
+
60
70
  interface MigrateContext {
61
71
  androidProfile?: Partial<AndroidBuildProfile>;
62
72
  iosProfile?: Partial<IosBuildProfile>;