diff --git a/.gitignore b/.gitignore
index 4a0603c6053..52b7c2bb39f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,8 +33,10 @@ web-types.lit.json
# Generated auto-complete CSS file for Lumo theme
packages/vaadin-lumo-styles/auto-complete.css
-# Generated release check
-scripts/check-releases.html
+# Release app
+release-app/node_modules
+release-app/dist
+release-app/branches.json
# TypeScript output
tsconfig.build.tsbuildinfo
diff --git a/eslint.config.js b/eslint.config.js
index 8ea2b27e9e0..f91502c9af2 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -183,6 +183,15 @@ export default [
'no-console': 'off',
},
},
+ {
+ files: ['release-app/**/*'],
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ },
+ },
+ },
{
files: ['packages/**/test/**', 'test/integration/**'],
languageOptions: {
diff --git a/package.json b/package.json
index 2219d0312d6..3e4adbb0d8a 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"serve:dist": "web-dev-server --app-index dist/index.html --open",
"start": "web-dev-server --node-resolve --open /dev",
"start:base": "web-dev-server --node-resolve --open /dev --theme=base",
- "check-releases": "node ./scripts/check-releases.js && web-dev-server --node-resolve --open /scripts/check-releases.html",
+ "release-app": "node ./release-app/check-branches.js && npx -y vite@7.0.0 build --config release-app/vite.config.js && npx -y electron@37.1.0 ./release-app",
"test": "web-test-runner",
"test:base": "yarn test --config web-test-runner-base.config.js",
"test:firefox": "yarn test --config web-test-runner-firefox.config.js",
diff --git a/release-app/app.js b/release-app/app.js
new file mode 100644
index 00000000000..f2d43decd55
--- /dev/null
+++ b/release-app/app.js
@@ -0,0 +1,230 @@
+import '@vaadin/vaadin-lumo-styles/test/autoload.js';
+import '@vaadin/button';
+import '@vaadin/details';
+import '@vaadin/dialog';
+import '@vaadin/text-field';
+import { html, LitElement } from 'lit';
+import { dialogRenderer } from '@vaadin/dialog/lit.js';
+import branches from './branches.json';
+
+class ReleaseApp extends LitElement {
+ createRenderRoot() {
+ return this;
+ }
+
+ render() {
+ const branchSections = branches.map((branch) => html` `);
+ return html`
+
+
Release app
+ ${branchSections}
+
+ `;
+ }
+}
+
+customElements.define('ra-app', ReleaseApp);
+
+class BranchSection extends LitElement {
+ static properties = {
+ branch: { type: Object },
+ };
+
+ createRenderRoot() {
+ return this;
+ }
+
+ firstUpdated() {
+ this.dialog = this.querySelector('vaadin-dialog');
+ }
+
+ render() {
+ return html`
+
+ ${this.branch.branch}
+
+
+
+ Current version |
+ ${this.branch.currentVersion} |
+
+
+ Next patch version |
+ ${this.branch.nextPatchVersion} |
+
+
+ Unreleased commits |
+ ${this.branch.commits.length} |
+
+
+
+ ${this.branch.commits.length > 0
+ ? html`
+
+
+
+ `
+ : html`No unreleased commits
`}
+
+ {
+ this.dialog.opened = true;
+ }}"
+ >Release instructions
+
+
+ html` `, [])}
+ >
+
+ `;
+ }
+}
+
+customElements.define('branch-section', BranchSection);
+
+class ReleaseDialog extends LitElement {
+ static properties = {
+ branch: { type: Object },
+ releaseVersion: { type: String },
+ };
+
+ createRenderRoot() {
+ return this;
+ }
+
+ firstUpdated() {
+ // Initialize the release version with the next patch version of the branch
+ this.releaseVersion = this.branch.nextPatchVersion;
+ }
+
+ render() {
+ return html`
+
+ {
+ this.releaseVersion = e.target.value;
+ }}"
+ >
+
+
+ Step 1: Release web components
+
+ -
+ Open CI build on Bender
+
+ -
+ Wait until the page has loaded and verify it prefilled these values:
+
+
+ Branch |
+ ${this.branch.branch} |
+
+
+ Version |
+ ${this.releaseVersion} |
+
+
+
+ - Click Run build
+ - Confirm if a dialog asks about using an auto-generated branch name
+ - Wait for the build to finish, then check that new versions have appeared on NPM (e.g.
+
@vaadin/grid@${this.releaseVersion}
)
+
+
+
+
+
+ Step 2: Release API docs
+
+ -
+ Open CI build on Bender
+
+ -
+ Wait until the page has loaded and verify it prefilled these values:
+
+
+ Branch |
+ ${this.branch.branch} |
+
+
+ Tag |
+ v${this.releaseVersion} |
+
+
+ Version |
+ ${this.releaseVersion} |
+
+
+
+ - Click Run build
+ - Confirm if a dialog asks about using an auto-generated branch name
+ - Wait for the build to finish
+
+
+
+
+ Step 3: Publish GitHub release
+
+
+
+
+ Step 4: Update Flow components to use the new version
+
+
+ `;
+ }
+}
+
+customElements.define('ra-release-dialog', ReleaseDialog);
diff --git a/scripts/check-releases.js b/release-app/check-branches.js
similarity index 90%
rename from scripts/check-releases.js
rename to release-app/check-branches.js
index 1f3ba43b93f..edc9b547d69 100644
--- a/scripts/check-releases.js
+++ b/release-app/check-branches.js
@@ -1,5 +1,6 @@
import dotenv from 'dotenv';
import fs from 'node:fs';
+import path from 'node:path';
dotenv.config();
@@ -98,13 +99,10 @@ async function run() {
}
}
- const template = fs.readFileSync('./scripts/check-releases.template.html', 'utf8');
- const filledTemplate = template.replace(
- '/*{inject-branch-info}*/',
- `window.branches = ${JSON.stringify(branchInfos, null, 2)};`,
- );
+ const json = JSON.stringify(branchInfos);
+ const outputPath = path.resolve('release-app', 'branches.json');
- fs.writeFileSync('./scripts/check-releases.html', filledTemplate, 'utf8');
+ fs.writeFileSync(outputPath, json, 'utf8');
}
run();
diff --git a/release-app/empty.html b/release-app/empty.html
new file mode 100644
index 00000000000..bd9137a2b3c
--- /dev/null
+++ b/release-app/empty.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+Click on a link on the left pane to open it in this pane.
+
+
diff --git a/release-app/index.html b/release-app/index.html
new file mode 100644
index 00000000000..150de9774a1
--- /dev/null
+++ b/release-app/index.html
@@ -0,0 +1,68 @@
+
+
+
+
+ Release app
+
+
+
+
+
+
+
diff --git a/release-app/main.js b/release-app/main.js
new file mode 100644
index 00000000000..87577750056
--- /dev/null
+++ b/release-app/main.js
@@ -0,0 +1,47 @@
+import { app, BaseWindow, WebContentsView } from 'electron';
+import fs from 'node:fs';
+import path from 'node:path';
+
+const createWindow = () => {
+ const win = new BaseWindow({ width: 1200, height: 800 });
+
+ const view1 = new WebContentsView();
+ const view2 = new WebContentsView({ webPreferences: { partition: 'persist:release-app' } });
+
+ const view1Url = `file://${path.resolve('release-app', 'dist', 'index.html')}`;
+ win.contentView.addChildView(view1);
+ view1.webContents.loadURL(view1Url);
+ view1.webContents.setWindowOpenHandler(({ url }) => {
+ view2.webContents.loadURL(url);
+ return { action: 'deny' };
+ });
+
+ const view2Url = `file://${path.resolve('release-app', 'empty.html')}`;
+ const prefillScript = fs.readFileSync(path.resolve('release-app', 'prefill-build-info.js'), 'utf8');
+ win.contentView.addChildView(view2);
+ view2.webContents.loadURL(view2Url);
+ view2.webContents.on('did-finish-load', () => {
+ view2.webContents.executeJavaScript(prefillScript);
+ });
+
+ function resizeViews() {
+ const bounds = win.getBounds();
+ const width = bounds.width;
+ const height = bounds.height;
+ const halfWidth = Math.floor(width / 2);
+
+ view1.setBounds({ x: 0, y: 0, width: halfWidth, height });
+ view2.setBounds({ x: halfWidth, y: 0, width: halfWidth, height });
+ }
+
+ resizeViews();
+ win.on('resize', resizeViews);
+};
+
+app.whenReady().then(() => {
+ createWindow();
+});
+
+app.on('window-all-closed', () => {
+ app.quit();
+});
diff --git a/release-app/package.json b/release-app/package.json
new file mode 100644
index 00000000000..9375fc4a8ae
--- /dev/null
+++ b/release-app/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "release-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "main.js",
+ "type": "module"
+}
diff --git a/release-app/prefill-build-info.js b/release-app/prefill-build-info.js
new file mode 100644
index 00000000000..3d745080091
--- /dev/null
+++ b/release-app/prefill-build-info.js
@@ -0,0 +1,54 @@
+async function prefill() {
+ // Skip if we are not on a TeamCity page
+ if (!window.location.href.includes('bender.vaadin.com')) {
+ return;
+ }
+
+ // Wait for custom run button to appear
+ // Use loop of 5 iterations with 1s delay to wait for the button to appear
+ for (let i = 0; i < 5; i++) {
+ if (document.querySelector('[data-hint-container-id="custom-run"]')) {
+ break;
+ }
+ await new Promise((resolve) => {
+ setTimeout(resolve, 1000);
+ });
+ }
+
+ // Open custom build dialog
+ document.querySelector('[data-hint-container-id="custom-run"]').closest('button').click();
+ await new Promise((resolve) => {
+ setTimeout(resolve, 1000);
+ });
+ document.querySelector('#runCustomBuildDiv #customRunPropertiesRequired').closest('li').click();
+ await new Promise((resolve) => {
+ setTimeout(resolve, 1000);
+ });
+
+ // Get parameters from URL query
+ const urlParams = new URLSearchParams(window.location.search);
+ const branch = urlParams.get('release_branch');
+ const version = urlParams.get('release_version');
+ const tag = urlParams.get('release_tag');
+
+ // Prefill checkout branch
+ const branchSelect = document.querySelector('#runCustomBuildDiv select[name^="parameter_CheckoutBranch"]');
+ if (branchSelect && branch) {
+ branchSelect.value = branch;
+ branchSelect.closest('tr').classList.add('modifiedParam');
+ }
+ // Prefill version
+ const versionInput = document.querySelector('#runCustomBuildDiv textarea[name^="parameter_version"]');
+ if (versionInput && version) {
+ versionInput.value = version;
+ versionInput.closest('tr').classList.add('modifiedParam');
+ }
+ // Prefill tag
+ const tagInput = document.querySelector('#runCustomBuildDiv textarea[name^="parameter_tag"]');
+ if (tagInput && tag) {
+ tagInput.value = tag;
+ tagInput.closest('tr').classList.add('modifiedParam');
+ }
+}
+
+prefill();
diff --git a/release-app/vite.config.js b/release-app/vite.config.js
new file mode 100644
index 00000000000..39720a134e4
--- /dev/null
+++ b/release-app/vite.config.js
@@ -0,0 +1,7 @@
+import path from 'node:path';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ root: path.resolve('release-app'),
+ base: './',
+});
diff --git a/scripts/check-releases.template.html b/scripts/check-releases.template.html
deleted file mode 100644
index 8c8eaf953ac..00000000000
--- a/scripts/check-releases.template.html
+++ /dev/null
@@ -1,265 +0,0 @@
-
-
-
-
- Release check
-
-
-
- Release check
-
-
-
-