Skip to content

Commit

Permalink
Workflow to update JupyterLab dependencies automatically (#7281)
Browse files Browse the repository at this point in the history
* workflow to upgrade JupyterLab dependencies

* Update upgrade-lab-dependencies.py

* Prettier code for Test Lint check

* Prettier code for Test Lint checks

* Prettier code for Test Lint check

* added ts scripts to upgrade lab dependencies

* Prettier code for test lint check

* Update buildutils/src/upgrade-lab-dependencies.ts

Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com>

* Lint

---------

Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com>
Co-authored-by: Jeremy Tuloup <jeremy.tuloup@gmail.com>
  • Loading branch information
3 people authored Dec 20, 2024
1 parent d9119b8 commit c0ddf01
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
92 changes: 92 additions & 0 deletions .github/workflows/upgrade-juypterlab-dependencies.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Check for latest JupyterLab releases

on:
schedule:
- cron: 30 17 * * *
workflow_dispatch:
inputs:
version:
description: 'JupyterLab version'
default: latest
required: true
type: string

env:
version_tag: 'latest'

permissions:
contents: write
pull-requests: write

jobs:
check_for_lab_updates:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install required dependencies
run: |
python -m pip install jupyterlab
sudo apt-get install hub
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '20.x'

- name: Install npm dependencies and build buildutils
run: |
jlpm install
jlpm run build:utils
- name: Check for new releases and update
shell: bash
run: |
set -eux
for version in ${{ inputs.version || env.version_tag }}
do
export LATEST=$(node buildutils/lib/get-latest-lab-version.js --set-version $version)
done
echo "latest=${LATEST}" >> $GITHUB_ENV
node buildutils/lib/upgrade-lab-dependencies.js --set-version ${LATEST}
if [[ ! -z "$(git status --porcelain package.json)" ]]; then
jlpm install
fi
- name: Create a PR
shell: bash
env:
GITHUB_USER: ${{ secrets.G_USER }}
GITHUB_TOKEN: ${{ secrets.G_TOKEN }}
run: |
set -eux
export LATEST=${{ env.latest }}
export BRANCH_NAME=update-to-v${LATEST}
# if resulted in any change:
if [[ ! -z "$(git status --porcelain package.json)" ]]; then
# if branch already exists.
if git ls-remote --heads origin | grep "refs/heads/${BRANCH_NAME}$" > /dev/null; then
echo "Branch '${BRANCH_NAME}' exists."
else
# new branch is created
git checkout -b "${BRANCH_NAME}"
git config user.name "Jupyter Bot"
git config user.email 'jupyterlab-bot@users.noreply.github.com'
git commit . -m "Update to JupyterLab v${LATEST}"
git push --set-upstream origin "${BRANCH_NAME}"
hub pull-request -m "Update to JupyterLab v${LATEST}" \
-m "New JupyterLab release [v${LATEST}](https://github.com/jupyterlab/jupyterlab/releases/tag/v${LATEST}) is available. Please review the lock file carefully.".
fi
fi
54 changes: 54 additions & 0 deletions buildutils/src/get-latest-lab-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
function extractVersionFromReleases(
releases: any,
versionTag: string
): string | null {
for (const release of releases) {
const tagName: string = release['tag_name'];
if (versionTag === 'latest') {
if (!release['prerelease'] && !release['draft']) {
return tagName;
}
} else if (versionTag === tagName) {
return tagName;
}
}
return null;
}

async function findVersion(versionTag: string): Promise<string> {
const url = 'https://api.github.com/repos/jupyterlab/jupyterlab/releases';
const response = await fetch(url);
if (!response.ok) {
const error_message = `Failed to fetch package.json from ${url}. HTTP status code: ${response.status}`;
throw new Error(error_message);
}
const releases: any = await response.json();
const version: string | null = extractVersionFromReleases(
releases,
versionTag
);
if (version === null) {
const error_message = 'Invalid release tag';
throw new Error(error_message);
}
return version.substring(1);
}

async function getLatestLabVersion(): Promise<void> {
const args: string[] = process.argv.slice(2);
if (args.length !== 2 || args[0] !== '--set-version') {
console.error('Usage: node script.js --set-version <version>');
process.exit(1);
}
const version_tag: string = args[1];

try {
const result: string = await findVersion(version_tag);
console.log(result);
} catch (error: any) {
console.error('Error:', error.message);
process.exit(1);
}
}

getLatestLabVersion();
104 changes: 104 additions & 0 deletions buildutils/src/upgrade-lab-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import fs from 'fs';
import path from 'path';

const PACKAGE_JSON_PATHS: string[] = [
'app/package.json',
'buildutils/package.json',
'package.json',
'packages/application-extension/package.json',
'packages/application/package.json',
'packages/console-extension/package.json',
'packages/docmanager-extension/package.json',
'packages/documentsearch-extension/package.json',
'packages/help-extension/package.json',
'packages/lab-extension/package.json',
'packages/notebook-extension/package.json',
'packages/terminal-extension/package.json',
'packages/tree-extension/package.json',
'packages/tree/package.json',
'packages/ui-components/package.json',
];

const DEPENDENCY_GROUP = '@jupyterlab';

async function updatePackageJson(newVersion: string): Promise<void> {
const url = `https://raw.githubusercontent.com/jupyterlab/jupyterlab/v${newVersion}/jupyterlab/staging/package.json`;
const response = await fetch(url);

if (!response.ok) {
const errorMessage = `Failed to fetch package.json from ${url}. HTTP status code: ${response.status}`;
throw new Error(errorMessage);
}

const newPackageJson = await response.json();

for (const packageJsonPath of PACKAGE_JSON_PATHS) {
const filePath: string = path.resolve(packageJsonPath);
const existingPackageJson = JSON.parse(fs.readFileSync(filePath, 'utf-8'));

const newDependencies = {
...newPackageJson.devDependencies,
...newPackageJson.resolutions,
};

updateDependencyVersion(existingPackageJson, newDependencies);

fs.writeFileSync(
filePath,
JSON.stringify(existingPackageJson, null, 2) + '\n'
);
}
}

function updateDependencyVersion(existingJson: any, newJson: any): void {
if (!existingJson) {
return;
}

const sectionPaths: string[] = [
'resolutions',
'dependencies',
'devDependencies',
];

for (const section of sectionPaths) {
if (!existingJson[section]) {
continue;
}

const updated = existingJson[section];

for (const [pkg, version] of Object.entries<string>(
existingJson[section]
)) {
if (pkg.startsWith(DEPENDENCY_GROUP) && pkg in newJson) {
if (version[0] === '^' || version[0] === '~') {
updated[pkg] = version[0] + absoluteVersion(newJson[pkg]);
} else {
updated[pkg] = absoluteVersion(newJson[pkg]);
}
}
}
}
}

function absoluteVersion(version: string): string {
if (version.length > 0 && (version[0] === '^' || version[0] === '~')) {
return version.substring(1);
}
return version;
}

async function upgradeLabDependencies(): Promise<void> {
const args: string[] = process.argv.slice(2);

if (args.length !== 2 || args[0] !== '--set-version') {
console.error('Usage: node script.js --set-version <version>');
process.exit(1);
}

const newVersion: string = args[1];
await updatePackageJson(newVersion);
}

upgradeLabDependencies();

0 comments on commit c0ddf01

Please sign in to comment.