Skip to content

Commit

Permalink
fix(expo): fix unable to build expo local for yarn 4 (#26992)
Browse files Browse the repository at this point in the history
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #22631
  • Loading branch information
xiongemi authored Aug 29, 2024
1 parent 0ef6892 commit 81acded
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 63 deletions.
16 changes: 16 additions & 0 deletions docs/generated/packages/expo/executors/build.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@
"type": "string",
"description": "Submit on build complete using the submit profile with provided name",
"examples": ["production", "development", "preview"]
},
"message": {
"type": "string",
"description": "A short message describing the build",
"examples": ["My message"]
},
"buildLoggerLevel": {
"type": "string",
"description": "The level of logs to output during the build process.",
"enum": ["trace", "debug", "info", "warn", "error", "fatal"],
"default": "info"
},
"freezeCredentials": {
"type": "boolean",
"description": "Prevent the build from updating credentials in non-interactive mode",
"default": false
}
},
"required": []
Expand Down
6 changes: 6 additions & 0 deletions packages/expo/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@
"cli": "nx",
"description": "Remove deprecated webpack.config.js",
"factory": "./src/migrations/update-19-2-0/remove-deprecated-webpack-config"
},
"update-19-7-0-remove-eas-pre-install": {
"version": "19.7.0-beta.4",
"cli": "nx",
"description": "Remove eas-build-pre-install script from app's package.json",
"factory": "./src/migrations/update-19-7-0/remove-eas-pre-install"
}
},
"packageJsonUpdates": {
Expand Down
3 changes: 1 addition & 2 deletions packages/expo/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ function buildExpoTargets(
executor: `@nx/expo:prebuild`,
},
[options.buildTargetName]: {
command: `eas build`,
options: { cwd: projectRoot },
executor: `@nx/expo:build`,
},
[options.submitTargetName]: {
command: `eas submit`,
Expand Down
82 changes: 81 additions & 1 deletion packages/expo/src/executors/build/build.impl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { ExecutorContext, names } from '@nx/devkit';
import {
detectPackageManager,
ExecutorContext,
getPackageManagerCommand,
names,
PackageManager,
readJsonFile,
} from '@nx/devkit';
import { getLockFileName } from '@nx/js';
import { copyFileSync, existsSync, removeSync, writeFileSync } from 'fs-extra';
import { resolve as pathResolve } from 'path';
import { ChildProcess, fork } from 'child_process';
import type { PackageJson } from 'nx/src/utils/package-json';

import { resolveEas } from '../../utils/resolve-eas';

Expand All @@ -19,10 +29,19 @@ export default async function* buildExecutor(
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;

let resetLocalFunction;

try {
resetLocalFunction = copyPackageJsonAndLock(
detectPackageManager(context.root),
context.root,
projectRoot
);
await runCliBuild(context.root, projectRoot, options);
yield { success: true };
} finally {
resetLocalFunction();

if (childProcess) {
childProcess.kill();
}
Expand Down Expand Up @@ -85,3 +104,64 @@ function createBuildOptions(options: ExpoEasBuildOptions) {
return acc;
}, []);
}

/**
* This function:
* - copies the root package.json and lock file to the project directory
* - returns a function that resets the project package.json and removes the lock file
*/
function copyPackageJsonAndLock(
packageManager: PackageManager,
workspaceRoot: string,
projectRoot: string
) {
const packageJson = pathResolve(workspaceRoot, 'package.json');
const rootPackageJson = readJsonFile<PackageJson>(packageJson);
// do not copy package.json and lock file if workspaces are enabled
if (
(packageManager === 'pnpm' &&
existsSync(pathResolve(workspaceRoot, 'pnpm-workspace.yaml'))) ||
rootPackageJson.workspaces
) {
return;
}

const packageJsonProject = pathResolve(projectRoot, 'package.json');
const projectPackageJson = readJsonFile<PackageJson>(packageJsonProject);

const lockFile = getLockFileName(detectPackageManager(workspaceRoot));
const lockFileProject = pathResolve(projectRoot, lockFile);

const rootPackageJsonDependencies = rootPackageJson.dependencies;
const projectPackageJsonDependencies = { ...projectPackageJson.dependencies };

const rootPackageJsonDevDependencies = rootPackageJson.devDependencies;
const projectPackageJsonDevDependencies = {
...projectPackageJson.devDependencies,
};

projectPackageJson.dependencies = rootPackageJsonDependencies;
projectPackageJson.devDependencies = rootPackageJsonDevDependencies;

// Copy dependencies from root package.json to project package.json
writeFileSync(
packageJsonProject,
JSON.stringify(projectPackageJson, null, 2)
);

// Copy lock file from root to project
copyFileSync(lockFile, lockFileProject);

return () => {
// Reset project package.json to original state
projectPackageJson.dependencies = projectPackageJsonDependencies;
projectPackageJson.devDependencies = projectPackageJsonDevDependencies;
writeFileSync(
packageJsonProject,
JSON.stringify(projectPackageJson, null, 2)
);

// Remove lock file from project
removeSync(lockFileProject);
};
}
4 changes: 4 additions & 0 deletions packages/expo/src/executors/build/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ export interface ExpoEasBuildOptions {
json: boolean; // default is false
autoSubmit: boolean; // default is false
autoSubmitWithProfile?: string;
message?: string;
// values from https://github.com/expo/eas-build/blob/main/packages/logger/src/level.ts
buildLoggerLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; // default is info
freezeCredentials: boolean; // default is false
}
16 changes: 16 additions & 0 deletions packages/expo/src/executors/build/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@
"type": "string",
"description": "Submit on build complete using the submit profile with provided name",
"examples": ["production", "development", "preview"]
},
"message": {
"type": "string",
"description": "A short message describing the build",
"examples": ["My message"]
},
"buildLoggerLevel": {
"type": "string",
"description": "The level of logs to output during the build process.",
"enum": ["trace", "debug", "info", "warn", "error", "fatal"],
"default": "info"
},
"freezeCredentials": {
"type": "boolean",
"description": "Prevent the build from updating credentials in non-interactive mode",
"default": false
}
},
"required": []
Expand Down
5 changes: 2 additions & 3 deletions packages/expo/src/executors/serve/serve.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,8 @@ function serveAsync(
childProcess.stdout.on('data', (data) => {
process.stdout.write(data);
if (
data
.toString()
.includes('Bundling complete' || data.toString().includes('Bundled'))
data.toString().includes('Bundling complete') ||
data.toString().includes('Bundled')
) {
resolve(childProcess);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"react-native-web": "*"
},
"scripts": {
"eas-build-pre-install": "cd <%= offsetFromRoot %> && node tools/scripts/eas-build-pre-install.mjs . <%= appProjectRoot %> && cp <%= packageLockFile %> <%= appProjectRoot %>",
"eas-build-post-install": "cd <%= offsetFromRoot %> && node tools/scripts/eas-build-post-install.mjs . <%= appProjectRoot %>"
}
}
40 changes: 0 additions & 40 deletions packages/expo/src/generators/application/lib/add-eas-scripts.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,5 @@
import type { Tree } from '@nx/devkit';

const preInstallScript = `
/*
* This script is used to patch the '@nx/expo' package to work with EAS Build.
* It is run as the eas-build-pre-install script in the 'package.json' of expo app.
* It is executed as 'node tools/scripts/eas-build-pre-install.mjs <workspace root> <project root>'
* It will copy the dependencies and devDependencies from the workspace package.json to project's package.json.
* This is needed because EAS Build does the install in project's directory and not workspace's directory.
*/
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
const [workspaceRoot, projectRoot] = process.argv.slice(2);
if (!workspaceRoot) {
throw new Error('Missing workspace root');
}
if (!projectRoot) {
throw new Error('Missing project root');
}
try {
const workspacePackage = JSON.parse(
readFileSync(join(workspaceRoot, 'package.json')).toString()
);
const projectPackage = JSON.parse(
readFileSync(join(projectRoot, 'package.json')).toString()
);
projectPackage.dependencies = workspacePackage.dependencies;
projectPackage.devDependencies = workspacePackage.devDependencies;
writeFileSync(
join(projectRoot, 'package.json'),
JSON.stringify(projectPackage, null, 2)
);
} catch (e) {
console.error('Error reading package.json file', e);
}
`;

const postInstallScript = `
/**
* This script is used to patch the '@nx/expo' package to work with EAS Build.
Expand Down Expand Up @@ -63,12 +27,8 @@ symlink(join(projectRoot, 'node_modules'), join(workspaceRoot, 'node_modules'),
`;

export function addEasScripts(tree: Tree) {
const preInstallScriptPath = 'tools/scripts/eas-build-pre-install.mjs';
const postInstallScriptPath = 'tools/scripts/eas-build-post-install.mjs';

if (!tree.exists(preInstallScriptPath)) {
tree.write(preInstallScriptPath, preInstallScript);
}
if (!tree.exists(postInstallScriptPath)) {
tree.write(postInstallScriptPath, postInstallScript);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,10 @@ describe('update-eas-scripts', () => {
it('should add scripts', async () => {
update(tree);

expect(tree.exists('tools/scripts/eas-build-pre-install.mjs')).toBeTruthy();
expect(
tree.exists('tools/scripts/eas-build-post-install.mjs')
).toBeTruthy();
const packageJson = readJson(tree, 'apps/products/package.json');
expect(packageJson.scripts['eas-build-pre-install']).toEqual(
'cd ../../ && node tools/scripts/eas-build-pre-install.mjs . apps/products && cp package-lock.json apps/products'
);
expect(packageJson.scripts['eas-build-post-install']).toEqual(
'cd ../../ && node tools/scripts/eas-build-post-install.mjs . apps/products'
);
Expand Down
13 changes: 1 addition & 12 deletions packages/expo/src/migrations/update-16-1-4/update-eas-scripts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import {
PackageManager,
Tree,
detectPackageManager,
getProjects,
logger,
offsetFromRoot,
Expand All @@ -11,16 +9,10 @@ import { addEasScripts } from '../../generators/application/lib/add-eas-scripts'
import { join } from 'path';

/**
* Update app's package.json to use eas-build-pre-install and eas-build-post-install scripts.
* Update app's package.json to use eas-build-post-install scripts.
*/
export default function update(tree: Tree) {
const projects = getProjects(tree);
const packageManagerLockFile: Record<PackageManager, string> = {
npm: 'package-lock.json',
yarn: 'yarn.lock',
pnpm: 'pnpm-lock.yaml',
bun: 'bun.lockb',
};

for (const [name, config] of projects.entries()) {
if (
Expand All @@ -33,12 +25,9 @@ export default function update(tree: Tree) {
if (packageJson.scripts?.['postinstall']) {
delete packageJson.scripts['postinstall'];
}
const packageManager = detectPackageManager(tree.root);
const packageLockFile = packageManagerLockFile[packageManager];
const offset = offsetFromRoot(config.root);
packageJson.scripts = {
...packageJson.scripts,
'eas-build-pre-install': `cd ${offset} && node tools/scripts/eas-build-pre-install.mjs . ${config.root} && cp ${packageLockFile} ${config.root}`,
'eas-build-post-install': `cd ${offset} && node tools/scripts/eas-build-post-install.mjs . ${config.root}`,
};
return packageJson;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './remove-eas-pre-install';
import { addProjectConfiguration, Tree } from '@nx/devkit';

describe('Remove eas-build-pre-install script from app package.json', () => {
let tree: Tree;

beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'product', {
root: 'apps/product',
});
});

it('should not throw an error if package.json does not exist', () => {
expect(() => update(tree)).not.toThrow();
});

it('should not throw an error if there is no scripts in package.json', () => {
tree.write('apps/product/package.json', JSON.stringify({}));
expect(() => update(tree)).not.toThrow();
});

it('should remove eas-build-pre-install script', async () => {
tree.write(
'apps/product/package.json',
JSON.stringify({
scripts: {
'eas-build-pre-install': 'echo "Hello World!"',
},
})
);
await update(tree);
expect(tree.read('apps/product/package.json').toString()).not.toContain(
'eas-build-pre-install'
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Tree, getProjects, logger, updateJson } from '@nx/devkit';
import { join } from 'path';

/**
* Remove eas-build-pre-install script from app's package.json.
* This script causes an issue with Yarn 4.
*/
export default function update(tree: Tree) {
const projects = getProjects(tree);

for (const [_, config] of projects.entries()) {
const packageJsonPath = join(config.root, 'package.json');
if (!tree.exists(packageJsonPath)) {
continue;
}
updateJson(tree, join(config.root, 'package.json'), (packageJson) => {
if (packageJson.scripts?.['eas-build-pre-install']) {
delete packageJson.scripts['eas-build-pre-install'];
}
return packageJson;
});
}
}

0 comments on commit 81acded

Please sign in to comment.