Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Remove external config evaluation script #2625

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@
"paths"
],
"dependencies": {
"@babel/register": "^7.8.3",
"@babel/core": "7.9.0",
"@expo/babel-preset-cli": "0.2.17",
"@expo/image-utils": "0.3.5",
"@expo/json-file": "8.2.22",
"@expo/plist": "0.0.9",
"fs-extra": "9.0.0",
"glob": "7.1.6",
"invariant": "^2.2.4",
"require-from-string": "^2.0.2",
"resolve-from": "^5.0.0",
"semver": "^7.1.3",
"slugify": "^1.3.4",
Expand Down
30 changes: 16 additions & 14 deletions packages/config/src/__tests__/getConfig-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,6 @@ describe('modifyConfigAsync', () => {
});

describe('getDynamicConfig', () => {
// This tests error are thrown properly and ensures that a more specific
// config is used instead of defaulting to a valid substitution.
it(`throws a useful error for dynamic configs with a syntax error`, () => {
const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error'));
expect(() => getDynamicConfig(paths.dynamicConfigPath, {})).toThrowError(
'Unexpected token (3:4)'
);
});
it(`exports a function`, () => {
expect(
getDynamicConfig(
Expand All @@ -62,6 +54,15 @@ describe('getDynamicConfig', () => {
).toBe('object');
});

// This tests error are thrown properly and ensures that a more specific
// config is used instead of defaulting to a valid substitution.
it(`throws a useful error for dynamic configs with a syntax error`, () => {
const paths = getConfigFilePaths(join(__dirname, 'fixtures/behavior/syntax-error'));
expect(() => getDynamicConfig(paths.dynamicConfigPath, {})).toThrowError(
'Unexpected token (3:4)'
);
});

describe('process.cwd in a child process', () => {
const originalCwd = process.cwd();
const projectRoot = join(__dirname, 'fixtures/behavior/dynamic-cwd');
Expand All @@ -74,15 +75,16 @@ describe('getDynamicConfig', () => {
process.chdir(originalCwd);
});

// Test that hot evaluation is spawned in the expected location
// https://github.com/expo/expo-cli/pull/2220
it('process.cwd in read-config script is not equal to the project root', () => {
const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), {});
expect(config.processCwd).toBe(__dirname);
});
it('process.cwd in read-config script is equal to the project root', () => {
const { config } = getDynamicConfig(join(projectRoot, 'app.config.ts'), {
projectRoot,
});
expect(config.processCwd).toBe(projectRoot);
expect(
getDynamicConfig(join(projectRoot, 'app.config.ts'), {
projectRoot,
}).config.processCwd
).toBe(__dirname);
});
});
});
Expand Down
48 changes: 48 additions & 0 deletions packages/config/src/evalConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// @ts-ignore
import requireString from 'require-from-string';

import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types';
import { ConfigError } from './Errors';
import { serializeAndEvaluate } from './Serialize';
// import babel from '@babel/core';

type RawDynamicConfig = AppJSONConfig | Partial<ExpoConfig> | null;

export type DynamicConfigResults = { config: RawDynamicConfig; exportedObjectType: string };

/**
* Transpile and evaluate the dynamic config object.
* This method is shared between the standard reading method in getConfig, and the headless script.
*
* @param options configFile path to the dynamic app.config.*, request to send to the dynamic config if it exports a function.
* @returns the serialized and evaluated config along with the exported object type (object or function).
*/
export function evalConfig(
configFile: string,
request: ConfigContext | null
): DynamicConfigResults {
const babel = require('@babel/core');
const { code } = babel.transformFileSync(require.resolve(configFile), {
only: [configFile],
cwd: request?.projectRoot || process.cwd(),
babelrc: false,
ignore: [/node_modules/],
filename: 'unknown',
presets: [require.resolve('@expo/babel-preset-cli')],
});

let result = requireString(code);
if (result.default != null) {
result = result.default;
}
const exportedObjectType = typeof result;
if (typeof result === 'function') {
result = result(request);
}

if (result instanceof Promise) {
throw new ConfigError(`Config file ${configFile} cannot return a Promise.`, 'INVALID_CONFIG');
}

return { config: serializeAndEvaluate(result), exportedObjectType };
}
67 changes: 16 additions & 51 deletions packages/config/src/getConfig.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
import JsonFile from '@expo/json-file';
import { spawnSync } from 'child_process';

import { AppJSONConfig, ConfigContext, ExpoConfig } from './Config.types';
import { ConfigError, errorFromJSON } from './Errors';
import { fileExists } from './Modules';
import { serializeAndEvaluate } from './Serialize';

type RawDynamicConfig = AppJSONConfig | Partial<ExpoConfig> | null;

type DynamicConfigResults = { config: RawDynamicConfig; exportedObjectType: string };
import { ConfigError } from './Errors';
import { DynamicConfigResults, evalConfig } from './evalConfig';

function isMissingFileCode(code: string): boolean {
return ['ENOENT', 'MODULE_NOT_FOUND', 'ENOTDIR'].includes(code);
}

function readConfigFile(
configFilePath: string,
context: ConfigContext
): null | DynamicConfigResults {
if (!fileExists(configFilePath)) 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.
function readConfigFile(configFile: string, context: ConfigContext): null | DynamicConfigResults {
try {
return evalConfig(configFilePath, context);
return evalConfig(configFile, context);
} catch (error) {
// If the file doesn't exist then we should skip it and continue searching.
if (!isMissingFileCode(error.code)) {
// @ts-ignore
error.isConfigError = true;
// @ts-ignore: Replace the babel stack with a more relevant stack.
error.stack = new Error().stack;
throw error;
}
}
Expand All @@ -34,48 +29,18 @@ function readConfigFile(
export function getDynamicConfig(configPath: string, request: ConfigContext): DynamicConfigResults {
const config = readConfigFile(configPath, request);
if (config) {
return serializeAndEvaluate(config);
// The config must be serialized and evaluated ahead of time so the spawned process can send it over.
return config;
}
// TODO: It seems this is only thrown if the file cannot be found (which may never happen).
// If so we should throw a more helpful error.
throw new ConfigError(`Failed to read config at: ${configPath}`, 'INVALID_CONFIG');
}

export function getStaticConfig(configPath: string): AppJSONConfig | ExpoConfig | null {
export function getStaticConfig(configPath: string): AppJSONConfig | ExpoConfig {
const config = JsonFile.read(configPath, { json5: true });
if (config) {
return serializeAndEvaluate(config);
return config as any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lmfao ¯_(ツ)_/¯

}
throw new ConfigError(`Failed to read config at: ${configPath}`, 'INVALID_CONFIG');
}

// 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): DynamicConfigResults {
const spawnResults = spawnSync(
'node',
[
require.resolve('@expo/config/build/scripts/read-config.js'),
'--colors',
configFile,
JSON.stringify({ ...request, config: serializeAndEvaluate(request.config) }),
],
{ cwd: request.projectRoot || process.cwd() }
);

if (spawnResults.status === 0) {
const spawnResultString = spawnResults.stdout.toString('utf8').trim();
const logs = spawnResultString.split('\n');
// Get the last console log to prevent parsing anything logged in the config.
const lastLog = logs.pop()!;
for (const log of logs) {
// Log out the logs from the config
console.log(log);
}
// Parse the final log of the script, it's the serialized config and exported object type.
const results = JSON.parse(lastLog);
return results;
} else {
// Parse the error data and throw it as expected
const errorData = JSON.parse(spawnResults.stderr.toString('utf8'));
throw errorFromJSON(errorData);
}
}
54 changes: 0 additions & 54 deletions packages/config/src/scripts/read-config.ts

This file was deleted.

7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,7 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-typescript" "^7.7.4"

"@babel/register@^7.0.0", "@babel/register@^7.8.3":
"@babel/register@7.8.3", "@babel/register@^7.0.0":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.8.3.tgz#5d5d30cfcc918437535d724b8ac1e4a60c5db1f8"
integrity sha512-t7UqebaWwo9nXWClIPLPloa5pN33A2leVs8Hf0e9g9YwUP8/H9NeR7DJU+4CXo23QtjChQv5a3DjEtT83ih1rg==
Expand Down Expand Up @@ -18653,6 +18653,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=

require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==

require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
Expand Down