1import JsonFile from '@expo/json-file';
2import chalk from 'chalk';
3import path from 'path';
4
5import { EXPO_DIR } from '../../Constants';
6import logger from '../../Logger';
7import { Task } from '../../TasksRunner';
8import * as Workspace from '../../Workspace';
9import { Parcel, TaskArgs } from '../types';
10
11const { green, yellow, cyan } = chalk;
12
13/**
14 * Updates versions of packages to be published in other workspace projects depending on them.
15 */
16export const updateWorkspaceProjects = new Task<TaskArgs>(
17  {
18    name: 'updateWorkspaceProjects',
19    filesToStage: ['**/package.json', 'yarn.lock'],
20  },
21  async (parcels: Parcel[]) => {
22    logger.info('\n�� Updating workspace projects...');
23
24    const workspaceInfo = await Workspace.getInfoAsync();
25    const dependenciesKeys = ['dependencies', 'devDependencies', 'peerDependencies'];
26
27    const parcelsObject = parcels.reduce((acc, parcel) => {
28      acc[parcel.pkg.packageName] = parcel;
29      return acc;
30    }, {});
31
32    await Promise.all(
33      Object.entries(workspaceInfo).map(async ([projectName, projectInfo]) => {
34        const projectDependencies = [
35          ...projectInfo.workspaceDependencies,
36          ...projectInfo.mismatchedWorkspaceDependencies,
37        ]
38          .map((dependencyName) => parcelsObject[dependencyName])
39          .filter(Boolean);
40
41        // If this project doesn't depend on any package we're going to publish.
42        if (projectDependencies.length === 0) {
43          return;
44        }
45
46        // Get copy of project's `package.json`.
47        const projectPackageJsonPath = path.join(EXPO_DIR, projectInfo.location, 'package.json');
48        const projectPackageJson = await JsonFile.readAsync(projectPackageJsonPath);
49        const batch = logger.batch();
50
51        batch.log('  ', green(projectName));
52
53        // Iterate through different dependencies types.
54        for (const dependenciesKey of dependenciesKeys) {
55          const dependenciesObject = projectPackageJson[dependenciesKey];
56
57          if (!dependenciesObject) {
58            continue;
59          }
60
61          for (const { pkg, state } of projectDependencies) {
62            const currentVersionRange = dependenciesObject[pkg.packageName];
63
64            if (!currentVersionRange) {
65              continue;
66            }
67
68            // Leave tilde and caret as they are, just replace the version.
69            const newVersionRange = currentVersionRange.replace(
70              /([\^~]?).*/,
71              `$1${state.releaseVersion}`
72            );
73            dependenciesObject[pkg.packageName] = newVersionRange;
74
75            batch.log(
76              '    ',
77              `Updating ${yellow(`${dependenciesKey}.${pkg.packageName}`)}`,
78              `from ${cyan(currentVersionRange)} to ${cyan(newVersionRange)}`
79            );
80          }
81        }
82
83        // Save project's `package.json`.
84        await JsonFile.writeAsync(projectPackageJsonPath, projectPackageJson);
85
86        // Flush batched logs.
87        batch.flush();
88      })
89    );
90  }
91);
92