@expo/eas-json
Advanced tools
Comparing version 0.22.1 to 0.22.2
@@ -1,34 +0,20 @@ | ||
import { Workflow } from '@expo/eas-build-job'; | ||
import { EasConfig } from './Config.types'; | ||
interface EasJson { | ||
builds: { | ||
android?: { | ||
[key: string]: BuildProfilePreValidation; | ||
}; | ||
ios?: { | ||
[key: string]: BuildProfilePreValidation; | ||
}; | ||
import { Platform } from '@expo/eas-build-job'; | ||
import { BuildProfile, EasJson, RawBuildProfile } from './EasJson.types'; | ||
interface EasJsonPreValidation { | ||
build: { | ||
[profile: string]: object; | ||
}; | ||
} | ||
interface BuildProfilePreValidation { | ||
workflow?: Workflow; | ||
extends?: string; | ||
} | ||
export declare class EasJsonReader { | ||
private projectDir; | ||
private platform; | ||
static formatEasJsonPath(projectDir: string): string; | ||
constructor(projectDir: string, platform: 'android' | 'ios' | 'all'); | ||
/** | ||
* Return build profile names for a particular platform. | ||
* If platform is 'all', return common build profiles for all platforms | ||
*/ | ||
constructor(projectDir: string); | ||
getBuildProfileNamesAsync(): Promise<string[]>; | ||
readAsync(buildProfileName: string): Promise<EasConfig>; | ||
validateAsync(): Promise<void>; | ||
readRawAsync(): Promise<EasJson>; | ||
private validateBuildProfile; | ||
readBuildProfileAsync<T extends Platform>(buildProfileName: string, platform: T): Promise<BuildProfile<T>>; | ||
readAndValidateAsync(): Promise<EasJson>; | ||
readRawAsync(): Promise<EasJsonPreValidation>; | ||
private resolveBuildProfile; | ||
private ensureProfileExists; | ||
} | ||
export declare function deepMerge(base: Record<string, any>, update: Record<string, any>): Record<string, any>; | ||
export declare function profileMerge(base: RawBuildProfile, update: RawBuildProfile): RawBuildProfile; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.deepMerge = exports.EasJsonReader = void 0; | ||
exports.profileMerge = exports.EasJsonReader = void 0; | ||
const tslib_1 = require("tslib"); | ||
@@ -8,10 +8,11 @@ const eas_build_job_1 = require("@expo/eas-build-job"); | ||
const path_1 = tslib_1.__importDefault(require("path")); | ||
const EasJson_types_1 = require("./EasJson.types"); | ||
const EasJsonSchema_1 = require("./EasJsonSchema"); | ||
function intersect(setA, setB) { | ||
return new Set([...setA].filter(i => setB.has(i))); | ||
} | ||
const defaults = { | ||
distribution: 'store', | ||
credentialsSource: EasJson_types_1.CredentialsSource.REMOTE, | ||
}; | ||
class EasJsonReader { | ||
constructor(projectDir, platform) { | ||
constructor(projectDir) { | ||
this.projectDir = projectDir; | ||
this.platform = platform; | ||
} | ||
@@ -21,63 +22,28 @@ static formatEasJsonPath(projectDir) { | ||
} | ||
/** | ||
* Return build profile names for a particular platform. | ||
* If platform is 'all', return common build profiles for all platforms | ||
*/ | ||
async getBuildProfileNamesAsync() { | ||
var _a, _b, _c, _d, _e, _f, _g, _h; | ||
var _a; | ||
const easJson = await this.readRawAsync(); | ||
if (this.platform === 'android') { | ||
return Object.keys((_b = (_a = easJson === null || easJson === void 0 ? void 0 : easJson.builds) === null || _a === void 0 ? void 0 : _a.android) !== null && _b !== void 0 ? _b : {}); | ||
return Object.keys((_a = easJson === null || easJson === void 0 ? void 0 : easJson.build) !== null && _a !== void 0 ? _a : {}); | ||
} | ||
async readBuildProfileAsync(buildProfileName, platform) { | ||
const easJson = await this.readAndValidateAsync(); | ||
this.ensureProfileExists(easJson, buildProfileName); | ||
const _a = this.resolveBuildProfile(easJson, buildProfileName), { android: resolvedAndroidSpecificValues, ios: resolvedIosSpecificValues } = _a, resolvedProfile = tslib_1.__rest(_a, ["android", "ios"]); | ||
if (platform === eas_build_job_1.Platform.ANDROID) { | ||
const profileWithoutDefaults = profileMerge(resolvedProfile, resolvedAndroidSpecificValues !== null && resolvedAndroidSpecificValues !== void 0 ? resolvedAndroidSpecificValues : {}); | ||
return profileMerge(defaults, profileWithoutDefaults); | ||
} | ||
else if (this.platform === 'ios') { | ||
return Object.keys((_d = (_c = easJson === null || easJson === void 0 ? void 0 : easJson.builds) === null || _c === void 0 ? void 0 : _c.ios) !== null && _d !== void 0 ? _d : {}); | ||
else if (platform === eas_build_job_1.Platform.IOS) { | ||
const profileWithoutDefaults = profileMerge(resolvedProfile, resolvedIosSpecificValues !== null && resolvedIosSpecificValues !== void 0 ? resolvedIosSpecificValues : {}); | ||
return profileMerge(defaults, profileWithoutDefaults); | ||
} | ||
else { | ||
const intersectingProfileNames = intersect(new Set(Object.keys((_f = (_e = easJson === null || easJson === void 0 ? void 0 : easJson.builds) === null || _e === void 0 ? void 0 : _e.ios) !== null && _f !== void 0 ? _f : {})), new Set(Object.keys((_h = (_g = easJson === null || easJson === void 0 ? void 0 : easJson.builds) === null || _g === void 0 ? void 0 : _g.android) !== null && _h !== void 0 ? _h : {}))); | ||
return Array.from(intersectingProfileNames); | ||
throw new Error(`Unknown platform ${platform}`); | ||
} | ||
} | ||
async readAsync(buildProfileName) { | ||
var _a, _b; | ||
async readAndValidateAsync() { | ||
const easJson = await this.readRawAsync(); | ||
let androidConfig; | ||
if (['android', 'all'].includes(this.platform)) { | ||
androidConfig = this.validateBuildProfile(eas_build_job_1.Platform.ANDROID, buildProfileName, ((_a = easJson.builds) === null || _a === void 0 ? void 0 : _a.android) || {}); | ||
} | ||
let iosConfig; | ||
if (['ios', 'all'].includes(this.platform)) { | ||
iosConfig = this.validateBuildProfile(eas_build_job_1.Platform.IOS, buildProfileName, ((_b = easJson.builds) === null || _b === void 0 ? void 0 : _b.ios) || {}); | ||
} | ||
return { | ||
builds: Object.assign(Object.assign({}, (androidConfig ? { android: androidConfig } : {})), (iosConfig ? { ios: iosConfig } : {})), | ||
}; | ||
} | ||
async validateAsync() { | ||
var _a, _b, _c, _d; | ||
const easJson = await this.readRawAsync(); | ||
const androidProfiles = (_b = (_a = easJson.builds) === null || _a === void 0 ? void 0 : _a.android) !== null && _b !== void 0 ? _b : {}; | ||
for (const name of Object.keys(androidProfiles)) { | ||
try { | ||
this.validateBuildProfile(eas_build_job_1.Platform.ANDROID, name, androidProfiles); | ||
} | ||
catch (err) { | ||
err.msg = `Failed to validate Android build profile "${name}"\n${err.msg}`; | ||
throw err; | ||
} | ||
} | ||
const iosProfiles = (_d = (_c = easJson.builds) === null || _c === void 0 ? void 0 : _c.ios) !== null && _d !== void 0 ? _d : {}; | ||
for (const name of Object.keys(iosProfiles)) { | ||
try { | ||
this.validateBuildProfile(eas_build_job_1.Platform.IOS, name, iosProfiles); | ||
} | ||
catch (err) { | ||
err.msg = `Failed to validate iOS build profile "${name}"\n${err.msg}`; | ||
throw err; | ||
} | ||
} | ||
} | ||
async readRawAsync() { | ||
const rawFile = await fs_extra_1.default.readFile(EasJsonReader.formatEasJsonPath(this.projectDir), 'utf8'); | ||
const json = JSON.parse(rawFile); | ||
const { value, error } = EasJsonSchema_1.EasJsonSchema.validate(json, { | ||
const { value, error } = EasJsonSchema_1.EasJsonSchema.validate(easJson, { | ||
allowUnknown: false, | ||
convert: true, | ||
abortEarly: false, | ||
@@ -90,26 +56,24 @@ }); | ||
} | ||
validateBuildProfile(platform, buildProfileName, buildProfiles) { | ||
const buildProfile = this.resolveBuildProfile(platform, buildProfileName, buildProfiles); | ||
const schema = EasJsonSchema_1.schemaBuildProfileMap[platform]; | ||
const { value, error } = schema.validate(buildProfile, { | ||
stripUnknown: true, | ||
convert: true, | ||
async readRawAsync() { | ||
const rawFile = await fs_extra_1.default.readFile(EasJsonReader.formatEasJsonPath(this.projectDir), 'utf8'); | ||
const json = JSON.parse(rawFile); | ||
const { value, error } = EasJsonSchema_1.MinimalEasJsonSchema.validate(json, { | ||
abortEarly: false, | ||
}); | ||
if (error) { | ||
throw new Error(`Object "${platform}.${buildProfileName}" in eas.json is not valid [${error.toString()}]`); | ||
throw new Error(`eas.json is not valid [${error.toString()}]`); | ||
} | ||
return value; | ||
} | ||
resolveBuildProfile(platform, buildProfileName, buildProfiles, depth = 0) { | ||
resolveBuildProfile(easJson, profileName, depth = 0) { | ||
if (depth >= 2) { | ||
throw new Error('Too long chain of build profile extensions, make sure "extends" keys do not make a cycle'); | ||
} | ||
const buildProfile = buildProfiles[buildProfileName]; | ||
const buildProfile = easJson.build[profileName]; | ||
if (!buildProfile) { | ||
throw new Error(`There is no profile named ${buildProfileName} for platform ${platform}`); | ||
throw new Error(`There is no profile named ${profileName}`); | ||
} | ||
const { extends: baseProfileName } = buildProfile, buildProfileRest = tslib_1.__rest(buildProfile, ["extends"]); | ||
if (baseProfileName) { | ||
return deepMerge(this.resolveBuildProfile(platform, baseProfileName, buildProfiles, depth + 1), buildProfileRest); | ||
return profileMerge(this.resolveBuildProfile(easJson, baseProfileName, depth + 1), buildProfileRest); | ||
} | ||
@@ -120,30 +84,22 @@ else { | ||
} | ||
ensureProfileExists(easJson, profileName) { | ||
if (!easJson.build || !easJson.build[profileName]) { | ||
throw new Error(`There is no profile named ${profileName} in eas.json.`); | ||
} | ||
} | ||
} | ||
exports.EasJsonReader = EasJsonReader; | ||
function isObject(value) { | ||
return typeof value === 'object' && value !== null; | ||
} | ||
function deepMerge(base, update) { | ||
const result = {}; | ||
Object.keys(base).forEach(key => { | ||
const oldValue = base[key]; | ||
const newValue = update[key]; | ||
if (isObject(newValue) && isObject(oldValue)) { | ||
result[key] = deepMerge(oldValue, newValue); | ||
} | ||
else if (newValue !== undefined) { | ||
result[key] = isObject(newValue) ? deepMerge({}, newValue) : newValue; | ||
} | ||
else { | ||
result[key] = isObject(oldValue) ? deepMerge({}, oldValue) : oldValue; | ||
} | ||
}); | ||
Object.keys(update).forEach(key => { | ||
const newValue = update[key]; | ||
if (result[key] === undefined) { | ||
result[key] = isObject(newValue) ? deepMerge({}, newValue) : newValue; | ||
} | ||
}); | ||
function profileMerge(base, update) { | ||
const result = Object.assign(Object.assign({}, base), update); | ||
if (base.env && update.env) { | ||
result.env = Object.assign(Object.assign({}, base.env), update.env); | ||
} | ||
if (base.android && update.android) { | ||
result.android = profileMerge(base.android, update.android); | ||
} | ||
if (base.ios && update.ios) { | ||
result.ios = profileMerge(base.ios, update.ios); | ||
} | ||
return result; | ||
} | ||
exports.deepMerge = deepMerge; | ||
exports.profileMerge = profileMerge; |
/// <reference types="hapi__joi" /> | ||
import Joi from '@hapi/joi'; | ||
export declare const schemaBuildProfileMap: Record<string, Joi.Schema>; | ||
export declare const MinimalEasJsonSchema: Joi.ObjectSchema<any>; | ||
export declare const EasJsonSchema: Joi.ObjectSchema<any>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.EasJsonSchema = exports.schemaBuildProfileMap = void 0; | ||
exports.EasJsonSchema = exports.MinimalEasJsonSchema = void 0; | ||
const tslib_1 = require("tslib"); | ||
const eas_build_job_1 = require("@expo/eas-build-job"); | ||
const joi_1 = tslib_1.__importDefault(require("@hapi/joi")); | ||
const semverSchemaCheck = (value, helpers) => { | ||
const semverSchemaCheck = (value) => { | ||
if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(value)) { | ||
@@ -15,71 +15,49 @@ return value; | ||
}; | ||
const AndroidBuilderEnvironmentSchema = joi_1.default.object({ | ||
image: joi_1.default.string() | ||
.valid(...eas_build_job_1.Android.builderBaseImages) | ||
.default('default'), | ||
const CacheSchema = joi_1.default.object({ | ||
disabled: joi_1.default.boolean(), | ||
key: joi_1.default.string().max(128), | ||
cacheDefaultPaths: joi_1.default.boolean(), | ||
customPaths: joi_1.default.array().items(joi_1.default.string()), | ||
}); | ||
const CommonBuildProfileSchema = joi_1.default.object({ | ||
credentialsSource: joi_1.default.string().valid('local', 'remote'), | ||
distribution: joi_1.default.string().valid('store', 'internal'), | ||
cache: CacheSchema, | ||
releaseChannel: joi_1.default.string(), | ||
channel: joi_1.default.string(), | ||
developmentClient: joi_1.default.boolean(), | ||
node: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
yarn: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
ndk: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
expoCli: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
env: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.string().empty(null)).default({}), | ||
env: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.string().empty(null)), | ||
}); | ||
const IosBuilderEnvironmentSchema = joi_1.default.object({ | ||
const AndroidBuildProfileSchema = CommonBuildProfileSchema.concat(joi_1.default.object({ | ||
withoutCredentials: joi_1.default.boolean(), | ||
image: joi_1.default.string().valid(...eas_build_job_1.Android.builderBaseImages), | ||
ndk: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
artifactPath: joi_1.default.string(), | ||
gradleCommand: joi_1.default.string(), | ||
buildType: joi_1.default.string().valid('apk', 'app-bundle'), | ||
})); | ||
const IosBuildProfileSchema = CommonBuildProfileSchema.concat(joi_1.default.object({ | ||
enterpriseProvisioning: joi_1.default.string().valid('adhoc', 'universal'), | ||
autoIncrement: joi_1.default.alternatives().try(joi_1.default.boolean(), joi_1.default.string().valid('version', 'buildNumber')), | ||
image: joi_1.default.string().valid(...eas_build_job_1.Ios.builderBaseImages), | ||
node: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
yarn: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
bundler: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
fastlane: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
cocoapods: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
expoCli: joi_1.default.string().empty(null).custom(semverSchemaCheck), | ||
env: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.string().empty(null)).default({}), | ||
}); | ||
const CacheSchema = joi_1.default.object({ | ||
disabled: joi_1.default.boolean().default(false), | ||
key: joi_1.default.string().max(128), | ||
cacheDefaultPaths: joi_1.default.boolean().default(true), | ||
customPaths: joi_1.default.array().items(joi_1.default.string()).default([]), | ||
}); | ||
const AndroidSchema = joi_1.default.object({ | ||
workflow: joi_1.default.string(), | ||
credentialsSource: joi_1.default.string().valid('local', 'remote').default('remote'), | ||
releaseChannel: joi_1.default.string(), | ||
channel: joi_1.default.string(), | ||
distribution: joi_1.default.string().valid('store', 'internal').default('store'), | ||
cache: CacheSchema.default(), | ||
withoutCredentials: joi_1.default.boolean().default(false), | ||
artifactPath: joi_1.default.string(), | ||
gradleCommand: joi_1.default.string(), | ||
buildType: joi_1.default.alternatives().conditional('distribution', { | ||
is: 'internal', | ||
then: joi_1.default.string().valid('apk', 'development-client'), | ||
otherwise: joi_1.default.string().valid('apk', 'app-bundle', 'development-client'), | ||
}), | ||
}).concat(AndroidBuilderEnvironmentSchema); | ||
const IosSchema = joi_1.default.object({ | ||
workflow: joi_1.default.string(), | ||
credentialsSource: joi_1.default.string().valid('local', 'remote').default('remote'), | ||
releaseChannel: joi_1.default.string(), | ||
channel: joi_1.default.string(), | ||
distribution: joi_1.default.string().valid('store', 'internal', 'simulator').default('store'), | ||
enterpriseProvisioning: joi_1.default.string().valid('adhoc', 'universal'), | ||
autoIncrement: joi_1.default.alternatives() | ||
.try(joi_1.default.boolean(), joi_1.default.string().valid('version', 'buildNumber')) | ||
.default(false), | ||
cache: CacheSchema.default(), | ||
artifactPath: joi_1.default.string(), | ||
scheme: joi_1.default.string(), | ||
schemeBuildConfiguration: joi_1.default.string(), | ||
buildType: joi_1.default.string().valid('release', 'development-client'), | ||
}).concat(IosBuilderEnvironmentSchema); | ||
exports.schemaBuildProfileMap = { | ||
android: AndroidSchema, | ||
ios: IosSchema, | ||
}; | ||
buildConfiguration: joi_1.default.string(), | ||
})); | ||
const EasJsonBuildProfileSchema = CommonBuildProfileSchema.concat(joi_1.default.object({ | ||
extends: joi_1.default.string(), | ||
android: AndroidBuildProfileSchema, | ||
ios: IosBuildProfileSchema, | ||
})); | ||
exports.MinimalEasJsonSchema = joi_1.default.object({ | ||
build: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object()), | ||
}); | ||
exports.EasJsonSchema = joi_1.default.object({ | ||
builds: joi_1.default.object({ | ||
android: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({}).unknown(true) // profile is validated further only if build is for that platform | ||
), | ||
ios: joi_1.default.object().pattern(joi_1.default.string(), joi_1.default.object({}).unknown(true) // profile is validated further only if build is for that platform | ||
), | ||
}), | ||
build: joi_1.default.object().pattern(joi_1.default.string(), EasJsonBuildProfileSchema), | ||
}); |
@@ -1,2 +0,3 @@ | ||
export { CredentialsSource, AndroidDistributionType, AndroidBuildProfile, BuildProfile, IosDistributionType, IosBuildProfile, IosEnterpriseProvisioning, DistributionType, EasConfig, VersionAutoIncrement, } from './Config.types'; | ||
export { AndroidBuildProfile, BuildProfile, CredentialsSource, DistributionType, EasJson, IosBuildProfile, IosEnterpriseProvisioning, VersionAutoIncrement, } from './EasJson.types'; | ||
export { EasJsonReader } from './EasJsonReader'; | ||
export { hasMismatchedExtendsAsync, isUsingDeprecatedFormatAsync, migrateAsync } from './migrate'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.EasJsonReader = exports.CredentialsSource = void 0; | ||
var Config_types_1 = require("./Config.types"); | ||
Object.defineProperty(exports, "CredentialsSource", { enumerable: true, get: function () { return Config_types_1.CredentialsSource; } }); | ||
exports.migrateAsync = exports.isUsingDeprecatedFormatAsync = exports.hasMismatchedExtendsAsync = exports.EasJsonReader = exports.CredentialsSource = void 0; | ||
var EasJson_types_1 = require("./EasJson.types"); | ||
Object.defineProperty(exports, "CredentialsSource", { enumerable: true, get: function () { return EasJson_types_1.CredentialsSource; } }); | ||
var EasJsonReader_1 = require("./EasJsonReader"); | ||
Object.defineProperty(exports, "EasJsonReader", { enumerable: true, get: function () { return EasJsonReader_1.EasJsonReader; } }); | ||
var migrate_1 = require("./migrate"); | ||
Object.defineProperty(exports, "hasMismatchedExtendsAsync", { enumerable: true, get: function () { return migrate_1.hasMismatchedExtendsAsync; } }); | ||
Object.defineProperty(exports, "isUsingDeprecatedFormatAsync", { enumerable: true, get: function () { return migrate_1.isUsingDeprecatedFormatAsync; } }); | ||
Object.defineProperty(exports, "migrateAsync", { enumerable: true, get: function () { return migrate_1.migrateAsync; } }); |
{ | ||
"name": "@expo/eas-json", | ||
"description": "A library for interacting with the eas.json", | ||
"version": "0.22.1", | ||
"version": "0.22.2", | ||
"author": "Expo <support@expo.io>", | ||
"bugs": "https://github.com/expo/eas-cli/issues", | ||
"dependencies": { | ||
"@expo/eas-build-job": "0.2.44", | ||
"@expo/eas-build-job": "0.2.45", | ||
"@hapi/joi": "17.1.1", | ||
@@ -42,3 +42,3 @@ "fs-extra": "9.0.1", | ||
}, | ||
"gitHead": "47784ca6d36a9e5cd04d2154afeb827ef58d70c1" | ||
"gitHead": "20b091fbfa45ef52a81b0ec2b885e6c98b71d7a7" | ||
} |
@@ -0,1 +1,2 @@ | ||
import { Platform } from '@expo/eas-build-job'; | ||
import fs from 'fs-extra'; | ||
@@ -13,32 +14,33 @@ import { vol } from 'memfs'; | ||
test('minimal valid android eas.json', async () => { | ||
test('minimal valid eas.json for both platforms', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
release: {}, | ||
}, | ||
build: { | ||
release: {}, | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const easJson = await reader.readAsync('release'); | ||
const reader = new EasJsonReader('/project'); | ||
const iosProfile = await reader.readBuildProfileAsync('release', Platform.IOS); | ||
const androidProfile = await reader.readBuildProfileAsync('release', Platform.ANDROID); | ||
expect({ | ||
builds: { | ||
android: { | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
env: {}, | ||
withoutCredentials: false, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
image: 'default', | ||
}, | ||
}, | ||
}).toEqual(easJson); | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
}).toEqual(androidProfile); | ||
expect({ | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
}).toEqual(iosProfile); | ||
}); | ||
test('minimal valid ios eas.json', async () => { | ||
test('valid eas.json for development client builds', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
ios: { | ||
release: {}, | ||
build: { | ||
release: {}, | ||
debug: { | ||
developmentClient: true, | ||
android: { | ||
withoutCredentials: true, | ||
}, | ||
}, | ||
@@ -48,60 +50,46 @@ }, | ||
const reader = new EasJsonReader('/project', 'ios'); | ||
const easJson = await reader.readAsync('release'); | ||
const reader = new EasJsonReader('/project'); | ||
const iosProfile = await reader.readBuildProfileAsync('debug', Platform.IOS); | ||
const androidProfile = await reader.readBuildProfileAsync('debug', Platform.ANDROID); | ||
expect({ | ||
builds: { | ||
ios: { | ||
credentialsSource: 'remote', | ||
distribution: 'store', | ||
autoIncrement: false, | ||
env: {}, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
}, | ||
}, | ||
}).toEqual(easJson); | ||
credentialsSource: 'remote', | ||
distribution: 'store', | ||
developmentClient: true, | ||
withoutCredentials: true, | ||
}).toEqual(androidProfile); | ||
expect({ | ||
credentialsSource: 'remote', | ||
distribution: 'store', | ||
developmentClient: true, | ||
}).toEqual(iosProfile); | ||
}); | ||
test('minimal valid eas.json for both platforms', async () => { | ||
test('valid profile for internal distribution on Android', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
release: {}, | ||
build: { | ||
internal: { | ||
distribution: 'internal', | ||
}, | ||
ios: { | ||
release: {}, | ||
}, | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'all'); | ||
const easJson = await reader.readAsync('release'); | ||
const reader = new EasJsonReader('/project'); | ||
const profile = await reader.readBuildProfileAsync('internal', Platform.ANDROID); | ||
expect({ | ||
builds: { | ||
android: { | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
env: {}, | ||
withoutCredentials: false, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
image: 'default', | ||
}, | ||
ios: { | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
autoIncrement: false, | ||
env: {}, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
}, | ||
}, | ||
}).toEqual(easJson); | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
}).toEqual(profile); | ||
}); | ||
test('valid eas.json with both platform, but reading only android', async () => { | ||
test('valid profile extending other profile', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
ios: { | ||
release: {}, | ||
build: { | ||
base: { | ||
node: '12.0.0', | ||
}, | ||
android: { | ||
release: {}, | ||
extension: { | ||
extends: 'base', | ||
distribution: 'internal', | ||
node: '13.0.0', | ||
}, | ||
@@ -111,31 +99,37 @@ }, | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const easJson = await reader.readAsync('release'); | ||
const reader = new EasJsonReader('/project'); | ||
const baseProfile = await reader.readBuildProfileAsync('base', Platform.ANDROID); | ||
const extendedProfile = await reader.readBuildProfileAsync('extension', Platform.ANDROID); | ||
expect({ | ||
builds: { | ||
android: { | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
env: {}, | ||
withoutCredentials: false, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
image: 'default', | ||
}, | ||
}, | ||
}).toEqual(easJson); | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
node: '12.0.0', | ||
}).toEqual(baseProfile); | ||
expect({ | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
node: '13.0.0', | ||
}).toEqual(extendedProfile); | ||
}); | ||
test('valid eas.json for development client builds', async () => { | ||
test('valid profile extending other profile with platform specific envs', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
ios: { | ||
release: {}, | ||
debug: { buildType: 'development-client' }, | ||
build: { | ||
base: { | ||
env: { | ||
BASE_ENV: '1', | ||
PROFILE: 'base', | ||
}, | ||
}, | ||
android: { | ||
release: {}, | ||
debug: { | ||
withoutCredentials: false, | ||
buildType: 'development-client', | ||
extension: { | ||
extends: 'base', | ||
distribution: 'internal', | ||
env: { | ||
PROFILE: 'extension', | ||
}, | ||
android: { | ||
env: { | ||
PROFILE: 'extension:android', | ||
}, | ||
}, | ||
}, | ||
@@ -145,129 +139,108 @@ }, | ||
const reader = new EasJsonReader('/project', 'all'); | ||
const easJson = await reader.readAsync('debug'); | ||
const reader = new EasJsonReader('/project'); | ||
const baseProfile = await reader.readBuildProfileAsync('base', Platform.ANDROID); | ||
const extendedAndroidProfile = await reader.readBuildProfileAsync('extension', Platform.ANDROID); | ||
const extendedIosProfile = await reader.readBuildProfileAsync('extension', Platform.IOS); | ||
expect({ | ||
builds: { | ||
android: { | ||
credentialsSource: 'remote', | ||
distribution: 'store', | ||
env: {}, | ||
image: 'default', | ||
withoutCredentials: false, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
buildType: 'development-client', | ||
}, | ||
ios: { | ||
credentialsSource: 'remote', | ||
distribution: 'store', | ||
autoIncrement: false, | ||
env: {}, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
buildType: 'development-client', | ||
}, | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
env: { | ||
BASE_ENV: '1', | ||
PROFILE: 'base', | ||
}, | ||
}).toEqual(easJson); | ||
}); | ||
test('valid generic profile for internal distribution on Android', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
internal: { | ||
distribution: 'internal', | ||
}, | ||
}, | ||
}).toEqual(baseProfile); | ||
expect({ | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
env: { | ||
BASE_ENV: '1', | ||
PROFILE: 'extension:android', | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const easJson = await reader.readAsync('internal'); | ||
}).toEqual(extendedAndroidProfile); | ||
expect({ | ||
builds: { | ||
android: { | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
env: {}, | ||
withoutCredentials: false, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
image: 'default', | ||
}, | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
env: { | ||
BASE_ENV: '1', | ||
PROFILE: 'extension', | ||
}, | ||
}).toEqual(easJson); | ||
}).toEqual(extendedIosProfile); | ||
}); | ||
test('valid managed profile for internal distribution on Android', async () => { | ||
test('valid profile extending other profile with platform specific caching', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
internal: { | ||
distribution: 'internal', | ||
build: { | ||
base: { | ||
cache: { | ||
disabled: true, | ||
}, | ||
}, | ||
extension: { | ||
extends: 'base', | ||
distribution: 'internal', | ||
cache: { | ||
key: 'extend-key', | ||
}, | ||
android: { | ||
cache: { | ||
cacheDefaultPaths: false, | ||
customPaths: ['somefakepath'], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const easJson = await reader.readAsync('internal'); | ||
const reader = new EasJsonReader('/project'); | ||
const baseProfile = await reader.readBuildProfileAsync('base', Platform.ANDROID); | ||
const extendedAndroidProfile = await reader.readBuildProfileAsync('extension', Platform.ANDROID); | ||
const extendedIosProfile = await reader.readBuildProfileAsync('extension', Platform.IOS); | ||
expect({ | ||
builds: { | ||
android: { | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
withoutCredentials: false, | ||
env: {}, | ||
cache: { disabled: false, cacheDefaultPaths: true, customPaths: [] }, | ||
image: 'default', | ||
}, | ||
distribution: 'store', | ||
credentialsSource: 'remote', | ||
cache: { | ||
disabled: true, | ||
}, | ||
}).toEqual(easJson); | ||
}); | ||
}).toEqual(baseProfile); | ||
expect({ | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
cache: { | ||
cacheDefaultPaths: false, | ||
customPaths: ['somefakepath'], | ||
}, | ||
}).toEqual(extendedAndroidProfile); | ||
expect({ | ||
distribution: 'internal', | ||
credentialsSource: 'remote', | ||
test('invalid managed profile for internal distribution on Android', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
internal: { | ||
buildType: 'aab', | ||
distribution: 'internal', | ||
}, | ||
}, | ||
cache: { | ||
key: 'extend-key', | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const promise = reader.readAsync('internal'); | ||
await expect(promise).rejects.toThrowError( | ||
'Object "android.internal" in eas.json is not valid [ValidationError: "buildType" must be one of [apk, development-client]]' | ||
); | ||
}).toEqual(extendedIosProfile); | ||
}); | ||
test('invalid eas.json with missing preset', async () => { | ||
test('valid eas.json with missing profile', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
release: {}, | ||
}, | ||
build: { | ||
release: {}, | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const promise = reader.readAsync('debug'); | ||
await expect(promise).rejects.toThrowError( | ||
'There is no profile named debug for platform android' | ||
); | ||
const reader = new EasJsonReader('/project'); | ||
const promise = reader.readBuildProfileAsync('debug', Platform.ANDROID); | ||
await expect(promise).rejects.toThrowError('There is no profile named debug in eas.json.'); | ||
}); | ||
test('invalid eas.json when using buildType for wrong platform', async () => { | ||
test('invalid eas.json when using wrong buildType', async () => { | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
release: { buildType: 'archive' }, | ||
}, | ||
build: { | ||
release: { android: { buildType: 'archive' } }, | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const promise = reader.readAsync('release'); | ||
const reader = new EasJsonReader('/project'); | ||
const promise = reader.readBuildProfileAsync('release', Platform.ANDROID); | ||
await expect(promise).rejects.toThrowError( | ||
'Object "android.release" in eas.json is not valid [ValidationError: "buildType" must be one of [apk, app-bundle, development-client]]' | ||
'eas.json is not valid [ValidationError: "build.release.android.buildType" must be one of [apk, app-bundle]]' | ||
); | ||
@@ -279,7 +252,5 @@ }); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const promise = reader.readAsync('release'); | ||
await expect(promise).rejects.toThrowError( | ||
'There is no profile named release for platform android' | ||
); | ||
const reader = new EasJsonReader('/project'); | ||
const promise = reader.readBuildProfileAsync('release', Platform.ANDROID); | ||
await expect(promise).rejects.toThrowError('There is no profile named release in eas.json.'); | ||
}); | ||
@@ -289,13 +260,11 @@ | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
release: { node: '12.0.0-alpha' }, | ||
}, | ||
build: { | ||
release: { node: '12.0.0-alpha' }, | ||
}, | ||
}); | ||
const reader = new EasJsonReader('/project', 'android'); | ||
const promise = reader.readAsync('release'); | ||
const reader = new EasJsonReader('/project'); | ||
const promise = reader.readBuildProfileAsync('release', Platform.ANDROID); | ||
await expect(promise).rejects.toThrowError( | ||
'Object "android.release" in eas.json is not valid [ValidationError: "node" failed custom validation because 12.0.0-alpha is not a valid version]' | ||
'eas.json is not valid [ValidationError: "build.release.node" failed custom validation because 12.0.0-alpha is not a valid version]' | ||
); | ||
@@ -306,25 +275,11 @@ }); | ||
await fs.writeJson('/project/eas.json', { | ||
builds: { | ||
android: { | ||
release: { node: '12.0.0-alpha' }, | ||
blah: { node: '12.0.0-alpha' }, | ||
}, | ||
ios: { | ||
test: { node: '12.0.0-alpha' }, | ||
blah: { node: '12.0.0-alpha' }, | ||
}, | ||
build: { | ||
release: { node: '12.0.0-alpha' }, | ||
blah: { node: '12.0.0-alpha' }, | ||
}, | ||
}); | ||
const androidReader = new EasJsonReader('/project', 'android'); | ||
const androidProfileNames = await androidReader.getBuildProfileNamesAsync(); | ||
expect(androidProfileNames.sort()).toEqual(['release', 'blah'].sort()); | ||
const iosReader = new EasJsonReader('/project', 'ios'); | ||
const iosProfileNames = await iosReader.getBuildProfileNamesAsync(); | ||
expect(iosProfileNames.sort()).toEqual(['test', 'blah'].sort()); | ||
const allReader = new EasJsonReader('/project', 'all'); | ||
const allProfileNames = await allReader.getBuildProfileNamesAsync(); | ||
expect(allProfileNames.sort()).toEqual(['blah'].sort()); | ||
const reader = new EasJsonReader('/project'); | ||
const allProfileNames = await reader.getBuildProfileNamesAsync(); | ||
expect(allProfileNames.sort()).toEqual(['blah', 'release'].sort()); | ||
}); |
@@ -1,24 +0,17 @@ | ||
import { Platform, Workflow } from '@expo/eas-build-job'; | ||
import { Platform } from '@expo/eas-build-job'; | ||
import fs from 'fs-extra'; | ||
import path from 'path'; | ||
import { AndroidBuildProfile, BuildProfile, EasConfig, IosBuildProfile } from './Config.types'; | ||
import { EasJsonSchema, schemaBuildProfileMap } from './EasJsonSchema'; | ||
import { BuildProfile, CredentialsSource, EasJson, RawBuildProfile } from './EasJson.types'; | ||
import { EasJsonSchema, MinimalEasJsonSchema } from './EasJsonSchema'; | ||
interface EasJson { | ||
builds: { | ||
android?: { [key: string]: BuildProfilePreValidation }; | ||
ios?: { [key: string]: BuildProfilePreValidation }; | ||
}; | ||
interface EasJsonPreValidation { | ||
build: { [profile: string]: object }; | ||
} | ||
interface BuildProfilePreValidation { | ||
workflow?: Workflow; | ||
extends?: string; | ||
} | ||
const defaults = { | ||
distribution: 'store', | ||
credentialsSource: CredentialsSource.REMOTE, | ||
} as const; | ||
function intersect<T>(setA: Set<T>, setB: Set<T>): Set<T> { | ||
return new Set([...setA].filter(i => setB.has(i))); | ||
} | ||
export class EasJsonReader { | ||
@@ -29,78 +22,53 @@ public static formatEasJsonPath(projectDir: string) { | ||
constructor(private projectDir: string, private platform: 'android' | 'ios' | 'all') {} | ||
constructor(private projectDir: string) {} | ||
/** | ||
* Return build profile names for a particular platform. | ||
* If platform is 'all', return common build profiles for all platforms | ||
*/ | ||
public async getBuildProfileNamesAsync(): Promise<string[]> { | ||
const easJson = await this.readRawAsync(); | ||
if (this.platform === 'android') { | ||
return Object.keys(easJson?.builds?.android ?? {}); | ||
} else if (this.platform === 'ios') { | ||
return Object.keys(easJson?.builds?.ios ?? {}); | ||
} else { | ||
const intersectingProfileNames = intersect( | ||
new Set(Object.keys(easJson?.builds?.ios ?? {})), | ||
new Set(Object.keys(easJson?.builds?.android ?? {})) | ||
); | ||
return Array.from(intersectingProfileNames); | ||
} | ||
return Object.keys(easJson?.build ?? {}); | ||
} | ||
public async readAsync(buildProfileName: string): Promise<EasConfig> { | ||
const easJson = await this.readRawAsync(); | ||
let androidConfig; | ||
if (['android', 'all'].includes(this.platform)) { | ||
androidConfig = this.validateBuildProfile<AndroidBuildProfile>( | ||
Platform.ANDROID, | ||
buildProfileName, | ||
easJson.builds?.android || {} | ||
public async readBuildProfileAsync<T extends Platform>( | ||
buildProfileName: string, | ||
platform: T | ||
): Promise<BuildProfile<T>> { | ||
const easJson = await this.readAndValidateAsync(); | ||
this.ensureProfileExists(easJson, buildProfileName); | ||
const { | ||
android: resolvedAndroidSpecificValues, | ||
ios: resolvedIosSpecificValues, | ||
...resolvedProfile | ||
} = this.resolveBuildProfile(easJson, buildProfileName); | ||
if (platform === Platform.ANDROID) { | ||
const profileWithoutDefaults = profileMerge( | ||
resolvedProfile, | ||
resolvedAndroidSpecificValues ?? {} | ||
); | ||
return profileMerge(defaults, profileWithoutDefaults) as BuildProfile<T>; | ||
} else if (platform === Platform.IOS) { | ||
const profileWithoutDefaults = profileMerge(resolvedProfile, resolvedIosSpecificValues ?? {}); | ||
return profileMerge(defaults, profileWithoutDefaults) as BuildProfile<T>; | ||
} else { | ||
throw new Error(`Unknown platform ${platform}`); | ||
} | ||
let iosConfig; | ||
if (['ios', 'all'].includes(this.platform)) { | ||
iosConfig = this.validateBuildProfile<IosBuildProfile>( | ||
Platform.IOS, | ||
buildProfileName, | ||
easJson.builds?.ios || {} | ||
); | ||
} | ||
return { | ||
builds: { | ||
...(androidConfig ? { android: androidConfig } : {}), | ||
...(iosConfig ? { ios: iosConfig } : {}), | ||
}, | ||
}; | ||
} | ||
public async validateAsync(): Promise<void> { | ||
public async readAndValidateAsync(): Promise<EasJson> { | ||
const easJson = await this.readRawAsync(); | ||
const { value, error } = EasJsonSchema.validate(easJson, { | ||
allowUnknown: false, | ||
convert: true, | ||
abortEarly: false, | ||
}); | ||
const androidProfiles = easJson.builds?.android ?? {}; | ||
for (const name of Object.keys(androidProfiles)) { | ||
try { | ||
this.validateBuildProfile(Platform.ANDROID, name, androidProfiles); | ||
} catch (err) { | ||
err.msg = `Failed to validate Android build profile "${name}"\n${err.msg}`; | ||
throw err; | ||
} | ||
if (error) { | ||
throw new Error(`eas.json is not valid [${error.toString()}]`); | ||
} | ||
const iosProfiles = easJson.builds?.ios ?? {}; | ||
for (const name of Object.keys(iosProfiles)) { | ||
try { | ||
this.validateBuildProfile(Platform.IOS, name, iosProfiles); | ||
} catch (err) { | ||
err.msg = `Failed to validate iOS build profile "${name}"\n${err.msg}`; | ||
throw err; | ||
} | ||
} | ||
return value as EasJson; | ||
} | ||
public async readRawAsync(): Promise<EasJson> { | ||
public async readRawAsync(): Promise<EasJsonPreValidation> { | ||
const rawFile = await fs.readFile(EasJsonReader.formatEasJsonPath(this.projectDir), 'utf8'); | ||
const json = JSON.parse(rawFile); | ||
const { value, error } = EasJsonSchema.validate(json, { | ||
const { value, error } = MinimalEasJsonSchema.validate(json, { | ||
abortEarly: false, | ||
@@ -115,29 +83,7 @@ }); | ||
private validateBuildProfile<T extends BuildProfile>( | ||
platform: Platform, | ||
buildProfileName: string, | ||
buildProfiles: Record<string, BuildProfilePreValidation> | ||
): T { | ||
const buildProfile = this.resolveBuildProfile(platform, buildProfileName, buildProfiles); | ||
const schema = schemaBuildProfileMap[platform]; | ||
const { value, error } = schema.validate(buildProfile, { | ||
stripUnknown: true, | ||
convert: true, | ||
abortEarly: false, | ||
}); | ||
if (error) { | ||
throw new Error( | ||
`Object "${platform}.${buildProfileName}" in eas.json is not valid [${error.toString()}]` | ||
); | ||
} | ||
return value; | ||
} | ||
private resolveBuildProfile( | ||
platform: Platform, | ||
buildProfileName: string, | ||
buildProfiles: Record<string, BuildProfilePreValidation>, | ||
easJson: EasJson, | ||
profileName: string, | ||
depth: number = 0 | ||
): Record<string, any> { | ||
): RawBuildProfile { | ||
if (depth >= 2) { | ||
@@ -148,10 +94,10 @@ throw new Error( | ||
} | ||
const buildProfile = buildProfiles[buildProfileName]; | ||
const buildProfile = easJson.build[profileName]; | ||
if (!buildProfile) { | ||
throw new Error(`There is no profile named ${buildProfileName} for platform ${platform}`); | ||
throw new Error(`There is no profile named ${profileName}`); | ||
} | ||
const { extends: baseProfileName, ...buildProfileRest } = buildProfile; | ||
if (baseProfileName) { | ||
return deepMerge( | ||
this.resolveBuildProfile(platform, baseProfileName, buildProfiles, depth + 1), | ||
return profileMerge( | ||
this.resolveBuildProfile(easJson, baseProfileName, depth + 1), | ||
buildProfileRest | ||
@@ -163,31 +109,28 @@ ); | ||
} | ||
} | ||
function isObject(value: any): boolean { | ||
return typeof value === 'object' && value !== null; | ||
private ensureProfileExists(easJson: EasJson, profileName: string) { | ||
if (!easJson.build || !easJson.build[profileName]) { | ||
throw new Error(`There is no profile named ${profileName} in eas.json.`); | ||
} | ||
} | ||
} | ||
export function deepMerge( | ||
base: Record<string, any>, | ||
update: Record<string, any> | ||
): Record<string, any> { | ||
const result: Record<string, any> = {}; | ||
Object.keys(base).forEach(key => { | ||
const oldValue = base[key]; | ||
const newValue = update[key]; | ||
if (isObject(newValue) && isObject(oldValue)) { | ||
result[key] = deepMerge(oldValue, newValue); | ||
} else if (newValue !== undefined) { | ||
result[key] = isObject(newValue) ? deepMerge({}, newValue) : newValue; | ||
} else { | ||
result[key] = isObject(oldValue) ? deepMerge({}, oldValue) : oldValue; | ||
} | ||
}); | ||
Object.keys(update).forEach(key => { | ||
const newValue = update[key]; | ||
if (result[key] === undefined) { | ||
result[key] = isObject(newValue) ? deepMerge({}, newValue) : newValue; | ||
} | ||
}); | ||
export function profileMerge(base: RawBuildProfile, update: RawBuildProfile): RawBuildProfile { | ||
const result = { | ||
...base, | ||
...update, | ||
}; | ||
if (base.env && update.env) { | ||
result.env = { | ||
...base.env, | ||
...update.env, | ||
}; | ||
} | ||
if (base.android && update.android) { | ||
result.android = profileMerge(base.android, update.android); | ||
} | ||
if (base.ios && update.ios) { | ||
result.ios = profileMerge(base.ios, update.ios); | ||
} | ||
return result; | ||
} |
import { Android, Ios } from '@expo/eas-build-job'; | ||
import Joi, { CustomHelpers } from '@hapi/joi'; | ||
import Joi from '@hapi/joi'; | ||
const semverSchemaCheck = (value: any, helpers: CustomHelpers) => { | ||
const semverSchemaCheck = (value: any) => { | ||
if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(value)) { | ||
@@ -12,85 +12,70 @@ return value; | ||
const AndroidBuilderEnvironmentSchema = Joi.object({ | ||
image: Joi.string() | ||
.valid(...Android.builderBaseImages) | ||
.default('default'), | ||
node: Joi.string().empty(null).custom(semverSchemaCheck), | ||
yarn: Joi.string().empty(null).custom(semverSchemaCheck), | ||
ndk: Joi.string().empty(null).custom(semverSchemaCheck), | ||
expoCli: Joi.string().empty(null).custom(semverSchemaCheck), | ||
env: Joi.object().pattern(Joi.string(), Joi.string().empty(null)).default({}), | ||
const CacheSchema = Joi.object({ | ||
disabled: Joi.boolean(), | ||
key: Joi.string().max(128), | ||
cacheDefaultPaths: Joi.boolean(), | ||
customPaths: Joi.array().items(Joi.string()), | ||
}); | ||
const IosBuilderEnvironmentSchema = Joi.object({ | ||
image: Joi.string().valid(...Ios.builderBaseImages), | ||
const CommonBuildProfileSchema = Joi.object({ | ||
credentialsSource: Joi.string().valid('local', 'remote'), | ||
distribution: Joi.string().valid('store', 'internal'), | ||
cache: CacheSchema, | ||
releaseChannel: Joi.string(), | ||
channel: Joi.string(), | ||
developmentClient: Joi.boolean(), | ||
node: Joi.string().empty(null).custom(semverSchemaCheck), | ||
yarn: Joi.string().empty(null).custom(semverSchemaCheck), | ||
bundler: Joi.string().empty(null).custom(semverSchemaCheck), | ||
fastlane: Joi.string().empty(null).custom(semverSchemaCheck), | ||
cocoapods: Joi.string().empty(null).custom(semverSchemaCheck), | ||
expoCli: Joi.string().empty(null).custom(semverSchemaCheck), | ||
env: Joi.object().pattern(Joi.string(), Joi.string().empty(null)).default({}), | ||
env: Joi.object().pattern(Joi.string(), Joi.string().empty(null)), | ||
}); | ||
const CacheSchema = Joi.object({ | ||
disabled: Joi.boolean().default(false), | ||
key: Joi.string().max(128), | ||
cacheDefaultPaths: Joi.boolean().default(true), | ||
customPaths: Joi.array().items(Joi.string()).default([]), | ||
}); | ||
const AndroidBuildProfileSchema = CommonBuildProfileSchema.concat( | ||
Joi.object({ | ||
withoutCredentials: Joi.boolean(), | ||
const AndroidSchema = Joi.object({ | ||
workflow: Joi.string(), | ||
credentialsSource: Joi.string().valid('local', 'remote').default('remote'), | ||
releaseChannel: Joi.string(), | ||
channel: Joi.string(), | ||
distribution: Joi.string().valid('store', 'internal').default('store'), | ||
cache: CacheSchema.default(), | ||
withoutCredentials: Joi.boolean().default(false), | ||
image: Joi.string().valid(...Android.builderBaseImages), | ||
ndk: Joi.string().empty(null).custom(semverSchemaCheck), | ||
artifactPath: Joi.string(), | ||
gradleCommand: Joi.string(), | ||
artifactPath: Joi.string(), | ||
gradleCommand: Joi.string(), | ||
buildType: Joi.alternatives().conditional('distribution', { | ||
is: 'internal', | ||
then: Joi.string().valid('apk', 'development-client'), | ||
otherwise: Joi.string().valid('apk', 'app-bundle', 'development-client'), | ||
}), | ||
}).concat(AndroidBuilderEnvironmentSchema); | ||
buildType: Joi.string().valid('apk', 'app-bundle'), | ||
}) | ||
); | ||
const IosSchema = Joi.object({ | ||
workflow: Joi.string(), | ||
credentialsSource: Joi.string().valid('local', 'remote').default('remote'), | ||
releaseChannel: Joi.string(), | ||
channel: Joi.string(), | ||
distribution: Joi.string().valid('store', 'internal', 'simulator').default('store'), | ||
enterpriseProvisioning: Joi.string().valid('adhoc', 'universal'), | ||
autoIncrement: Joi.alternatives() | ||
.try(Joi.boolean(), Joi.string().valid('version', 'buildNumber')) | ||
.default(false), | ||
cache: CacheSchema.default(), | ||
const IosBuildProfileSchema = CommonBuildProfileSchema.concat( | ||
Joi.object({ | ||
enterpriseProvisioning: Joi.string().valid('adhoc', 'universal'), | ||
autoIncrement: Joi.alternatives().try( | ||
Joi.boolean(), | ||
Joi.string().valid('version', 'buildNumber') | ||
), | ||
artifactPath: Joi.string(), | ||
scheme: Joi.string(), | ||
schemeBuildConfiguration: Joi.string(), | ||
image: Joi.string().valid(...Ios.builderBaseImages), | ||
bundler: Joi.string().empty(null).custom(semverSchemaCheck), | ||
fastlane: Joi.string().empty(null).custom(semverSchemaCheck), | ||
cocoapods: Joi.string().empty(null).custom(semverSchemaCheck), | ||
buildType: Joi.string().valid('release', 'development-client'), | ||
}).concat(IosBuilderEnvironmentSchema); | ||
artifactPath: Joi.string(), | ||
scheme: Joi.string(), | ||
buildConfiguration: Joi.string(), | ||
}) | ||
); | ||
export const schemaBuildProfileMap: Record<string, Joi.Schema> = { | ||
android: AndroidSchema, | ||
ios: IosSchema, | ||
}; | ||
const EasJsonBuildProfileSchema = CommonBuildProfileSchema.concat( | ||
Joi.object({ | ||
extends: Joi.string(), | ||
android: AndroidBuildProfileSchema, | ||
ios: IosBuildProfileSchema, | ||
}) | ||
); | ||
export const MinimalEasJsonSchema = Joi.object({ | ||
build: Joi.object().pattern(Joi.string(), Joi.object()), | ||
}); | ||
export const EasJsonSchema = Joi.object({ | ||
builds: Joi.object({ | ||
android: Joi.object().pattern( | ||
Joi.string(), | ||
Joi.object({}).unknown(true) // profile is validated further only if build is for that platform | ||
), | ||
ios: Joi.object().pattern( | ||
Joi.string(), | ||
Joi.object({}).unknown(true) // profile is validated further only if build is for that platform | ||
), | ||
}), | ||
build: Joi.object().pattern(Joi.string(), EasJsonBuildProfileSchema), | ||
}); |
export { | ||
CredentialsSource, | ||
AndroidDistributionType, | ||
AndroidBuildProfile, | ||
BuildProfile, | ||
IosDistributionType, | ||
CredentialsSource, | ||
DistributionType, | ||
EasJson, | ||
IosBuildProfile, | ||
IosEnterpriseProvisioning, | ||
DistributionType, | ||
EasConfig, | ||
VersionAutoIncrement, | ||
} from './Config.types'; | ||
} from './EasJson.types'; | ||
export { EasJsonReader } from './EasJsonReader'; | ||
export { hasMismatchedExtendsAsync, isUsingDeprecatedFormatAsync, migrateAsync } from './migrate'; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
77751
33
1999
4
+ Added@expo/eas-build-job@0.2.45(transitive)
- Removed@expo/eas-build-job@0.2.44(transitive)
Updated@expo/eas-build-job@0.2.45