Skip to content

Commit b5aff4f

Browse files
authored
GLSP-1298: Add updateNext command (#1299)
* GLSP-1298: Add `updateNext` command Add `updateNext` command that serves as a replacement for the (currently broken) update:next scripts in GLSP repos. It automatically scanns all workspace pacakges for `next` dependencies, resolves the latest `next` version for these dependencies and then updates the `yarn.lock` by temorary adding a `resolutions` block to the root package.json to enforece an update. Fixes #1298 * Address review feedback
1 parent 886d2d0 commit b5aff4f

File tree

6 files changed

+819
-24
lines changed

6 files changed

+819
-24
lines changed

dev-packages/cli/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ Options:
8383
-h, --help display help for command
8484
```
8585

86+
## updateNext
87+
88+
```console
89+
$ glsp updateNext -h
90+
Usage: glsp updateNext|u [options] [rootDir]
91+
92+
Updates all `next` dependencies in GLSP project to the latest version
93+
94+
Arguments:
95+
rootDir The repository root (default: "<cwd>")
96+
97+
Options:
98+
-v, --verbose Enable verbose (debug) log output (default: false)
99+
-h, --help display help for command
100+
```
101+
86102
## More information
87103

88104
For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/).

dev-packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"commander": "^10.0.1",
4545
"glob": "^10.3.10",
4646
"node-fetch": "^2.6.11",
47+
"node-jq": "^4.3.1",
4748
"readline-sync": "^1.4.10",
4849
"semver": "^7.5.1",
4950
"shelljs": "^0.8.5"

dev-packages/cli/src/app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { CheckHeaderCommand } from './commands/check-header';
1818
import { CoverageReportCommand } from './commands/coverage-report';
1919
import { ReleaseCommand } from './commands/release/release';
20+
import { UpdateNextCommand } from './commands/update-next';
2021
import { baseCommand } from './util/command-util';
2122

2223
export const COMMAND_VERSION = '1.1.0-next';
@@ -26,6 +27,7 @@ const app = baseCommand() //
2627
.name('glsp')
2728
.addCommand(CoverageReportCommand)
2829
.addCommand(ReleaseCommand)
29-
.addCommand(CheckHeaderCommand);
30+
.addCommand(CheckHeaderCommand)
31+
.addCommand(UpdateNextCommand);
3032

3133
app.parse(process.argv);

dev-packages/cli/src/commands/release/release.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ export async function release(
7777
cliOptions: ReleaseCmdOptions
7878
): Promise<void> {
7979
try {
80+
configureLogger(cliOptions.verbose);
8081
LOGGER.debug('Cli options:', cliOptions);
8182
configureShell({ silent: !cliOptions.verbose });
82-
configureLogger(cliOptions.verbose);
8383
checkGHCli();
8484
const version = deriveVersion(releaseType, customVersion);
8585
const options: ReleaseOptions = { ...cliOptions, component, releaseType, version };
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/********************************************************************************
2+
* Copyright (c) 2024 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
import * as fs from 'fs';
18+
import { glob } from 'glob';
19+
import * as jq from 'node-jq';
20+
import * as path from 'path';
21+
import * as sh from 'shelljs';
22+
import { baseCommand, configureShell } from '../util/command-util';
23+
import { getUncommittedChanges } from '../util/git-util';
24+
import { LOGGER, configureLogger } from '../util/logger';
25+
import { validateGitDirectory } from '../util/validation-util';
26+
export const UpdateNextCommand = baseCommand() //
27+
.name('updateNext')
28+
.alias('u')
29+
.description('Updates all `next` dependencies in GLSP project to the latest version')
30+
.argument('[rootDir]', 'The repository root', validateGitDirectory, process.cwd())
31+
.option('-v, --verbose', 'Enable verbose (debug) log output', false)
32+
.action(updateNext);
33+
34+
export async function updateNext(rootDir: string, options: { verbose: boolean }): Promise<void> {
35+
configureLogger(options.verbose);
36+
configureShell({ silent: true, fatal: true });
37+
38+
const rootPackage = path.join(rootDir, 'package.json');
39+
if (getUncommittedChanges(rootDir).includes(rootPackage)) {
40+
LOGGER.warn('Uncommitted changes in root `package.json`. Please commit or stash them before running this command.');
41+
return;
42+
}
43+
44+
configureShell({ silent: false, fatal: true });
45+
46+
LOGGER.info('Updating next dependencies ...');
47+
rootDir = path.resolve(rootDir);
48+
const packages = await getWorkspacePackages(rootDir);
49+
LOGGER.debug(`Scanning ${packages.length} packages to derive resolutions`, packages);
50+
const resolutions = await getResolutions(packages);
51+
if (Object.keys(resolutions).length === 0) {
52+
LOGGER.info('No next dependencies found');
53+
return;
54+
}
55+
LOGGER.info('Upgrade and rebuild packages ...');
56+
const packageJson = fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8');
57+
LOGGER.debug('Updating package.json with resolutions', resolutions);
58+
fs.writeFileSync(path.join(rootDir, 'package.json'), JSON.stringify({ ...JSON.parse(packageJson), resolutions }, undefined, 2));
59+
LOGGER.debug('Running yarn install');
60+
sh.exec('yarn install --ignore-scripts');
61+
LOGGER.debug('Reverting package.json');
62+
sh.exec('git checkout HEAD -- package.json');
63+
LOGGER.debug('Rebuild to update yarn.lock');
64+
sh.exec('yarn');
65+
LOGGER.info('Upgrade successfully completed');
66+
}
67+
68+
async function getWorkspacePackages(rootDir: string): Promise<string[]> {
69+
const rootPackage = path.join(rootDir, 'package.json');
70+
const packages = [rootPackage];
71+
if (!fs.existsSync(rootPackage)) {
72+
LOGGER.error('No package.json found in root directory');
73+
process.exit(1);
74+
}
75+
const workspaces = await getWorkspaceConfig(rootPackage);
76+
if (workspaces) {
77+
workspaces
78+
.map(workspace => `${workspace}/**/package.json`)
79+
.forEach(pattern => {
80+
glob.sync(pattern, {
81+
cwd: rootDir,
82+
ignore: ['**/node_modules/**']
83+
}).forEach(packageJson => packages.push(path.join(rootDir, packageJson)));
84+
});
85+
}
86+
87+
return [...new Set(packages)];
88+
}
89+
90+
async function getResolutions(packages: string[]): Promise<Record<string, string>> {
91+
let dependencies: string[] = [];
92+
for (const pkg of packages) {
93+
const deps = await jq.run(
94+
'.dependencies //{} + .devDependencies + .peerDependencies | with_entries(select(.value == "next")) | keys',
95+
pkg,
96+
{
97+
output: 'json'
98+
}
99+
);
100+
if (Array.isArray(deps)) {
101+
dependencies.push(...deps);
102+
}
103+
}
104+
dependencies = [...new Set(dependencies)];
105+
LOGGER.debug(`Found ${dependencies.length} 'next' dependencies`, dependencies);
106+
LOGGER.info('Retrieve next versions ... ');
107+
const resolutions: Record<string, string> = {};
108+
[...new Set(dependencies)].forEach(dep => {
109+
LOGGER.info(`Retrieving next version for ${dep}`);
110+
const version = sh.exec(`npm view ${dep}@next version`, { silent: true }).stdout.trim();
111+
resolutions[`**/${dep}`] = version;
112+
});
113+
return resolutions;
114+
}
115+
async function getWorkspaceConfig(rootPackage: string): Promise<string[] | undefined> {
116+
const result = await jq.run('.workspaces', rootPackage, { output: 'json' });
117+
if (!result) {
118+
return undefined;
119+
}
120+
if (Array.isArray(result)) {
121+
return result;
122+
}
123+
if (typeof result === 'object' && 'packages' in result && Array.isArray(result.packages)) {
124+
return result.packages;
125+
}
126+
127+
return undefined;
128+
}

0 commit comments

Comments
 (0)