From 8d8be68d98949abb13a41e314aa3f3aa27a6a79f Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Tue, 10 Dec 2019 15:20:17 +0100 Subject: [PATCH 01/27] Update Modules.ts --- packages/config/src/Modules.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/config/src/Modules.ts b/packages/config/src/Modules.ts index 9ae61ecf84..04a15286d3 100644 --- a/packages/config/src/Modules.ts +++ b/packages/config/src/Modules.ts @@ -1,6 +1,8 @@ import resolveFrom from 'resolve-from'; import { stat, statSync } from 'fs-extra'; +import { join, resolve } from 'path'; import { ExpoConfig } from './Config.types'; +import { ConfigError } from './Errors'; export function resolveModule( request: string, @@ -47,3 +49,20 @@ export function fileExists(file: string): boolean { return false; } } + +export function getRootPackageJsonPath( + projectRoot: string, + exp: Pick +): string { + const packageJsonPath = + 'nodeModulesPath' in exp && typeof exp.nodeModulesPath === 'string' + ? join(resolve(projectRoot, exp.nodeModulesPath), 'package.json') + : join(projectRoot, 'package.json'); + if (!fileExists(packageJsonPath)) { + throw new ConfigError( + `The expected package.json path: ${packageJsonPath} does not exist`, + 'MODULE_NOT_FOUND' + ); + } + return packageJsonPath; +} From f259da90581b8560b4941176611207a66b53a7a1 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Tue, 10 Dec 2019 15:20:43 +0100 Subject: [PATCH 02/27] Update Project.ts --- packages/config/src/Project.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/config/src/Project.ts b/packages/config/src/Project.ts index ec939f3b0b..f61a02f030 100644 --- a/packages/config/src/Project.ts +++ b/packages/config/src/Project.ts @@ -16,7 +16,10 @@ export function isUsingYarn(projectRoot: string): boolean { } } -export function getExpoSDKVersion(projectRoot: string, exp: ExpoConfig): string { +export function getExpoSDKVersion( + projectRoot: string, + exp: Pick +): string { if (exp && exp.sdkVersion) { return exp.sdkVersion; } From 4035f526f8440da9f6070a12f7d817df91573c8e Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Tue, 10 Dec 2019 15:21:28 +0100 Subject: [PATCH 03/27] Created eval method --- packages/config/package.json | 1 + packages/config/src/Config.types.ts | 11 ++++- packages/config/src/getConfig.ts | 64 +++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 packages/config/src/getConfig.ts diff --git a/packages/config/package.json b/packages/config/package.json index 7874293458..53d6d5a4e5 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -49,6 +49,7 @@ "find-yarn-workspace-root": "^1.2.1", "fs-extra": "^7.0.1", "invariant": "^2.2.4", + "js-yaml": "^3.13.1", "resolve-from": "^5.0.0", "slugify": "^1.3.4" }, diff --git a/packages/config/src/Config.types.ts b/packages/config/src/Config.types.ts index 043da63ba0..1cc2613c37 100644 --- a/packages/config/src/Config.types.ts +++ b/packages/config/src/Config.types.ts @@ -13,4 +13,13 @@ export type ExpoConfig = { }; export type ExpRc = { [key: string]: any }; export type Platform = 'android' | 'ios' | 'web'; -export type ConfigErrorCode = 'NO_APP_JSON' | 'NOT_OBJECT' | 'NO_EXPO' | 'MODULE_NOT_FOUND'; +export type ConfigErrorCode = + | 'NO_APP_JSON' + | 'NOT_OBJECT' + | 'NO_EXPO' + | 'MODULE_NOT_FOUND' + | 'INVALID_CONFIG'; +export type ConfigContext = { + projectRoot: string; + config: Partial; +}; diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts new file mode 100644 index 0000000000..2123dd733d --- /dev/null +++ b/packages/config/src/getConfig.ts @@ -0,0 +1,64 @@ +import fs from 'fs-extra'; +import { safeLoad } from 'js-yaml'; +import path from 'path'; + +import { ConfigContext, ExpoConfig } from './Config.types'; +import { ConfigError } from './Errors'; + +// support all common config types +export const allowedConfigFileNames: string[] = (() => { + const prefix = 'app'; + return [ + `${prefix}.config.js`, + `${prefix}.config.json`, + `${prefix}.config.json5`, + `${prefix}.config.yml`, + `${prefix}.config.yaml`, + `${prefix}.config.toml`, + // app.json should take lowest priority so that files like app.config.js can import, modify, and re-export the app.json config + `${prefix}.json`, + ]; +})(); + +export function findAndReadConfig(request: ConfigContext): ExpoConfig | null { + // TODO(Bacon): Should we support `expo` or `app` field with an object in the `package.json` too? + for (const configFile of allowedConfigFileNames) { + try { + return evalConfig(path.join(request.projectRoot, configFile), request); + } catch (error) { + if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; + } + } + + return null; +} + +// We cannot use async config resolution right now because Next.js doesn't support async configs. +// If they don't add support for async Webpack configs then we may need to pull support for Next.js. +export function evalConfig(configFile: string, request: ConfigContext): Partial { + const data = fs.readFileSync(configFile, 'utf8'); + let result; + if (configFile.endsWith('.json5') || configFile.endsWith('.json')) { + result = require('json5').parse(data); + } else if (configFile.endsWith('.js')) { + result = require(configFile); + if (result.default != null) { + result = result.default; + } + if (typeof result === 'function') { + result = result(request); + } + } else if (configFile.endsWith('.toml')) { + result = require('toml').parse(data); + } else { + result = safeLoad(data); + } + + // result = await Promise.resolve(result); + + if (result instanceof Promise) { + throw new ConfigError(`Config file ${configFile} cannot return a Promise.`, 'INVALID_CONFIG'); + } + + return result; +} From ff86c23157f17f617d445131cb8bca5c6d951253 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Tue, 10 Dec 2019 15:24:14 +0100 Subject: [PATCH 04/27] Added types and comments --- packages/config/package.json | 1 + packages/config/src/getConfig.ts | 2 ++ yarn.lock | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/packages/config/package.json b/packages/config/package.json index 53d6d5a4e5..ff48291c49 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -55,6 +55,7 @@ }, "devDependencies": { "@expo/babel-preset-cli": "^0.2.2", + "@types/js-yaml": "^3.12.1", "memfs": "^2.15.5" }, "publishConfig": { diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 2123dd733d..38beb6b105 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -9,6 +9,7 @@ import { ConfigError } from './Errors'; export const allowedConfigFileNames: string[] = (() => { const prefix = 'app'; return [ + // order is important `${prefix}.config.js`, `${prefix}.config.json`, `${prefix}.config.json5`, @@ -26,6 +27,7 @@ export function findAndReadConfig(request: ConfigContext): ExpoConfig | null { try { return evalConfig(path.join(request.projectRoot, configFile), request); } catch (error) { + // If the file doesn't exist then we should skip it and continue searching. if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; } } diff --git a/yarn.lock b/yarn.lock index 46fa8c925e..19f9d8c48f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2506,6 +2506,11 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-14.3.4.tgz#eed1e14cbb07716079c814138831a520a725a1e0" integrity sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A== +"@types/js-yaml@^3.12.1": + version "3.12.1" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656" + integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA== + "@types/jscodeshift@^0.6.0": version "0.6.3" resolved "https://registry.yarnpkg.com/@types/jscodeshift/-/jscodeshift-0.6.3.tgz#96b3acc2b5b761faa77056f777c295b3d16ee2f2" From 49c9cc0e4d35a58ea62547d4d807315c1ec6c632 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Tue, 10 Dec 2019 16:02:37 +0100 Subject: [PATCH 05/27] Add getConfig method --- packages/config/src/Config.ts | 84 ++++++++++++++++++++++------- packages/config/src/Config.types.ts | 1 + packages/config/src/getConfig.ts | 7 +-- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index ccd851d1a8..1062c5536e 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -3,9 +3,71 @@ import fs from 'fs-extra'; import path from 'path'; import slug from 'slugify'; -import { AppJSONConfig, ExpRc, ExpoConfig, PackageJSONConfig, ProjectConfig } from './Config.types'; +import { + AppJSONConfig, + ExpRc, + ExpoConfig, + PackageJSONConfig, + Platform, + ProjectConfig, +} from './Config.types'; import { ConfigError } from './Errors'; import { getExpoSDKVersion } from './Project'; +import { getRootPackageJsonPath, projectHasModule } from './Modules'; +import { findAndEvalConfig } from './getConfig'; + +/** + * Get all platforms that a project is currently capable of running. + * + * @param projectRoot + * @param exp + */ +function getSupportedPlatforms( + projectRoot: string, + exp: Pick +): Platform[] { + const platforms: Platform[] = []; + if (projectHasModule('react-native', projectRoot, exp)) { + platforms.push('ios', 'android'); + } + if (projectHasModule('react-native-web', projectRoot, exp)) { + platforms.push('web'); + } + return platforms; +} + +export function getConfigJson( + projectRoot: string, + options: { skipSDKVersionRequirement?: boolean } +): ProjectConfig { + // TODO(Bacon): This doesn't support changing the location of the package.json + const packageJsonPath = getRootPackageJsonPath(projectRoot, {}); + const pkg = JsonFile.read(packageJsonPath); + + const configRoot = + projectRoot in customConfigPaths ? path.dirname(customConfigPaths[projectRoot]) : projectRoot; + + const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, {}, pkg, true); + + const context = { + projectRoot, + configRoot, + config: configFromPkg, + }; + const config = findAndEvalConfig(context); + + const finalConfig = config || context.config; + + return { + ...ensureConfigHasDefaultValues( + projectRoot, + finalConfig, + pkg, + options.skipSDKVersionRequirement + ), + rootConfig: finalConfig as AppJSONConfig, + }; +} export function readConfigJson( projectRoot: string, @@ -109,25 +171,11 @@ function parseAndValidateRootConfig( }; } -function getRootPackageJsonPath(projectRoot: string, exp: ExpoConfig): string { - const packageJsonPath = - 'nodeModulesPath' in exp && typeof exp.nodeModulesPath === 'string' - ? path.join(path.resolve(projectRoot, exp.nodeModulesPath), 'package.json') - : path.join(projectRoot, 'package.json'); - if (!fs.existsSync(packageJsonPath)) { - throw new ConfigError( - `The expected package.json path: ${packageJsonPath} does not exist`, - 'MODULE_NOT_FOUND' - ); - } - return packageJsonPath; -} - function ensureConfigHasDefaultValues( projectRoot: string, exp: ExpoConfig, pkg: JSONObject, - skipNativeValidation: boolean = false + skipSDKVersionRequirement: boolean = false ): { exp: ExpoConfig; pkg: PackageJSONConfig } { if (!exp) exp = {}; @@ -154,11 +202,11 @@ function ensureConfigHasDefaultValues( try { exp.sdkVersion = getExpoSDKVersion(projectRoot, exp); } catch (error) { - if (!skipNativeValidation) throw error; + if (!skipSDKVersionRequirement) throw error; } if (!exp.platforms) { - exp.platforms = ['android', 'ios']; + exp.platforms = getSupportedPlatforms(projectRoot, exp); } return { exp, pkg }; diff --git a/packages/config/src/Config.types.ts b/packages/config/src/Config.types.ts index 1cc2613c37..0365198747 100644 --- a/packages/config/src/Config.types.ts +++ b/packages/config/src/Config.types.ts @@ -21,5 +21,6 @@ export type ConfigErrorCode = | 'INVALID_CONFIG'; export type ConfigContext = { projectRoot: string; + configRoot: string; config: Partial; }; diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 38beb6b105..b1668cfdfe 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -21,11 +21,12 @@ export const allowedConfigFileNames: string[] = (() => { ]; })(); -export function findAndReadConfig(request: ConfigContext): ExpoConfig | null { +export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { + // TODO(Bacon): Support custom config path with `findConfigFile` // TODO(Bacon): Should we support `expo` or `app` field with an object in the `package.json` too? for (const configFile of allowedConfigFileNames) { try { - return evalConfig(path.join(request.projectRoot, configFile), request); + return evalConfig(path.join(request.configRoot, configFile), request); } catch (error) { // If the file doesn't exist then we should skip it and continue searching. if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; @@ -37,7 +38,7 @@ export function findAndReadConfig(request: ConfigContext): ExpoConfig | null { // We cannot use async config resolution right now because Next.js doesn't support async configs. // If they don't add support for async Webpack configs then we may need to pull support for Next.js. -export function evalConfig(configFile: string, request: ConfigContext): Partial { +function evalConfig(configFile: string, request: ConfigContext): Partial { const data = fs.readFileSync(configFile, 'utf8'); let result; if (configFile.endsWith('.json5') || configFile.endsWith('.json')) { From 73db50922825d53e821d67963c922c03673cf063 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Tue, 10 Dec 2019 16:03:40 +0100 Subject: [PATCH 06/27] split tests --- packages/config/src/__tests__/Config-test.js | 57 ------------------ packages/config/src/__tests__/Web-test.js | 63 ++++++++++++++++++++ 2 files changed, 63 insertions(+), 57 deletions(-) create mode 100644 packages/config/src/__tests__/Web-test.js diff --git a/packages/config/src/__tests__/Config-test.js b/packages/config/src/__tests__/Config-test.js index 13003c6edc..0d86885f75 100644 --- a/packages/config/src/__tests__/Config-test.js +++ b/packages/config/src/__tests__/Config-test.js @@ -1,67 +1,10 @@ import { vol } from 'memfs'; import { readConfigJson } from '../Config'; -import { getWebOutputPath } from '../Web'; jest.mock('fs'); jest.mock('resolve-from'); -describe('getWebOutputPath', () => { - beforeAll(() => { - const packageJson = JSON.stringify( - { - name: 'testing123', - version: '0.1.0', - main: 'index.js', - }, - null, - 2 - ); - - const appJson = { - name: 'testing 123', - version: '0.1.0', - slug: 'testing-123', - sdkVersion: '100.0.0', - }; - - vol.fromJSON({ - '/standard/package.json': JSON.stringify(packageJson), - '/standard/app.json': JSON.stringify({ expo: appJson }), - '/custom/package.json': JSON.stringify(packageJson), - '/custom/app.json': JSON.stringify({ - expo: { ...appJson, web: { build: { output: 'defined-in-config' } } }, - }), - }); - }); - afterAll(() => vol.reset()); - - it('uses the default output build path for web', () => { - const { exp } = readConfigJson('/standard'); - const outputPath = getWebOutputPath(exp); - expect(outputPath).toBe('web-build'); - }); - - it('uses a custom output build path from the config', () => { - const { exp } = readConfigJson('/custom'); - const outputPath = getWebOutputPath(exp); - expect(outputPath).toBe('defined-in-config'); - }); - - beforeEach(() => { - delete process.env.WEBPACK_BUILD_OUTPUT_PATH; - }); - it('uses an env variable for the web build path', () => { - process.env.WEBPACK_BUILD_OUTPUT_PATH = 'custom-env-path'; - - for (const project of ['/custom', '/standard']) { - const { exp } = readConfigJson(project); - const outputPath = getWebOutputPath(exp); - expect(outputPath).toBe('custom-env-path'); - } - }); -}); - describe('readConfigJson', () => { describe('sdkVersion', () => { beforeAll(() => { diff --git a/packages/config/src/__tests__/Web-test.js b/packages/config/src/__tests__/Web-test.js new file mode 100644 index 0000000000..729a4aa7a4 --- /dev/null +++ b/packages/config/src/__tests__/Web-test.js @@ -0,0 +1,63 @@ +import { vol } from 'memfs'; + +import { readConfigJson } from '../Config'; +import { getWebOutputPath } from '../Web'; + +jest.mock('fs'); +jest.mock('resolve-from'); + +describe('getWebOutputPath', () => { + beforeAll(() => { + const packageJson = JSON.stringify( + { + name: 'testing123', + version: '0.1.0', + main: 'index.js', + }, + null, + 2 + ); + + const appJson = { + name: 'testing 123', + version: '0.1.0', + slug: 'testing-123', + sdkVersion: '100.0.0', + }; + + vol.fromJSON({ + '/standard/package.json': JSON.stringify(packageJson), + '/standard/app.json': JSON.stringify({ expo: appJson }), + '/custom/package.json': JSON.stringify(packageJson), + '/custom/app.json': JSON.stringify({ + expo: { ...appJson, web: { build: { output: 'defined-in-config' } } }, + }), + }); + }); + afterAll(() => vol.reset()); + + it('uses the default output build path for web', () => { + const { exp } = readConfigJson('/standard'); + const outputPath = getWebOutputPath(exp); + expect(outputPath).toBe('web-build'); + }); + + it('uses a custom output build path from the config', () => { + const { exp } = readConfigJson('/custom'); + const outputPath = getWebOutputPath(exp); + expect(outputPath).toBe('defined-in-config'); + }); + + beforeEach(() => { + delete process.env.WEBPACK_BUILD_OUTPUT_PATH; + }); + it('uses an env variable for the web build path', () => { + process.env.WEBPACK_BUILD_OUTPUT_PATH = 'custom-env-path'; + + for (const project of ['/custom', '/standard']) { + const { exp } = readConfigJson(project); + const outputPath = getWebOutputPath(exp); + expect(outputPath).toBe('custom-env-path'); + } + }); +}); From 74e9422ef6359c5b9285bb8ecf4b689d78f491ad Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Tue, 10 Dec 2019 16:58:20 +0100 Subject: [PATCH 07/27] Added tests --- packages/config/package.json | 3 +- packages/config/src/Config.ts | 9 ++-- packages/config/src/Config.types.ts | 2 +- .../src/__tests__/ConfigParsing-test.js | 54 +++++++++++++++++++ .../language-support/js/app.config.js | 5 ++ .../fixtures/language-support/js/app.json | 3 ++ .../js/export-json_app.config.js | 4 ++ .../fixtures/language-support/js/package.json | 4 ++ .../js/with-default_app.config.js | 5 ++ .../language-support/json5/app.config.json5 | 4 ++ .../fixtures/language-support/json5/app.json5 | 4 ++ .../language-support/json5/package.json | 4 ++ .../language-support/toml/app.config.toml | 1 + .../fixtures/language-support/toml/app.toml | 1 + .../language-support/toml/package.json | 4 ++ .../language-support/yaml/app.config.yaml | 1 + .../fixtures/language-support/yaml/app.yaml | 1 + .../language-support/yaml/package.json | 4 ++ packages/config/src/getConfig.ts | 45 ++++++++++++++-- yarn.lock | 5 ++ 20 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 packages/config/src/__tests__/ConfigParsing-test.js create mode 100644 packages/config/src/__tests__/fixtures/language-support/js/app.config.js create mode 100644 packages/config/src/__tests__/fixtures/language-support/js/app.json create mode 100644 packages/config/src/__tests__/fixtures/language-support/js/export-json_app.config.js create mode 100644 packages/config/src/__tests__/fixtures/language-support/js/package.json create mode 100644 packages/config/src/__tests__/fixtures/language-support/js/with-default_app.config.js create mode 100644 packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 create mode 100644 packages/config/src/__tests__/fixtures/language-support/json5/app.json5 create mode 100644 packages/config/src/__tests__/fixtures/language-support/json5/package.json create mode 100644 packages/config/src/__tests__/fixtures/language-support/toml/app.config.toml create mode 100644 packages/config/src/__tests__/fixtures/language-support/toml/app.toml create mode 100644 packages/config/src/__tests__/fixtures/language-support/toml/package.json create mode 100644 packages/config/src/__tests__/fixtures/language-support/yaml/app.config.yaml create mode 100644 packages/config/src/__tests__/fixtures/language-support/yaml/app.yaml create mode 100644 packages/config/src/__tests__/fixtures/language-support/yaml/package.json diff --git a/packages/config/package.json b/packages/config/package.json index ff48291c49..c7bc6d4efc 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -51,7 +51,8 @@ "invariant": "^2.2.4", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0", - "slugify": "^1.3.4" + "slugify": "^1.3.4", + "toml": "^3.0.0" }, "devDependencies": { "@expo/babel-preset-cli": "^0.2.2", diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 1062c5536e..2212756dcc 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -36,22 +36,21 @@ function getSupportedPlatforms( return platforms; } -export function getConfigJson( +export function getConfig( projectRoot: string, - options: { skipSDKVersionRequirement?: boolean } + options: { configPath?: string; skipSDKVersionRequirement?: boolean } ): ProjectConfig { // TODO(Bacon): This doesn't support changing the location of the package.json const packageJsonPath = getRootPackageJsonPath(projectRoot, {}); const pkg = JsonFile.read(packageJsonPath); - const configRoot = - projectRoot in customConfigPaths ? path.dirname(customConfigPaths[projectRoot]) : projectRoot; + const configPath = options.configPath || customConfigPaths[projectRoot]; const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, {}, pkg, true); const context = { projectRoot, - configRoot, + configPath, config: configFromPkg, }; const config = findAndEvalConfig(context); diff --git a/packages/config/src/Config.types.ts b/packages/config/src/Config.types.ts index 0365198747..5f4cf8c995 100644 --- a/packages/config/src/Config.types.ts +++ b/packages/config/src/Config.types.ts @@ -21,6 +21,6 @@ export type ConfigErrorCode = | 'INVALID_CONFIG'; export type ConfigContext = { projectRoot: string; - configRoot: string; + configPath?: string; config: Partial; }; diff --git a/packages/config/src/__tests__/ConfigParsing-test.js b/packages/config/src/__tests__/ConfigParsing-test.js new file mode 100644 index 0000000000..772e18735e --- /dev/null +++ b/packages/config/src/__tests__/ConfigParsing-test.js @@ -0,0 +1,54 @@ +import * as path from 'path'; + +import { getConfig } from '../Config'; + +describe('getConfig', () => { + // Tests the following: + // - All supported languages are working + // - ensure `app.config` has higher priority to `app` + // - generated `.expo` object is created and the language hint is added + describe('language support', () => { + // ensure config is composed (package.json values still exist) + it('parses a js config', () => { + const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); + const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + expect(exp.foo).toBe('bar'); + expect(exp.name).toBe('js-config-test+config'); + expect(exp._expo.configType).toBe('js'); + }); + it('parses a js config with export default', () => { + const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); + const configPath = path.resolve(projectRoot, 'with-default_app.config.js'); + const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true, configPath }); + expect(exp.foo).toBe('bar'); + expect(exp.name).toBe('js-config-test+config-default'); + expect(exp._expo.configType).toBe('js'); + }); + it('parses a js config that exports json', () => { + const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); + const configPath = path.resolve(projectRoot, 'export-json_app.config.js'); + const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true, configPath }); + expect(exp.foo).toBe('bar'); + expect(exp.name).toBe('cool+export-json_app.config'); + expect(exp._expo.configType).toBe('js'); + }); + it('parses a yaml config', () => { + const projectRoot = path.resolve(__dirname, './fixtures/language-support/yaml'); + const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + expect(exp.foo).toBe('bar'); + expect(exp._expo.configType).toBe('yaml'); + }); + it('parses a toml config', () => { + const projectRoot = path.resolve(__dirname, './fixtures/language-support/toml'); + const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + expect(exp.foo).toBe('bar'); + expect(exp._expo.configType).toBe('toml'); + }); + it('parses a json5 config', () => { + const projectRoot = path.resolve(__dirname, './fixtures/language-support/json5'); + const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + expect(exp.foo).toBe('bar'); + expect(exp._expo.configType).toBe('json'); + }); + }); +}); diff --git a/packages/config/src/__tests__/fixtures/language-support/js/app.config.js b/packages/config/src/__tests__/fixtures/language-support/js/app.config.js new file mode 100644 index 0000000000..dff9f78aec --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/js/app.config.js @@ -0,0 +1,5 @@ +module.exports = function({ config }) { + config.foo = 'bar'; + if (config.name) config.name += '+config'; + return config; +}; diff --git a/packages/config/src/__tests__/fixtures/language-support/js/app.json b/packages/config/src/__tests__/fixtures/language-support/js/app.json new file mode 100644 index 0000000000..871feffd90 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/js/app.json @@ -0,0 +1,3 @@ +{ + "foo": "invalid" +} diff --git a/packages/config/src/__tests__/fixtures/language-support/js/export-json_app.config.js b/packages/config/src/__tests__/fixtures/language-support/js/export-json_app.config.js new file mode 100644 index 0000000000..46d76f0715 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/js/export-json_app.config.js @@ -0,0 +1,4 @@ +module.exports = { + foo: 'bar', + name: 'cool+export-json_app.config', +}; diff --git a/packages/config/src/__tests__/fixtures/language-support/js/package.json b/packages/config/src/__tests__/fixtures/language-support/js/package.json new file mode 100644 index 0000000000..c8fc586969 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/js/package.json @@ -0,0 +1,4 @@ +{ + "name": "js-config-test", + "version": "1.0.0" +} diff --git a/packages/config/src/__tests__/fixtures/language-support/js/with-default_app.config.js b/packages/config/src/__tests__/fixtures/language-support/js/with-default_app.config.js new file mode 100644 index 0000000000..e03835ba52 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/js/with-default_app.config.js @@ -0,0 +1,5 @@ +export default function({ config }) { + config.foo = 'bar'; + if (config.name) config.name += '+config-default'; + return config; +} diff --git a/packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 b/packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 new file mode 100644 index 0000000000..8629e3a583 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 @@ -0,0 +1,4 @@ +{ + // comments are cool + foo: 'bar', +} diff --git a/packages/config/src/__tests__/fixtures/language-support/json5/app.json5 b/packages/config/src/__tests__/fixtures/language-support/json5/app.json5 new file mode 100644 index 0000000000..e1fe0e002d --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/json5/app.json5 @@ -0,0 +1,4 @@ +{ + // comments are cool + foo: 'invalid', +} diff --git a/packages/config/src/__tests__/fixtures/language-support/json5/package.json b/packages/config/src/__tests__/fixtures/language-support/json5/package.json new file mode 100644 index 0000000000..8d14aac737 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/json5/package.json @@ -0,0 +1,4 @@ +{ + "name": "json5-config-test", + "version": "1.0.0" +} diff --git a/packages/config/src/__tests__/fixtures/language-support/toml/app.config.toml b/packages/config/src/__tests__/fixtures/language-support/toml/app.config.toml new file mode 100644 index 0000000000..21731b3ee9 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/toml/app.config.toml @@ -0,0 +1 @@ +foo = "bar" \ No newline at end of file diff --git a/packages/config/src/__tests__/fixtures/language-support/toml/app.toml b/packages/config/src/__tests__/fixtures/language-support/toml/app.toml new file mode 100644 index 0000000000..e3e6c8f7df --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/toml/app.toml @@ -0,0 +1 @@ +foo = "invalid" \ No newline at end of file diff --git a/packages/config/src/__tests__/fixtures/language-support/toml/package.json b/packages/config/src/__tests__/fixtures/language-support/toml/package.json new file mode 100644 index 0000000000..c8fc586969 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/toml/package.json @@ -0,0 +1,4 @@ +{ + "name": "js-config-test", + "version": "1.0.0" +} diff --git a/packages/config/src/__tests__/fixtures/language-support/yaml/app.config.yaml b/packages/config/src/__tests__/fixtures/language-support/yaml/app.config.yaml new file mode 100644 index 0000000000..20e9ff3fea --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/yaml/app.config.yaml @@ -0,0 +1 @@ +foo: bar diff --git a/packages/config/src/__tests__/fixtures/language-support/yaml/app.yaml b/packages/config/src/__tests__/fixtures/language-support/yaml/app.yaml new file mode 100644 index 0000000000..fb09ec4f8e --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/yaml/app.yaml @@ -0,0 +1 @@ +foo: invalid diff --git a/packages/config/src/__tests__/fixtures/language-support/yaml/package.json b/packages/config/src/__tests__/fixtures/language-support/yaml/package.json new file mode 100644 index 0000000000..c8fc586969 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/yaml/package.json @@ -0,0 +1,4 @@ +{ + "name": "js-config-test", + "version": "1.0.0" +} diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index b1668cfdfe..bca6cd13e2 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -2,8 +2,10 @@ import fs from 'fs-extra'; import { safeLoad } from 'js-yaml'; import path from 'path'; +import JsonFile from '@expo/json-file'; import { ConfigContext, ExpoConfig } from './Config.types'; import { ConfigError } from './Errors'; +import { fileExists } from './Modules'; // support all common config types export const allowedConfigFileNames: string[] = (() => { @@ -24,13 +26,35 @@ export const allowedConfigFileNames: string[] = (() => { export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { // TODO(Bacon): Support custom config path with `findConfigFile` // TODO(Bacon): Should we support `expo` or `app` field with an object in the `package.json` too? - for (const configFile of allowedConfigFileNames) { + + function testFileName(configFilePath: string): null | Partial { + if (!fileExists(configFilePath)) return null; + try { - return evalConfig(path.join(request.configRoot, configFile), request); + return evalConfig(configFilePath, request); } catch (error) { // If the file doesn't exist then we should skip it and continue searching. if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; } + return null; + } + + if (request.configPath) { + const config = testFileName(request.configPath); + if (config) { + return config; + } else { + throw new ConfigError( + `Config with custom path ${request.configPath} couldn't be parsed.`, + 'INVALID_CONFIG' + ); + } + } + + for (const configFileName of allowedConfigFileNames) { + const configFilePath = path.join(request.projectRoot, configFileName); + const config = testFileName(configFilePath); + if (config) return config; } return null; @@ -39,11 +63,13 @@ export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { // We cannot use async config resolution right now because Next.js doesn't support async configs. // If they don't add support for async Webpack configs then we may need to pull support for Next.js. function evalConfig(configFile: string, request: ConfigContext): Partial { - const data = fs.readFileSync(configFile, 'utf8'); - let result; + let result: any; + let format: string = ''; if (configFile.endsWith('.json5') || configFile.endsWith('.json')) { - result = require('json5').parse(data); + format = 'json'; + result = JsonFile.read(configFile, { json5: true }); } else if (configFile.endsWith('.js')) { + format = 'js'; result = require(configFile); if (result.default != null) { result = result.default; @@ -52,8 +78,12 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Tue, 10 Dec 2019 17:23:32 +0100 Subject: [PATCH 08/27] Add TS support --- packages/config/__mocks__/resolve-from.js | 37 ++++++++++--------- .../src/__tests__/ConfigParsing-test.js | 9 ++++- .../language-support/ts/app.config.ts | 7 ++++ .../fixtures/language-support/ts/package.json | 4 ++ .../language-support/ts/tsconfig.json | 11 ++++++ packages/config/src/getConfig.ts | 18 ++++++++- 6 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 packages/config/src/__tests__/fixtures/language-support/ts/app.config.ts create mode 100644 packages/config/src/__tests__/fixtures/language-support/ts/package.json create mode 100644 packages/config/src/__tests__/fixtures/language-support/ts/tsconfig.json diff --git a/packages/config/__mocks__/resolve-from.js b/packages/config/__mocks__/resolve-from.js index 80270367d6..6e9ac58c36 100644 --- a/packages/config/__mocks__/resolve-from.js +++ b/packages/config/__mocks__/resolve-from.js @@ -1,21 +1,24 @@ -module.exports = { - silent: (fromDirectory, request) => { - const fs = require('fs'); - const path = require('path'); +module.exports = require(require.resolve('resolve-from')); - try { - fromDirectory = fs.realpathSync(fromDirectory); - } catch (error) { - if (error.code === 'ENOENT') { - fromDirectory = path.resolve(fromDirectory); - } else { - return; - } - } +module.exports.silent = (fromDirectory, request) => { + const fs = require('fs'); + const path = require('path'); - const outputPath = path.join(fromDirectory, 'node_modules', request); - if (fs.existsSync(outputPath)) { - return outputPath; + // if (fromDirectory.includes('fixtures/language-support')) { + // return require(require.resolve('resolve-from'))(fromDirectory, request); + // } + try { + fromDirectory = fs.realpathSync(fromDirectory); + } catch (error) { + if (error.code === 'ENOENT') { + fromDirectory = path.resolve(fromDirectory); + } else { + return; } - }, + } + + const outputPath = path.join(fromDirectory, 'node_modules', request); + if (fs.existsSync(outputPath)) { + return outputPath; + } }; diff --git a/packages/config/src/__tests__/ConfigParsing-test.js b/packages/config/src/__tests__/ConfigParsing-test.js index 772e18735e..081fa28738 100644 --- a/packages/config/src/__tests__/ConfigParsing-test.js +++ b/packages/config/src/__tests__/ConfigParsing-test.js @@ -8,8 +8,15 @@ describe('getConfig', () => { // - ensure `app.config` has higher priority to `app` // - generated `.expo` object is created and the language hint is added describe('language support', () => { - // ensure config is composed (package.json values still exist) + it('parses a ts config', () => { + const projectRoot = path.resolve(__dirname, './fixtures/language-support/ts'); + const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + expect(exp.foo).toBe('bar'); + expect(exp.name).toBe('rewrote+ts-config-test'); + expect(exp._expo.configType).toBe('ts'); + }); it('parses a js config', () => { + // ensure config is composed (package.json values still exist) const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); expect(exp.foo).toBe('bar'); diff --git a/packages/config/src/__tests__/fixtures/language-support/ts/app.config.ts b/packages/config/src/__tests__/fixtures/language-support/ts/app.config.ts new file mode 100644 index 0000000000..8be4466eaa --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/ts/app.config.ts @@ -0,0 +1,7 @@ +import { ConfigContext, ExpoConfig } from '../../../../'; + +export default function({ config }: ConfigContext): ExpoConfig { + config.name = 'rewrote+' + config.name; + config.foo = 'bar'; + return config; +} diff --git a/packages/config/src/__tests__/fixtures/language-support/ts/package.json b/packages/config/src/__tests__/fixtures/language-support/ts/package.json new file mode 100644 index 0000000000..cba9b73432 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/ts/package.json @@ -0,0 +1,4 @@ +{ + "name": "ts-config-test", + "version": "1.0.0" +} diff --git a/packages/config/src/__tests__/fixtures/language-support/ts/tsconfig.json b/packages/config/src/__tests__/fixtures/language-support/ts/tsconfig.json new file mode 100644 index 0000000000..2ee1a98f42 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/language-support/ts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "jsx": "react-native", + "lib": ["dom", "esnext"], + "moduleResolution": "node", + "noEmit": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index bca6cd13e2..01eaf46a25 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -5,13 +5,14 @@ import path from 'path'; import JsonFile from '@expo/json-file'; import { ConfigContext, ExpoConfig } from './Config.types'; import { ConfigError } from './Errors'; -import { fileExists } from './Modules'; +import { fileExists, projectHasModule, resolveModule } from './Modules'; // support all common config types export const allowedConfigFileNames: string[] = (() => { const prefix = 'app'; return [ // order is important + `${prefix}.config.ts`, `${prefix}.config.js`, `${prefix}.config.json`, `${prefix}.config.json5`, @@ -68,6 +69,21 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Tue, 10 Dec 2019 20:29:43 +0100 Subject: [PATCH 09/27] Update Config.ts --- packages/config/src/Config.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 2212756dcc..630949757e 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -1,5 +1,4 @@ import JsonFile, { JSONObject } from '@expo/json-file'; -import fs from 'fs-extra'; import path from 'path'; import slug from 'slugify'; @@ -12,9 +11,9 @@ import { ProjectConfig, } from './Config.types'; import { ConfigError } from './Errors'; -import { getExpoSDKVersion } from './Project'; -import { getRootPackageJsonPath, projectHasModule } from './Modules'; import { findAndEvalConfig } from './getConfig'; +import { getRootPackageJsonPath, projectHasModule } from './Modules'; +import { getExpoSDKVersion } from './Project'; /** * Get all platforms that a project is currently capable of running. From 6d269240516d3b73e0b32a1bdd449877d1d85632 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Fri, 13 Dec 2019 11:29:38 +0100 Subject: [PATCH 10/27] Disable language support for yaml, toml, and TypeScript --- packages/config/package.json | 7 +--- .../src/__tests__/ConfigParsing-test.js | 6 +-- packages/config/src/getConfig.ts | 37 ++++++++++--------- yarn.lock | 10 ----- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/packages/config/package.json b/packages/config/package.json index c7bc6d4efc..f5b2c693a3 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -45,18 +45,15 @@ ], "dependencies": { "@expo/json-file": "^8.2.2", - "@types/invariant": "^2.2.30", "find-yarn-workspace-root": "^1.2.1", "fs-extra": "^7.0.1", "invariant": "^2.2.4", - "js-yaml": "^3.13.1", "resolve-from": "^5.0.0", - "slugify": "^1.3.4", - "toml": "^3.0.0" + "slugify": "^1.3.4" }, "devDependencies": { + "@types/invariant": "^2.2.30", "@expo/babel-preset-cli": "^0.2.2", - "@types/js-yaml": "^3.12.1", "memfs": "^2.15.5" }, "publishConfig": { diff --git a/packages/config/src/__tests__/ConfigParsing-test.js b/packages/config/src/__tests__/ConfigParsing-test.js index 081fa28738..0297a955ef 100644 --- a/packages/config/src/__tests__/ConfigParsing-test.js +++ b/packages/config/src/__tests__/ConfigParsing-test.js @@ -8,7 +8,7 @@ describe('getConfig', () => { // - ensure `app.config` has higher priority to `app` // - generated `.expo` object is created and the language hint is added describe('language support', () => { - it('parses a ts config', () => { + xit('parses a ts config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/ts'); const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); expect(exp.foo).toBe('bar'); @@ -39,13 +39,13 @@ describe('getConfig', () => { expect(exp.name).toBe('cool+export-json_app.config'); expect(exp._expo.configType).toBe('js'); }); - it('parses a yaml config', () => { + xit('parses a yaml config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/yaml'); const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); expect(exp.foo).toBe('bar'); expect(exp._expo.configType).toBe('yaml'); }); - it('parses a toml config', () => { + xit('parses a toml config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/toml'); const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); expect(exp.foo).toBe('bar'); diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 01eaf46a25..f2982ea866 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -1,24 +1,24 @@ -import fs from 'fs-extra'; -import { safeLoad } from 'js-yaml'; +import JsonFile from '@expo/json-file'; import path from 'path'; -import JsonFile from '@expo/json-file'; import { ConfigContext, ExpoConfig } from './Config.types'; import { ConfigError } from './Errors'; -import { fileExists, projectHasModule, resolveModule } from './Modules'; +import { fileExists } from './Modules'; // support all common config types export const allowedConfigFileNames: string[] = (() => { const prefix = 'app'; return [ // order is important - `${prefix}.config.ts`, + // TODO: Bacon: Slowly rollout support for other config languages: ts, yml, toml + // `${prefix}.config.ts`, `${prefix}.config.js`, `${prefix}.config.json`, `${prefix}.config.json5`, - `${prefix}.config.yml`, - `${prefix}.config.yaml`, - `${prefix}.config.toml`, + // `${prefix}.config.yml`, + // `${prefix}.config.yaml`, + // `${prefix}.config.toml`, + // app.json should take lowest priority so that files like app.config.js can import, modify, and re-export the app.json config `${prefix}.json`, ]; @@ -69,6 +69,17 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Fri, 13 Dec 2019 11:32:41 +0100 Subject: [PATCH 11/27] Update resolve-from.js --- packages/config/__mocks__/resolve-from.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/config/__mocks__/resolve-from.js b/packages/config/__mocks__/resolve-from.js index 6e9ac58c36..7aeef4e7dd 100644 --- a/packages/config/__mocks__/resolve-from.js +++ b/packages/config/__mocks__/resolve-from.js @@ -4,9 +4,6 @@ module.exports.silent = (fromDirectory, request) => { const fs = require('fs'); const path = require('path'); - // if (fromDirectory.includes('fixtures/language-support')) { - // return require(require.resolve('resolve-from'))(fromDirectory, request); - // } try { fromDirectory = fs.realpathSync(fromDirectory); } catch (error) { From 7141f69016ce9ee87f3f8d88c23b92a1e0150140 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Thu, 2 Jan 2020 22:53:30 -0800 Subject: [PATCH 12/27] revert auto-format changes --- packages/config/src/Web.ts | 6 ++---- packages/expo-cli/src/commands/fetch/android.js | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/config/src/Web.ts b/packages/config/src/Web.ts index 726cde9b27..754d57cb5d 100644 --- a/packages/config/src/Web.ts +++ b/packages/config/src/Web.ts @@ -143,10 +143,8 @@ function applyWebDefaults(appJSON: AppJSONConfig | ExpoConfig): ExpoConfig { * >> The banner won't show up if the app is already installed: * https://github.com/GoogleChrome/samples/issues/384#issuecomment-326387680 */ - const preferRelatedApplications = - webManifest.preferRelatedApplications === undefined - ? DEFAULT_PREFER_RELATED_APPLICATIONS - : webManifest.preferRelatedApplications; + const preferRelatedApplications = + webManifest.preferRelatedApplications === undefined ? DEFAULT_PREFER_RELATED_APPLICATIONS : webManifest.preferRelatedApplications; const relatedApplications = inferWebRelatedApplicationsFromConfig(appManifest); diff --git a/packages/expo-cli/src/commands/fetch/android.js b/packages/expo-cli/src/commands/fetch/android.js index 72b51979af..d7b3e42bee 100644 --- a/packages/expo-cli/src/commands/fetch/android.js +++ b/packages/expo-cli/src/commands/fetch/android.js @@ -33,9 +33,7 @@ async function fetchAndroidHashesAsync(projectDir) { keystorePassword: get(view, 'credentials.keystorePassword'), keyAlias: get(view, 'credentials.keyAlias'), }); - log( - `\nNote: if you are using Google Play signing, this app will be signed with a different key after publishing to the store, and you'll need to use the hashes displayed in the Google Play console.` - ); + log(`\nNote: if you are using Google Play signing, this app will be signed with a different key after publishing to the store, and you'll need to use the hashes displayed in the Google Play console.`); } finally { try { fs.unlink(outputPath); From 5228dd48a1e6c49eeb378922acd24d2df6e5a3f3 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Fri, 3 Jan 2020 20:59:53 -0800 Subject: [PATCH 13/27] serialize config --- packages/config/src/Config.ts | 39 +++++++++++++++---- packages/config/src/__tests__/Config-test.js | 20 +++++++++- .../__snapshots__/Config-test.js.snap | 27 +++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 packages/config/src/__tests__/__snapshots__/Config-test.js.snap diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index b10d9fce94..7e6b72c4be 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -35,6 +35,27 @@ function getSupportedPlatforms( return platforms; } +export function serialize(val: any): any { + if (['undefined', 'string', 'boolean', 'number'].includes(typeof val)) { + return val; + } else if (typeof val === 'function') { + // TODO: Bacon: Should we support async methods? + return val(); + } else if (Array.isArray(val)) { + return val.map(serialize); + } else if (typeof val === 'object') { + const output: { [key: string]: any } = {}; + for (const property in val) { + if (val.hasOwnProperty(property)) { + output[property] = serialize(val[property]); + } + } + return output; + } + + throw new Error(`Unhandled item type: ${typeof val}`); +} + export function getConfig( projectRoot: string, options: { configPath?: string; skipSDKVersionRequirement?: boolean } @@ -56,13 +77,15 @@ export function getConfig( const finalConfig = config || context.config; + const configs = ensureConfigHasDefaultValues( + projectRoot, + finalConfig, + pkg, + options.skipSDKVersionRequirement + ); return { - ...ensureConfigHasDefaultValues( - projectRoot, - finalConfig, - pkg, - options.skipSDKVersionRequirement - ), + ...configs, + exp: serialize(configs.exp), rootConfig: finalConfig as AppJSONConfig, }; } @@ -81,8 +104,10 @@ export function readConfigJson( const packageJsonPath = getRootPackageJsonPath(projectRoot, exp); const pkg = JsonFile.read(packageJsonPath); + const configs = ensureConfigHasDefaultValues(projectRoot, exp, pkg, skipNativeValidation); return { - ...ensureConfigHasDefaultValues(projectRoot, exp, pkg, skipNativeValidation), + ...configs, + exp: serialize(configs.exp), rootConfig: rootConfig as AppJSONConfig, }; } diff --git a/packages/config/src/__tests__/Config-test.js b/packages/config/src/__tests__/Config-test.js index 0d86885f75..37da623bd5 100644 --- a/packages/config/src/__tests__/Config-test.js +++ b/packages/config/src/__tests__/Config-test.js @@ -1,6 +1,6 @@ import { vol } from 'memfs'; -import { readConfigJson } from '../Config'; +import { readConfigJson, serialize } from '../Config'; jest.mock('fs'); jest.mock('resolve-from'); @@ -62,6 +62,24 @@ describe('readConfigJson', () => { }); }); + describe('serializing', () => { + it(`serializes item`, () => { + expect( + serialize({ + foo: 'bar', + boo: true, + inn: 200, + then: [true, { foo: 'bar' }], + fun: () => ({ time: ['val'] }), + last: { + bar: 'foo', + kid: [2, 'yo'], + }, + }) + ).toMatchSnapshot(); + }); + }); + describe('validation', () => { beforeAll(() => { const packageJson = JSON.stringify({ diff --git a/packages/config/src/__tests__/__snapshots__/Config-test.js.snap b/packages/config/src/__tests__/__snapshots__/Config-test.js.snap new file mode 100644 index 0000000000..9dbeee16fe --- /dev/null +++ b/packages/config/src/__tests__/__snapshots__/Config-test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`readConfigJson serializing serializes item 1`] = ` +Object { + "boo": true, + "foo": "bar", + "fun": Object { + "time": Array [ + "val", + ], + }, + "inn": 200, + "last": Object { + "bar": "foo", + "kid": Array [ + 2, + "yo", + ], + }, + "then": Array [ + true, + Object { + "foo": "bar", + }, + ], +} +`; From 1d438de0736c1d2466516d9a19cc814a8045a8f1 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 6 Jan 2020 16:38:00 -0800 Subject: [PATCH 14/27] Pass the app.json into the app.config.js method --- packages/config/src/Config.ts | 11 +++++++++-- packages/config/src/__tests__/Config-test.js | 11 +++++++++-- packages/config/src/__tests__/ConfigParsing-test.js | 2 ++ .../fixtures/language-support/js/app.config.js | 1 + .../__tests__/fixtures/language-support/js/app.json | 3 ++- packages/config/src/getConfig.ts | 3 --- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 2183732ec5..903fb4a807 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -58,7 +58,7 @@ export function serialize(val: any): any { export function getConfig( projectRoot: string, - options: { configPath?: string; skipSDKVersionRequirement?: boolean } + options: { configPath?: string; skipSDKVersionRequirement?: boolean } = {} ): ProjectConfig { // TODO(Bacon): This doesn't support changing the location of the package.json const packageJsonPath = getRootPackageJsonPath(projectRoot, {}); @@ -66,7 +66,14 @@ export function getConfig( const configPath = options.configPath || customConfigPaths[projectRoot]; - const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, {}, pkg, true); + // If the app.json exists, we'll read it and pass it to the app.config.js for further modification + const { configPath: appJsonConfigPath } = findConfigFile(projectRoot); + let rawConfig: JSONObject = {}; + try { + rawConfig = JsonFile.read(appJsonConfigPath, { json5: true }); + } catch (_) {} + + const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, rawConfig, pkg, true); const context = { projectRoot, diff --git a/packages/config/src/__tests__/Config-test.js b/packages/config/src/__tests__/Config-test.js index 37da623bd5..fb008b2e2d 100644 --- a/packages/config/src/__tests__/Config-test.js +++ b/packages/config/src/__tests__/Config-test.js @@ -1,6 +1,6 @@ import { vol } from 'memfs'; -import { readConfigJson, serialize } from '../Config'; +import { getConfig, readConfigJson, serialize } from '../Config'; jest.mock('fs'); jest.mock('resolve-from'); @@ -115,13 +115,20 @@ describe('readConfigJson', () => { }); it(`will throw if the app.json is missing`, () => { - expect(() => readConfigJson('/no-config')).toThrow(/app.json must include a JSON object./); + expect(() => readConfigJson('/no-config')).toThrow( + /Project at path \/no-config does not contain a valid app.json/ + ); + // No config is required for new method + expect(() => getConfig('/no-config')).not.toThrow(); }); it(`will throw if the expo package is missing`, () => { expect(() => readConfigJson('/no-package', true)).toThrow( /Cannot determine which native SDK version your project uses/ ); + expect(() => getConfig('/no-package', { skipSDKVersionRequirement: false })).toThrow( + /Cannot determine which native SDK version your project uses/ + ); }); }); }); diff --git a/packages/config/src/__tests__/ConfigParsing-test.js b/packages/config/src/__tests__/ConfigParsing-test.js index 0297a955ef..564a63940c 100644 --- a/packages/config/src/__tests__/ConfigParsing-test.js +++ b/packages/config/src/__tests__/ConfigParsing-test.js @@ -21,6 +21,8 @@ describe('getConfig', () => { const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); expect(exp.foo).toBe('bar'); expect(exp.name).toBe('js-config-test+config'); + // Ensures that the app.json is read and passed to the method + expect(exp.slug).toBe('someslug+config'); expect(exp._expo.configType).toBe('js'); }); it('parses a js config with export default', () => { diff --git a/packages/config/src/__tests__/fixtures/language-support/js/app.config.js b/packages/config/src/__tests__/fixtures/language-support/js/app.config.js index dff9f78aec..82cb223ae3 100644 --- a/packages/config/src/__tests__/fixtures/language-support/js/app.config.js +++ b/packages/config/src/__tests__/fixtures/language-support/js/app.config.js @@ -1,5 +1,6 @@ module.exports = function({ config }) { config.foo = 'bar'; if (config.name) config.name += '+config'; + if (config.slug) config.slug += '+config'; return config; }; diff --git a/packages/config/src/__tests__/fixtures/language-support/js/app.json b/packages/config/src/__tests__/fixtures/language-support/js/app.json index 871feffd90..75d8cede7c 100644 --- a/packages/config/src/__tests__/fixtures/language-support/js/app.json +++ b/packages/config/src/__tests__/fixtures/language-support/js/app.json @@ -1,3 +1,4 @@ { - "foo": "invalid" + "foo": "invalid", + "slug": "someslug" } diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index f2982ea866..72bfa3f38c 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -18,9 +18,6 @@ export const allowedConfigFileNames: string[] = (() => { // `${prefix}.config.yml`, // `${prefix}.config.yaml`, // `${prefix}.config.toml`, - - // app.json should take lowest priority so that files like app.config.js can import, modify, and re-export the app.json config - `${prefix}.json`, ]; })(); From 367b9cf8a9a1bdd7f1283999540ffc3c737a683c Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 6 Jan 2020 16:54:02 -0800 Subject: [PATCH 15/27] Added mode option to config --- packages/config/src/Config.ts | 37 ++++++++++++++++- packages/config/src/Config.types.ts | 3 ++ packages/config/src/__tests__/Config-test.js | 11 +++-- .../src/__tests__/ConfigParsing-test.js | 40 +++++++++++++++---- .../language-support/js/app.config.js | 4 +- 5 files changed, 80 insertions(+), 15 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 903fb4a807..bd91a8e11a 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -56,10 +56,44 @@ export function serialize(val: any): any { throw new Error(`Unhandled item type: ${typeof val}`); } +/** + * Evaluate the config for an Expo project. + * If a function is exported from the `app.config.js` then a partial config will be passed as an argument. + * The partial config is composed from any existing app.json, and certain fields from the `package.json` like name and description. + * + * You should use the supplied `mode` option in an `app.config.js` instead of `process.env.NODE_ENV` + * + * **Example** + * ```js + * module.exports = function({ config, mode }) { + * // mutate the config before returning it. + * config.slug = 'new slug' + * return config; + * } + * + * **Supports** + * - `app.config.js` + * - `app.config.json` + * - `app.json` + * + * @param projectRoot the root folder containing all of your application code + * @param options enforce criteria for a project config + */ export function getConfig( projectRoot: string, - options: { configPath?: string; skipSDKVersionRequirement?: boolean } = {} + options: { + mode: 'development' | 'production'; + configPath?: string; + skipSDKVersionRequirement?: boolean; + } ): ProjectConfig { + if (!['development', 'production'].includes(options.mode)) { + throw new ConfigError( + `Invalid mode "${options.mode}" was used to evaluate the project config.`, + 'INVALID_MODE' + ); + } + // TODO(Bacon): This doesn't support changing the location of the package.json const packageJsonPath = getRootPackageJsonPath(projectRoot, {}); const pkg = JsonFile.read(packageJsonPath); @@ -76,6 +110,7 @@ export function getConfig( const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, rawConfig, pkg, true); const context = { + mode: options.mode, projectRoot, configPath, config: configFromPkg, diff --git a/packages/config/src/Config.types.ts b/packages/config/src/Config.types.ts index cd04c2406c..1b9c61cfd4 100644 --- a/packages/config/src/Config.types.ts +++ b/packages/config/src/Config.types.ts @@ -920,9 +920,12 @@ export type ConfigErrorCode = | 'NOT_OBJECT' | 'NO_EXPO' | 'MODULE_NOT_FOUND' + | 'INVALID_MODE' | 'INVALID_CONFIG'; + export type ConfigContext = { projectRoot: string; configPath?: string; config: Partial; + mode: 'development' | 'production'; }; diff --git a/packages/config/src/__tests__/Config-test.js b/packages/config/src/__tests__/Config-test.js index fb008b2e2d..5a4d6b7c10 100644 --- a/packages/config/src/__tests__/Config-test.js +++ b/packages/config/src/__tests__/Config-test.js @@ -119,16 +119,19 @@ describe('readConfigJson', () => { /Project at path \/no-config does not contain a valid app.json/ ); // No config is required for new method - expect(() => getConfig('/no-config')).not.toThrow(); + expect(() => getConfig('/no-config', { mode: 'development' })).not.toThrow(); }); it(`will throw if the expo package is missing`, () => { expect(() => readConfigJson('/no-package', true)).toThrow( /Cannot determine which native SDK version your project uses/ ); - expect(() => getConfig('/no-package', { skipSDKVersionRequirement: false })).toThrow( - /Cannot determine which native SDK version your project uses/ - ); + expect(() => + getConfig('/no-package', { mode: 'development', skipSDKVersionRequirement: false }) + ).toThrow(/Cannot determine which native SDK version your project uses/); + }); + it(`will throw if an invalid mode is used to get the config`, () => { + expect(() => getConfig('/no-config', { mode: 'invalid' })).toThrow(/Invalid mode/); }); }); }); diff --git a/packages/config/src/__tests__/ConfigParsing-test.js b/packages/config/src/__tests__/ConfigParsing-test.js index 564a63940c..73692f3e5c 100644 --- a/packages/config/src/__tests__/ConfigParsing-test.js +++ b/packages/config/src/__tests__/ConfigParsing-test.js @@ -10,7 +10,10 @@ describe('getConfig', () => { describe('language support', () => { xit('parses a ts config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/ts'); - const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + const { exp } = getConfig(projectRoot, { + mode: 'development', + skipSDKVersionRequirement: true, + }); expect(exp.foo).toBe('bar'); expect(exp.name).toBe('rewrote+ts-config-test'); expect(exp._expo.configType).toBe('ts'); @@ -18,9 +21,13 @@ describe('getConfig', () => { it('parses a js config', () => { // ensure config is composed (package.json values still exist) const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); - const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + const { exp } = getConfig(projectRoot, { + mode: 'development', + skipSDKVersionRequirement: true, + }); expect(exp.foo).toBe('bar'); - expect(exp.name).toBe('js-config-test+config'); + // Ensure the config is passed the package.json values and mode + expect(exp.name).toBe('js-config-test+config+development'); // Ensures that the app.json is read and passed to the method expect(exp.slug).toBe('someslug+config'); expect(exp._expo.configType).toBe('js'); @@ -28,7 +35,11 @@ describe('getConfig', () => { it('parses a js config with export default', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); const configPath = path.resolve(projectRoot, 'with-default_app.config.js'); - const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true, configPath }); + const { exp } = getConfig(projectRoot, { + mode: 'development', + skipSDKVersionRequirement: true, + configPath, + }); expect(exp.foo).toBe('bar'); expect(exp.name).toBe('js-config-test+config-default'); expect(exp._expo.configType).toBe('js'); @@ -36,26 +47,39 @@ describe('getConfig', () => { it('parses a js config that exports json', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); const configPath = path.resolve(projectRoot, 'export-json_app.config.js'); - const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true, configPath }); + const { exp } = getConfig(projectRoot, { + mode: 'development', + skipSDKVersionRequirement: true, + configPath, + }); expect(exp.foo).toBe('bar'); expect(exp.name).toBe('cool+export-json_app.config'); expect(exp._expo.configType).toBe('js'); }); xit('parses a yaml config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/yaml'); - const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + const { exp } = getConfig(projectRoot, { + mode: 'development', + skipSDKVersionRequirement: true, + }); expect(exp.foo).toBe('bar'); expect(exp._expo.configType).toBe('yaml'); }); xit('parses a toml config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/toml'); - const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + const { exp } = getConfig(projectRoot, { + mode: 'development', + skipSDKVersionRequirement: true, + }); expect(exp.foo).toBe('bar'); expect(exp._expo.configType).toBe('toml'); }); it('parses a json5 config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/json5'); - const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); + const { exp } = getConfig(projectRoot, { + mode: 'development', + skipSDKVersionRequirement: true, + }); expect(exp.foo).toBe('bar'); expect(exp._expo.configType).toBe('json'); }); diff --git a/packages/config/src/__tests__/fixtures/language-support/js/app.config.js b/packages/config/src/__tests__/fixtures/language-support/js/app.config.js index 82cb223ae3..3ba001b547 100644 --- a/packages/config/src/__tests__/fixtures/language-support/js/app.config.js +++ b/packages/config/src/__tests__/fixtures/language-support/js/app.config.js @@ -1,6 +1,6 @@ -module.exports = function({ config }) { +module.exports = function({ config, mode }) { config.foo = 'bar'; - if (config.name) config.name += '+config'; + if (config.name) config.name += '+config+' + mode; if (config.slug) config.slug += '+config'; return config; }; From 537c5de8b005337ee3278bd57efd6203888558b9 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 13 Jan 2020 18:13:10 -0800 Subject: [PATCH 16/27] Remove generated --- packages/config/src/getConfig.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 72bfa3f38c..9ec253a0a3 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -109,10 +109,5 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Mon, 13 Jan 2020 18:29:41 -0800 Subject: [PATCH 17/27] Move serialize --- packages/config/src/Config.ts | 39 ++++--------------- .../config/src/__tests__/getConfig-test.js | 19 +++++++++ packages/config/src/getConfig.ts | 28 ++++++++++--- 3 files changed, 49 insertions(+), 37 deletions(-) create mode 100644 packages/config/src/__tests__/getConfig-test.js diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index bd91a8e11a..894889752d 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -35,27 +35,6 @@ function getSupportedPlatforms( return platforms; } -export function serialize(val: any): any { - if (['undefined', 'string', 'boolean', 'number'].includes(typeof val)) { - return val; - } else if (typeof val === 'function') { - // TODO: Bacon: Should we support async methods? - return val(); - } else if (Array.isArray(val)) { - return val.map(serialize); - } else if (typeof val === 'object') { - const output: { [key: string]: any } = {}; - for (const property in val) { - if (val.hasOwnProperty(property)) { - output[property] = serialize(val[property]); - } - } - return output; - } - - throw new Error(`Unhandled item type: ${typeof val}`); -} - /** * Evaluate the config for an Expo project. * If a function is exported from the `app.config.js` then a partial config will be passed as an argument. @@ -119,15 +98,13 @@ export function getConfig( const finalConfig = config || context.config; - const configs = ensureConfigHasDefaultValues( - projectRoot, - finalConfig, - pkg, - options.skipSDKVersionRequirement - ); return { - ...configs, - exp: serialize(configs.exp), + ...ensureConfigHasDefaultValues( + projectRoot, + finalConfig, + pkg, + options.skipSDKVersionRequirement + ), rootConfig: finalConfig as AppJSONConfig, }; } @@ -146,10 +123,8 @@ export function readConfigJson( const packageJsonPath = getRootPackageJsonPath(projectRoot, exp); const pkg = JsonFile.read(packageJsonPath); - const configs = ensureConfigHasDefaultValues(projectRoot, exp, pkg, skipNativeValidation); return { - ...configs, - exp: serialize(configs.exp), + ...ensureConfigHasDefaultValues(projectRoot, exp, pkg, skipNativeValidation), rootConfig: rootConfig as AppJSONConfig, }; } diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js new file mode 100644 index 0000000000..43acb61ecc --- /dev/null +++ b/packages/config/src/__tests__/getConfig-test.js @@ -0,0 +1,19 @@ +import { serializeAndEvaluate } from '../getConfig'; + +describe('serializeAndEvaluate', () => { + it(`serializes item`, () => { + expect( + serializeAndEvaluate({ + foo: 'bar', + boo: true, + inn: 200, + then: [true, { foo: 'bar' }], + fun: () => ({ time: ['val'] }), + last: { + bar: 'foo', + kid: [2, 'yo'], + }, + }) + ).toMatchSnapshot(); + }); +}); diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 9ec253a0a3..e7eca7c51c 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -40,7 +40,7 @@ export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { if (request.configPath) { const config = testFileName(request.configPath); if (config) { - return config; + return serializeAndEvaluate(config); } else { throw new ConfigError( `Config with custom path ${request.configPath} couldn't be parsed.`, @@ -52,7 +52,7 @@ export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { for (const configFileName of allowedConfigFileNames) { const configFilePath = path.join(request.projectRoot, configFileName); const config = testFileName(configFilePath); - if (config) return config; + if (config) return serializeAndEvaluate(config); } return null; @@ -62,12 +62,9 @@ export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { // If they don't add support for async Webpack configs then we may need to pull support for Next.js. function evalConfig(configFile: string, request: ConfigContext): Partial { let result: any; - let format: string = ''; if (configFile.endsWith('.json5') || configFile.endsWith('.json')) { - format = 'json'; result = JsonFile.read(configFile, { json5: true }); } else { - format = 'js'; result = require(configFile); if (result.default != null) { result = result.default; @@ -111,3 +108,24 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Mon, 13 Jan 2020 18:38:39 -0800 Subject: [PATCH 18/27] Update ConfigParsing-test.js --- packages/config/src/__tests__/ConfigParsing-test.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/config/src/__tests__/ConfigParsing-test.js b/packages/config/src/__tests__/ConfigParsing-test.js index 73692f3e5c..af70f5aa35 100644 --- a/packages/config/src/__tests__/ConfigParsing-test.js +++ b/packages/config/src/__tests__/ConfigParsing-test.js @@ -16,7 +16,6 @@ describe('getConfig', () => { }); expect(exp.foo).toBe('bar'); expect(exp.name).toBe('rewrote+ts-config-test'); - expect(exp._expo.configType).toBe('ts'); }); it('parses a js config', () => { // ensure config is composed (package.json values still exist) @@ -30,7 +29,6 @@ describe('getConfig', () => { expect(exp.name).toBe('js-config-test+config+development'); // Ensures that the app.json is read and passed to the method expect(exp.slug).toBe('someslug+config'); - expect(exp._expo.configType).toBe('js'); }); it('parses a js config with export default', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); @@ -42,7 +40,6 @@ describe('getConfig', () => { }); expect(exp.foo).toBe('bar'); expect(exp.name).toBe('js-config-test+config-default'); - expect(exp._expo.configType).toBe('js'); }); it('parses a js config that exports json', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/js'); @@ -54,7 +51,6 @@ describe('getConfig', () => { }); expect(exp.foo).toBe('bar'); expect(exp.name).toBe('cool+export-json_app.config'); - expect(exp._expo.configType).toBe('js'); }); xit('parses a yaml config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/yaml'); @@ -63,7 +59,6 @@ describe('getConfig', () => { skipSDKVersionRequirement: true, }); expect(exp.foo).toBe('bar'); - expect(exp._expo.configType).toBe('yaml'); }); xit('parses a toml config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/toml'); @@ -72,7 +67,6 @@ describe('getConfig', () => { skipSDKVersionRequirement: true, }); expect(exp.foo).toBe('bar'); - expect(exp._expo.configType).toBe('toml'); }); it('parses a json5 config', () => { const projectRoot = path.resolve(__dirname, './fixtures/language-support/json5'); @@ -81,7 +75,6 @@ describe('getConfig', () => { skipSDKVersionRequirement: true, }); expect(exp.foo).toBe('bar'); - expect(exp._expo.configType).toBe('json'); }); }); }); From 1c16f651b1aba6e510dca17ef1a98f7014c51e4a Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 13 Jan 2020 18:44:49 -0800 Subject: [PATCH 19/27] Update Config.ts --- packages/config/src/Config.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 894889752d..dc2104eed0 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -94,18 +94,12 @@ export function getConfig( configPath, config: configFromPkg, }; - const config = findAndEvalConfig(context); - const finalConfig = config || context.config; + const config = findAndEvalConfig(context) ?? configFromPkg; return { - ...ensureConfigHasDefaultValues( - projectRoot, - finalConfig, - pkg, - options.skipSDKVersionRequirement - ), - rootConfig: finalConfig as AppJSONConfig, + ...ensureConfigHasDefaultValues(projectRoot, config, pkg, options.skipSDKVersionRequirement), + rootConfig: config as AppJSONConfig, }; } From f2ddf981d6105140d02dc564a4c771e776b84976 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 13 Jan 2020 18:46:47 -0800 Subject: [PATCH 20/27] Remove support for yaml, toml, ts --- packages/config/src/getConfig.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index e7eca7c51c..bf0dc8b71b 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -11,13 +11,9 @@ export const allowedConfigFileNames: string[] = (() => { return [ // order is important // TODO: Bacon: Slowly rollout support for other config languages: ts, yml, toml - // `${prefix}.config.ts`, `${prefix}.config.js`, `${prefix}.config.json`, `${prefix}.config.json5`, - // `${prefix}.config.yml`, - // `${prefix}.config.yaml`, - // `${prefix}.config.toml`, ]; })(); @@ -73,34 +69,6 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Mon, 13 Jan 2020 19:00:26 -0800 Subject: [PATCH 21/27] Updated tests --- packages/config/src/__tests__/Config-test.js | 20 +------------------ ...ig-test.js.snap => getConfig-test.js.snap} | 14 ++++++------- .../config/src/__tests__/getConfig-test.js | 4 ++-- 3 files changed, 10 insertions(+), 28 deletions(-) rename packages/config/src/__tests__/__snapshots__/{Config-test.js.snap => getConfig-test.js.snap} (66%) diff --git a/packages/config/src/__tests__/Config-test.js b/packages/config/src/__tests__/Config-test.js index 5a95822cf8..db940572ea 100644 --- a/packages/config/src/__tests__/Config-test.js +++ b/packages/config/src/__tests__/Config-test.js @@ -1,6 +1,6 @@ import { vol } from 'memfs'; -import { getConfig, readConfigJson, serialize } from '../Config'; +import { getConfig, readConfigJson } from '../Config'; jest.mock('fs'); jest.mock('resolve-from'); @@ -62,24 +62,6 @@ describe('readConfigJson', () => { }); }); - describe('serializing', () => { - it(`serializes item`, () => { - expect( - serialize({ - foo: 'bar', - boo: true, - inn: 200, - then: [true, { foo: 'bar' }], - fun: () => ({ time: ['val'] }), - last: { - bar: 'foo', - kid: [2, 'yo'], - }, - }) - ).toMatchSnapshot(); - }); - }); - describe('validation', () => { beforeAll(() => { const packageJson = JSON.stringify({ diff --git a/packages/config/src/__tests__/__snapshots__/Config-test.js.snap b/packages/config/src/__tests__/__snapshots__/getConfig-test.js.snap similarity index 66% rename from packages/config/src/__tests__/__snapshots__/Config-test.js.snap rename to packages/config/src/__tests__/__snapshots__/getConfig-test.js.snap index 9dbeee16fe..d378df1a0b 100644 --- a/packages/config/src/__tests__/__snapshots__/Config-test.js.snap +++ b/packages/config/src/__tests__/__snapshots__/getConfig-test.js.snap @@ -1,20 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`readConfigJson serializing serializes item 1`] = ` +exports[`serializeAndEvaluate serializes item 1`] = ` Object { - "boo": true, - "foo": "bar", - "fun": Object { - "time": Array [ + "alpha": Object { + "beta": Array [ "val", ], }, + "boo": true, + "foo": "bar", "inn": 200, "last": Object { "bar": "foo", - "kid": Array [ + "charlie": Array [ 2, - "yo", + "delta", ], }, "then": Array [ diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index 43acb61ecc..3fb8aa1469 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -8,10 +8,10 @@ describe('serializeAndEvaluate', () => { boo: true, inn: 200, then: [true, { foo: 'bar' }], - fun: () => ({ time: ['val'] }), + alpha: () => ({ beta: ['val'] }), last: { bar: 'foo', - kid: [2, 'yo'], + charlie: [2, 'delta'], }, }) ).toMatchSnapshot(); From 09c3322c175d4a4fc335453dd947ea42eecbb14c Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 13 Jan 2020 19:06:36 -0800 Subject: [PATCH 22/27] Move context resolver to another method --- packages/config/src/Config.ts | 69 ++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index dc2104eed0..2bb45a270c 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -35,6 +35,42 @@ function getSupportedPlatforms( return platforms; } +type GetConfigOptions = { + mode: 'development' | 'production'; + configPath?: string; + skipSDKVersionRequirement?: boolean; +}; + +function getConfigContext( + projectRoot: string, + options: GetConfigOptions +): { context: ConfigContext; pkg: JSONObject } { + // TODO(Bacon): This doesn't support changing the location of the package.json + const packageJsonPath = getRootPackageJsonPath(projectRoot, {}); + const pkg = JsonFile.read(packageJsonPath); + + const configPath = options.configPath || customConfigPaths[projectRoot]; + + // If the app.json exists, we'll read it and pass it to the app.config.js for further modification + const { configPath: appJsonConfigPath } = findConfigFile(projectRoot); + let rawConfig: JSONObject = {}; + try { + rawConfig = JsonFile.read(appJsonConfigPath, { json5: true }); + } catch (_) {} + + const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, rawConfig, pkg, true); + + return { + pkg, + context: { + mode: options.mode, + projectRoot, + configPath, + config: configFromPkg, + }, + }; +} + /** * Evaluate the config for an Expo project. * If a function is exported from the `app.config.js` then a partial config will be passed as an argument. @@ -58,14 +94,7 @@ function getSupportedPlatforms( * @param projectRoot the root folder containing all of your application code * @param options enforce criteria for a project config */ -export function getConfig( - projectRoot: string, - options: { - mode: 'development' | 'production'; - configPath?: string; - skipSDKVersionRequirement?: boolean; - } -): ProjectConfig { +export function getConfig(projectRoot: string, options: GetConfigOptions): ProjectConfig { if (!['development', 'production'].includes(options.mode)) { throw new ConfigError( `Invalid mode "${options.mode}" was used to evaluate the project config.`, @@ -73,29 +102,9 @@ export function getConfig( ); } - // TODO(Bacon): This doesn't support changing the location of the package.json - const packageJsonPath = getRootPackageJsonPath(projectRoot, {}); - const pkg = JsonFile.read(packageJsonPath); - - const configPath = options.configPath || customConfigPaths[projectRoot]; - - // If the app.json exists, we'll read it and pass it to the app.config.js for further modification - const { configPath: appJsonConfigPath } = findConfigFile(projectRoot); - let rawConfig: JSONObject = {}; - try { - rawConfig = JsonFile.read(appJsonConfigPath, { json5: true }); - } catch (_) {} - - const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, rawConfig, pkg, true); - - const context = { - mode: options.mode, - projectRoot, - configPath, - config: configFromPkg, - }; + const { context, pkg } = getConfigContext(projectRoot, options); - const config = findAndEvalConfig(context) ?? configFromPkg; + const config = findAndEvalConfig(context) ?? context.config; return { ...ensureConfigHasDefaultValues(projectRoot, config, pkg, options.skipSDKVersionRequirement), From 0456477747899c3606c6095bb5b0ce1c4ddb8164 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Mon, 13 Jan 2020 19:10:26 -0800 Subject: [PATCH 23/27] import ConfigContext --- packages/config/src/Config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 2bb45a270c..cc19be98dd 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -4,6 +4,7 @@ import slug from 'slugify'; import { AppJSONConfig, + ConfigContext, ExpRc, ExpoConfig, PackageJSONConfig, From 7e5cefd1bd0d93fd5460855115896a41141910a7 Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Wed, 29 Jan 2020 17:01:31 -0800 Subject: [PATCH 24/27] Format error messages for syntax errors in JS --- packages/config/package.json | 4 +- packages/config/src/Errors.ts | 11 +- .../behavior/syntax-error/app.config.js | 4 + .../config/src/__tests__/getConfig-test.js | 16 +- packages/config/src/getConfig.ts | 29 +- yarn.lock | 374 +++++++++++++++++- 6 files changed, 426 insertions(+), 12 deletions(-) create mode 100644 packages/config/src/__tests__/fixtures/behavior/syntax-error/app.config.js diff --git a/packages/config/package.json b/packages/config/package.json index dfb5b3730d..eda23937e4 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -44,17 +44,19 @@ "paths" ], "dependencies": { + "@babel/register": "^7.8.3", "@expo/json-file": "^8.2.5", "@types/invariant": "^2.2.30", "find-yarn-workspace-root": "^1.2.1", "fs-extra": "^7.0.1", "invariant": "^2.2.4", + "jest-message-util": "^25.1.0", "resolve-from": "^5.0.0", "slugify": "^1.3.4" }, "devDependencies": { - "@types/invariant": "^2.2.30", "@expo/babel-preset-cli": "^0.2.5", + "@types/invariant": "^2.2.30", "memfs": "^2.15.5" }, "publishConfig": { diff --git a/packages/config/src/Errors.ts b/packages/config/src/Errors.ts index 2af4f6307b..40eb53f986 100644 --- a/packages/config/src/Errors.ts +++ b/packages/config/src/Errors.ts @@ -1,10 +1,11 @@ import { ConfigErrorCode } from './Config.types'; +/** + * Based on `JsonFileError` from `@expo/json-file` + */ export class ConfigError extends Error { - code: ConfigErrorCode; - - constructor(message: string, code: ConfigErrorCode) { - super(message); - this.code = code; + constructor(message: string, public code: ConfigErrorCode, public cause?: Error) { + super(cause ? `${message}\nā””ā”€ Cause: ${cause.name}: ${cause.message}` : message); + this.name = this.constructor.name; } } diff --git a/packages/config/src/__tests__/fixtures/behavior/syntax-error/app.config.js b/packages/config/src/__tests__/fixtures/behavior/syntax-error/app.config.js new file mode 100644 index 0000000000..bc3bf24af6 --- /dev/null +++ b/packages/config/src/__tests__/fixtures/behavior/syntax-error/app.config.js @@ -0,0 +1,4 @@ +/* eslint-disable */ +module.exports = { + > +} diff --git a/packages/config/src/__tests__/getConfig-test.js b/packages/config/src/__tests__/getConfig-test.js index 3fb8aa1469..c0d35ea620 100644 --- a/packages/config/src/__tests__/getConfig-test.js +++ b/packages/config/src/__tests__/getConfig-test.js @@ -1,4 +1,6 @@ -import { serializeAndEvaluate } from '../getConfig'; +import { join } from 'path'; + +import { findAndEvalConfig, serializeAndEvaluate } from '../getConfig'; describe('serializeAndEvaluate', () => { it(`serializes item`, () => { @@ -17,3 +19,15 @@ describe('serializeAndEvaluate', () => { ).toMatchSnapshot(); }); }); + +describe('findAndEvalConfig', () => { + it(`throws a useful error for JS configs with a syntax error`, () => { + expect(() => + findAndEvalConfig({ + mode: 'development', + projectRoot: join(__dirname, 'fixtures/behavior/syntax-error'), + config: {}, + }) + ).toThrowError('Unexpected token (3:4)'); + }); +}); diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index bf0dc8b71b..2c025f45aa 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -1,4 +1,5 @@ import JsonFile from '@expo/json-file'; +import { formatExecError } from 'jest-message-util'; import path from 'path'; import { ConfigContext, ExpoConfig } from './Config.types'; @@ -17,6 +18,10 @@ export const allowedConfigFileNames: string[] = (() => { ]; })(); +function isMissingFileCode(code: string): boolean { + return ['ENOENT', 'ENOTDIR'].includes(code); +} + export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { // TODO(Bacon): Support custom config path with `findConfigFile` // TODO(Bacon): Should we support `expo` or `app` field with an object in the `package.json` too? @@ -28,7 +33,9 @@ export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { return evalConfig(configFilePath, request); } catch (error) { // If the file doesn't exist then we should skip it and continue searching. - if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; + if (!isMissingFileCode(error.code)) { + throw error; + } } return null; } @@ -61,7 +68,25 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Wed, 29 Jan 2020 18:19:15 -0800 Subject: [PATCH 25/27] Support expo object in app.json --- packages/config/src/Config.ts | 3 +++ .../src/__tests__/fixtures/language-support/js/app.json | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index cc19be98dd..294a767e99 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -57,6 +57,9 @@ function getConfigContext( let rawConfig: JSONObject = {}; try { rawConfig = JsonFile.read(appJsonConfigPath, { json5: true }); + if (typeof rawConfig.expo === 'object') { + rawConfig = rawConfig.expo as JSONObject; + } } catch (_) {} const { exp: configFromPkg } = ensureConfigHasDefaultValues(projectRoot, rawConfig, pkg, true); diff --git a/packages/config/src/__tests__/fixtures/language-support/js/app.json b/packages/config/src/__tests__/fixtures/language-support/js/app.json index 75d8cede7c..8c40aba900 100644 --- a/packages/config/src/__tests__/fixtures/language-support/js/app.json +++ b/packages/config/src/__tests__/fixtures/language-support/js/app.json @@ -1,4 +1,6 @@ { - "foo": "invalid", - "slug": "someslug" + "expo": { + "foo": "invalid", + "slug": "someslug" + } } From e62f020e45b040201319c4fff7a93143ee2c542c Mon Sep 17 00:00:00 2001 From: Evan Bacon Date: Wed, 29 Jan 2020 19:01:23 -0800 Subject: [PATCH 26/27] Update getConfig.ts --- packages/config/src/getConfig.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 2c025f45aa..179e01afc8 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -103,7 +103,7 @@ function evalConfig(configFile: string, request: ConfigContext): Partial Date: Tue, 4 Feb 2020 21:10:19 -0800 Subject: [PATCH 27/27] Remove support for json5 --- packages/config/src/__tests__/ConfigParsing-test.js | 8 -------- .../fixtures/language-support/json5/app.config.json5 | 4 ---- .../__tests__/fixtures/language-support/json5/app.json5 | 4 ---- .../fixtures/language-support/json5/package.json | 4 ---- packages/config/src/getConfig.ts | 3 +-- 5 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 delete mode 100644 packages/config/src/__tests__/fixtures/language-support/json5/app.json5 delete mode 100644 packages/config/src/__tests__/fixtures/language-support/json5/package.json diff --git a/packages/config/src/__tests__/ConfigParsing-test.js b/packages/config/src/__tests__/ConfigParsing-test.js index af70f5aa35..a90b782e90 100644 --- a/packages/config/src/__tests__/ConfigParsing-test.js +++ b/packages/config/src/__tests__/ConfigParsing-test.js @@ -68,13 +68,5 @@ describe('getConfig', () => { }); expect(exp.foo).toBe('bar'); }); - it('parses a json5 config', () => { - const projectRoot = path.resolve(__dirname, './fixtures/language-support/json5'); - const { exp } = getConfig(projectRoot, { - mode: 'development', - skipSDKVersionRequirement: true, - }); - expect(exp.foo).toBe('bar'); - }); }); }); diff --git a/packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 b/packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 deleted file mode 100644 index 8629e3a583..0000000000 --- a/packages/config/src/__tests__/fixtures/language-support/json5/app.config.json5 +++ /dev/null @@ -1,4 +0,0 @@ -{ - // comments are cool - foo: 'bar', -} diff --git a/packages/config/src/__tests__/fixtures/language-support/json5/app.json5 b/packages/config/src/__tests__/fixtures/language-support/json5/app.json5 deleted file mode 100644 index e1fe0e002d..0000000000 --- a/packages/config/src/__tests__/fixtures/language-support/json5/app.json5 +++ /dev/null @@ -1,4 +0,0 @@ -{ - // comments are cool - foo: 'invalid', -} diff --git a/packages/config/src/__tests__/fixtures/language-support/json5/package.json b/packages/config/src/__tests__/fixtures/language-support/json5/package.json deleted file mode 100644 index 8d14aac737..0000000000 --- a/packages/config/src/__tests__/fixtures/language-support/json5/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "json5-config-test", - "version": "1.0.0" -} diff --git a/packages/config/src/getConfig.ts b/packages/config/src/getConfig.ts index 179e01afc8..6b8e55d98d 100644 --- a/packages/config/src/getConfig.ts +++ b/packages/config/src/getConfig.ts @@ -14,7 +14,6 @@ export const allowedConfigFileNames: string[] = (() => { // TODO: Bacon: Slowly rollout support for other config languages: ts, yml, toml `${prefix}.config.js`, `${prefix}.config.json`, - `${prefix}.config.json5`, ]; })(); @@ -65,7 +64,7 @@ export function findAndEvalConfig(request: ConfigContext): ExpoConfig | null { // If they don't add support for async Webpack configs then we may need to pull support for Next.js. function evalConfig(configFile: string, request: ConfigContext): Partial { let result: any; - if (configFile.endsWith('.json5') || configFile.endsWith('.json')) { + if (configFile.endsWith('.json')) { result = JsonFile.read(configFile, { json5: true }); } else { try {