InteractionDialog: host on the right form + don't lose queued sibling dialogs (#5193) #2189
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Hugo Website | |
| on: | |
| pull_request: | |
| paths: | |
| - 'docs/website/**' | |
| - 'docs/developer-guide/**' | |
| - 'scripts/cn1playground/**' | |
| - 'scripts/initializr/**' | |
| - 'scripts/website/**' | |
| - 'scripts/skindesigner/**' | |
| # The Initializr/playground/skindesigner JS bundles are built from | |
| # JavaScriptPort sources and the ParparVM translator at workflow | |
| # time, so a change in either also needs to redeploy the website. | |
| # Without these paths a JS-port bug fix (e.g. dialog rendering | |
| # in be3bc6dcd) sits in the branch with no Cloudflare preview | |
| # refresh until an unrelated docs change happens to trigger | |
| # the workflow. | |
| - 'Ports/JavaScriptPort/**' | |
| - 'vm/ByteCodeTranslator/**' | |
| - 'vm/JavaAPI/**' | |
| - 'CodenameOne/src/**' | |
| # The Hugo build embeds JavaDocs produced by this script, so a change | |
| # to how the docs are generated must redeploy the website too. | |
| - '.github/scripts/build_javadocs.sh' | |
| - '.github/workflows/website-docs.yml' | |
| push: | |
| branches: [main, master] | |
| paths: | |
| - 'docs/website/**' | |
| - 'docs/developer-guide/**' | |
| - 'scripts/cn1playground/**' | |
| - 'scripts/initializr/**' | |
| - 'scripts/website/**' | |
| - 'scripts/skindesigner/**' | |
| - 'Ports/JavaScriptPort/**' | |
| - 'vm/ByteCodeTranslator/**' | |
| - 'vm/JavaAPI/**' | |
| - 'CodenameOne/src/**' | |
| # The Hugo build embeds JavaDocs produced by this script, so a change | |
| # to how the docs are generated must redeploy the website too. | |
| - '.github/scripts/build_javadocs.sh' | |
| - '.github/workflows/website-docs.yml' | |
| workflow_dispatch: | |
| # Daily production rebuild so future-dated blog posts (the daily release | |
| # follow-ups) publish on their own date with no manual push. Production | |
| # builds keep HUGO_BUILD_FUTURE=false, so a post only appears once its date | |
| # has arrived (UTC; hugo.toml sets no timeZone). Runs at 00:15 UTC; GitHub | |
| # may delay scheduled runs under load, but the post still surfaces same-day. | |
| schedule: | |
| - cron: '15 0 * * *' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| env: | |
| CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN || secrets.CF_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID || secrets.CF_ACCOUNT_ID }} | |
| CF_PAGES_PROJECT_NAME: ${{ vars.CLOUDFLARE_PAGES_PROJECT_NAME || 'codenameone' }} | |
| CF_PAGES_PRODUCTION_BRANCH: ${{ vars.CLOUDFLARE_PAGES_PRODUCTION_BRANCH || 'main' }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Hugo | |
| uses: peaceiris/actions-hugo@v3 | |
| with: | |
| hugo-version: 'latest' | |
| extended: true | |
| - name: Set up Java 8 for Initializr build | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '8' | |
| - name: Set up Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.3' | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| - name: Install Asciidoctor tooling | |
| run: | | |
| set -euo pipefail | |
| gem install --no-document asciidoctor rouge | |
| - name: Download latest OTA skins | |
| run: | | |
| set -euo pipefail | |
| scripts/website/fetch_ota_skins.sh | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| - name: Update developer guide PDF redirect | |
| run: | | |
| set -euo pipefail | |
| scripts/website/update_developer_guide_redirect.sh | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| - name: Set up Java 17 for Skin Designer screenshots | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Set up xvfb for headless simulator | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends xvfb | |
| - name: Bootstrap Codename One tooling for screenshots | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$HOME/.codenameone" | |
| cp maven/CodeNameOneBuildClient.jar "$HOME/.codenameone/CodeNameOneBuildClient.jar" | |
| touch "$HOME/.codenameone/guibuilder.jar" | |
| - name: Generate Skin Designer screenshots | |
| run: ./scripts/skindesigner/screenshots/take-screenshots.sh | |
| - name: Set up Java 25 for website build | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '25' | |
| - name: Build website | |
| run: | | |
| set -euo pipefail | |
| scripts/website/build.sh | |
| env: | |
| WEBSITE_INCLUDE_JAVADOCS: "true" | |
| WEBSITE_INCLUDE_DEVGUIDE: "true" | |
| WEBSITE_INCLUDE_INITIALIZR: "auto" | |
| WEBSITE_INCLUDE_PLAYGROUND: "auto" | |
| # PR previews build with future-dated posts visible so reviewers | |
| # can read posts staged for later in the week. Production deploys | |
| # (push to master) keep the default so future posts only appear | |
| # on their actual date. | |
| HUGO_BUILD_FUTURE: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} | |
| CN1_USER: ${{ secrets.CN1_USER }} | |
| CN1_TOKEN: ${{ secrets.CN1_TOKEN }} | |
| - name: Validate Initializr page output | |
| run: | | |
| set -euo pipefail | |
| test -f docs/website/public/initializr/index.html | |
| - name: Validate Playground page output | |
| run: | | |
| set -euo pipefail | |
| test -f docs/website/public/playground/index.html | |
| - name: Validate Initializr JS bundle output | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${CN1_USER}" ] && [ -n "${CN1_TOKEN}" ]; then | |
| test -f docs/website/public/initializr-app/index.html | |
| else | |
| echo "CN1 credentials not configured; skipping Initializr JS bundle output validation." | |
| fi | |
| env: | |
| CN1_USER: ${{ secrets.CN1_USER }} | |
| CN1_TOKEN: ${{ secrets.CN1_TOKEN }} | |
| - name: Validate Playground JS bundle output | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${CN1_USER}" ] && [ -n "${CN1_TOKEN}" ]; then | |
| test -f docs/website/public/playground-app/index.html | |
| else | |
| echo "CN1 credentials not configured; skipping Playground JS bundle output validation." | |
| fi | |
| env: | |
| CN1_USER: ${{ secrets.CN1_USER }} | |
| CN1_TOKEN: ${{ secrets.CN1_TOKEN }} | |
| - name: Validate redirects against local Pages runtime | |
| run: | | |
| set -euo pipefail | |
| PORT=8788 | |
| cd docs/website | |
| npx --yes wrangler@4 pages dev public \ | |
| --port "${PORT}" \ | |
| --ip 127.0.0.1 \ | |
| --compatibility-date=2026-02-18 > /tmp/wrangler-pages-dev.log 2>&1 & | |
| WRANGLER_PID=$! | |
| cleanup() { | |
| kill "${WRANGLER_PID}" >/dev/null 2>&1 || true | |
| } | |
| trap cleanup EXIT | |
| print_log_on_error() { | |
| echo "wrangler pages dev log (tail):" >&2 | |
| tail -n 200 /tmp/wrangler-pages-dev.log >&2 || true | |
| } | |
| trap print_log_on_error ERR | |
| for i in $(seq 1 60); do | |
| if curl -sSf "http://127.0.0.1:${PORT}/" >/dev/null; then | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| if ! curl -sSf "http://127.0.0.1:${PORT}/" >/dev/null; then | |
| echo "wrangler pages dev did not become ready on port ${PORT}" >&2 | |
| exit 1 | |
| fi | |
| cd ../.. | |
| python3 scripts/website/test_redirects.py \ | |
| --base-url "http://127.0.0.1:${PORT}" \ | |
| --redirects-file docs/website/public/_redirects \ | |
| --skip-auto-cases | |
| - name: Validate internal links and images | |
| uses: lycheeverse/lychee-action@v2 | |
| with: | |
| # Pin to v0.23.0. lychee v0.24.0 renamed its release assets | |
| # from `lychee-<arch>-unknown-linux-gnu.tar.gz` to | |
| # `lychee-lychee-v0.24.0-<arch>-unknown-linux-gnu.tar.gz`, | |
| # and `lychee-action@v2` still constructs the old URL, so | |
| # `lycheeVersion: latest` 404s and breaks this job with | |
| # curl exit code 22. Bump once the action catches up. | |
| lycheeVersion: v0.23.0 | |
| workingDirectory: docs/website | |
| args: >- | |
| --offline | |
| --no-progress | |
| --root-dir public | |
| public/**/*.html | |
| - name: Reject absolute codenameone.com links | |
| run: | | |
| set -euo pipefail | |
| report="docs/website/reports/disallowed-codenameone-links.txt" | |
| mkdir -p "$(dirname "$report")" | |
| rg -n --no-heading -S '\]\((https?:)?//(www\.)?codenameone\.com([/:?#)]|$)|<https?://(www\.)?codenameone\.com([/:?#>]|$)|(href|src)=["'"'"']https?://(www\.)?codenameone\.com([/:?#]|$)' \ | |
| docs/website/content docs/website/layouts docs/website/static > "$report" || true | |
| if [ -s "$report" ]; then | |
| echo "Disallowed absolute codenameone.com links found (use relative URLs or non-www subdomains):" >&2 | |
| sed -n '1,200p' "$report" >&2 | |
| exit 1 | |
| fi | |
| - name: Upload codenameone link policy report | |
| if: ${{ always() }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: codenameone-link-policy-report | |
| path: docs/website/reports/disallowed-codenameone-links.txt | |
| if-no-files-found: ignore | |
| - name: Validate OTA skin output | |
| run: | | |
| set -euo pipefail | |
| test -f docs/website/public/OTA/Skins.xml | |
| - name: Validate developer guide redirect output | |
| run: | | |
| set -euo pipefail | |
| test -f docs/website/public/_redirects | |
| test -f docs/website/public/developer-guide/index.html | |
| grep -Eq '^/files/developer-guide\.pdf https://github\.com/codenameone/CodenameOne/releases/download/.+/developer-guide\.pdf 302$' docs/website/public/_redirects | |
| grep -Eq '^/manual /developer-guide/ 301$' docs/website/public/_redirects | |
| grep -Eq '^/manual/ /developer-guide/ 301$' docs/website/public/_redirects | |
| grep -Eq '^/developer-guide /developer-guide/ 301$' docs/website/public/_redirects | |
| - name: Validate RSS output and alias | |
| run: | | |
| set -euo pipefail | |
| test -f docs/website/public/blog/index.xml | |
| test ! -f docs/website/public/index.xml | |
| grep -Eq '^/feed\.xml /blog/index\.xml 302$' docs/website/public/_redirects | |
| - name: Upload built site artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: website-preview | |
| path: docs/website/public | |
| if-no-files-found: error | |
| - name: Check Cloudflare preview deploy credentials | |
| if: ${{ github.event_name == 'pull_request' && env.CLOUDFLARE_TOKEN == '' }} | |
| run: | | |
| echo "::warning::Skipping Cloudflare Pages preview deploy because no API token secret is configured. Set CLOUDFLARE_TOKEN (preferred) or CF_API_TOKEN." | |
| - name: Deploy PR preview to Cloudflare Pages | |
| id: cf_preview | |
| if: ${{ github.event_name == 'pull_request' && env.CLOUDFLARE_TOKEN != '' }} | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ env.CLOUDFLARE_TOKEN }} | |
| PREVIEW_BRANCH: pr-${{ github.event.pull_request.number }}-website-preview | |
| run: | | |
| set -uo pipefail | |
| # Stream wrangler output to the job log (via tee) while still | |
| # capturing it so we can pull the *.pages.dev preview URL out. The | |
| # previous `deploy_output=$(... 2>&1)` form hid every line — when | |
| # wrangler died without any stdout we had nothing to debug with. | |
| # -e is intentionally off for the wrangler invocation so we can | |
| # report its exit status explicitly instead of exiting opaquely. | |
| deploy_log="$(mktemp)" | |
| npx --yes wrangler@4 pages deploy docs/website/public \ | |
| --project-name "${CF_PAGES_PROJECT_NAME}" \ | |
| --branch "${PREVIEW_BRANCH}" 2>&1 | tee "${deploy_log}" | |
| wrangler_status="${PIPESTATUS[0]}" | |
| if [ "${wrangler_status}" -ne 0 ]; then | |
| echo "wrangler pages deploy exited with status ${wrangler_status}" >&2 | |
| exit "${wrangler_status}" | |
| fi | |
| preview_url="$(grep -Eo 'https://[A-Za-z0-9._-]+\.pages\.dev' "${deploy_log}" | tail -n1 || true)" | |
| if [ -z "${preview_url}" ]; then | |
| echo "Could not determine Cloudflare preview URL from deploy output." >&2 | |
| exit 1 | |
| fi | |
| echo "preview_url=${preview_url}" >> "${GITHUB_OUTPUT}" | |
| echo "preview_branch=${PREVIEW_BRANCH}" >> "${GITHUB_OUTPUT}" | |
| - name: Publish Cloudflare preview link in CI summary | |
| if: ${{ steps.cf_preview.outputs.preview_url != '' }} | |
| run: | | |
| { | |
| echo "## Cloudflare Preview" | |
| echo | |
| echo "- URL: ${{ steps.cf_preview.outputs.preview_url }}" | |
| echo "- Branch: \`${{ steps.cf_preview.outputs.preview_branch }}\`" | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| - name: Comment Cloudflare preview link on PR | |
| if: ${{ github.event_name == 'pull_request' && steps.cf_preview.outputs.preview_url != '' }} | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const marker = '<!-- cn1-cloudflare-preview -->'; | |
| const body = [ | |
| marker, | |
| '## Cloudflare Preview', | |
| '', | |
| `- URL: ${{ steps.cf_preview.outputs.preview_url }}`, | |
| `- Branch: \`${{ steps.cf_preview.outputs.preview_branch }}\``, | |
| ].join('\n'); | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, | |
| repo, | |
| issue_number, | |
| per_page: 100, | |
| }); | |
| const existing = comments.find((comment) => | |
| comment.user?.type === 'Bot' && comment.body?.includes(marker) | |
| ); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number, | |
| body, | |
| }); | |
| } | |
| - name: Prune older Cloudflare preview deployments for this PR branch | |
| if: ${{ steps.cf_preview.outputs.preview_url != '' }} | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ env.CLOUDFLARE_TOKEN }} | |
| PREVIEW_BRANCH: ${{ steps.cf_preview.outputs.preview_branch }} | |
| PREVIEW_URL: ${{ steps.cf_preview.outputs.preview_url }} | |
| run: | | |
| set -euo pipefail | |
| api_base="https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${CF_PAGES_PROJECT_NAME}/deployments" | |
| tmp_json="$(mktemp)" | |
| curl -sS \ | |
| -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| "${api_base}" > "${tmp_json}" | |
| cat > /tmp/cf_preview_cleanup.py <<'PY' | |
| import json | |
| import sys | |
| path, preview_branch, preview_url = sys.argv[1:] | |
| data = json.load(open(path, encoding="utf-8")) | |
| result = data.get("result", []) or [] | |
| def aliases_of(dep): | |
| aliases = dep.get("aliases") or [] | |
| out = [] | |
| for a in aliases: | |
| if isinstance(a, str): | |
| out.append(a) | |
| elif isinstance(a, dict): | |
| v = a.get("url") | |
| if isinstance(v, str): | |
| out.append(v) | |
| return out | |
| def branch_of(dep): | |
| trig = dep.get("deployment_trigger") or {} | |
| meta = trig.get("metadata") or {} | |
| return meta.get("branch") | |
| current_id = None | |
| for dep in result: | |
| if preview_url in aliases_of(dep): | |
| current_id = dep.get("id") | |
| break | |
| for dep in result: | |
| dep_id = dep.get("id") | |
| if not dep_id or dep_id == current_id: | |
| continue | |
| if branch_of(dep) == preview_branch: | |
| print(dep_id) | |
| PY | |
| python3 /tmp/cf_preview_cleanup.py "${tmp_json}" "${PREVIEW_BRANCH}" "${PREVIEW_URL}" > /tmp/cf_deployments_to_delete.txt | |
| while IFS= read -r dep_id; do | |
| [ -z "${dep_id}" ] && continue | |
| echo "Deleting old preview deployment: ${dep_id}" | |
| curl -sS -X DELETE \ | |
| -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| "${api_base}/${dep_id}" >/dev/null | |
| done < /tmp/cf_deployments_to_delete.txt | |
| - name: Check Cloudflare deploy credentials | |
| if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master')) || github.event_name == 'schedule') && env.CLOUDFLARE_TOKEN == '' }} | |
| run: | | |
| echo "::warning::Skipping Cloudflare Pages deploy because no API token secret is configured. Set CLOUDFLARE_TOKEN (preferred) or CF_API_TOKEN." | |
| - name: Deploy to Cloudflare Pages | |
| if: ${{ ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master')) || github.event_name == 'schedule') && env.CLOUDFLARE_TOKEN != '' }} | |
| uses: cloudflare/wrangler-action@v3 | |
| env: | |
| # Keep these env vars explicit so Wrangler can authenticate in non-interactive CI. | |
| CLOUDFLARE_API_TOKEN: ${{ env.CLOUDFLARE_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ env.CLOUDFLARE_ACCOUNT_ID }} | |
| with: | |
| # Also pass through action inputs for compatibility with wrangler-action versions. | |
| apiToken: ${{ env.CLOUDFLARE_TOKEN }} | |
| accountId: ${{ env.CLOUDFLARE_ACCOUNT_ID }} | |
| command: >- | |
| pages deploy docs/website/public | |
| --project-name=${{ env.CF_PAGES_PROJECT_NAME }} | |
| --branch=${{ env.CF_PAGES_PRODUCTION_BRANCH }} | |
| # touched to retrigger Hugo workflow (path filter matches this file) |