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

`} + + 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` +
+ +
+
+

Step 1: Release web components

+ +
+ +
+

Step 2: Release API docs

+ +
+ +
+

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

- - - -