Update monetization guide with modern purchase flows #48
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 Developer Guide Docs | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| paths: | |
| - 'docs/developer-guide/**' | |
| - 'docs/demos/**' | |
| - '.github/workflows/developer-guide-docs.yml' | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| actions: read | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Determine changed components | |
| id: changes | |
| if: github.event_name == 'pull_request' | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| demos: | |
| - 'docs/demos/**' | |
| docs: | |
| - 'docs/developer-guide/**' | |
| - name: Set up Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '11' | |
| - name: Build Codename One demos | |
| if: github.event_name != 'pull_request' || steps.changes.outputs.demos == 'true' | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$HOME/.codenameone" | |
| touch "$HOME/.codenameone/guibuilder.jar" | |
| cp maven/CodeNameOneBuildClient.jar "$HOME/.codenameone/CodeNameOneBuildClient.jar" | |
| xvfb-run -a mvn -B -ntp -Dgenerate-gui-sources-done=true -pl common -am -f docs/demos/pom.xml test | |
| - name: Determine publication metadata | |
| run: | | |
| set -euo pipefail | |
| REV_DATE="$(date -u +%Y-%m-%d)" | |
| VERSION="${GITHUB_HEAD_REF:-}" | |
| if [ -z "$VERSION" ] && [ -n "${GITHUB_REF_NAME:-}" ]; then | |
| VERSION="$GITHUB_REF_NAME" | |
| fi | |
| if [ -z "$VERSION" ] && [ -n "${GITHUB_SHA:-}" ]; then | |
| VERSION="${GITHUB_SHA:0:7}" | |
| fi | |
| VERSION="${VERSION#v}" | |
| if [ -z "$VERSION" ]; then | |
| VERSION="UNKNOWN" | |
| fi | |
| if date -u -d "$REV_DATE" '+%B %-d, %Y' >/tmp/rev_human 2>/dev/null; then | |
| REV_HUMAN_DATE="$(cat /tmp/rev_human)" | |
| else | |
| REV_HUMAN_DATE="$(date -u '+%B %-d, %Y')" | |
| fi | |
| echo "Developer guide workflow metadata:" >&2 | |
| echo " REV_DATE=$REV_DATE" >&2 | |
| echo " REV_NUMBER=$VERSION" >&2 | |
| echo " REV_HUMAN_DATE=$REV_HUMAN_DATE" >&2 | |
| { | |
| echo "REV_DATE=$REV_DATE" | |
| echo "REV_NUMBER=$VERSION" | |
| echo "REV_HUMAN_DATE=$REV_HUMAN_DATE" | |
| } >> "$GITHUB_ENV" | |
| - name: Render publication cover artwork | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends librsvg2-bin | |
| SOURCE="docs/developer-guide/book-cover.svg" | |
| GENERATED_SVG="docs/developer-guide/book-cover.generated.svg" | |
| GENERATED_PNG="docs/developer-guide/book-cover.generated.png" | |
| cp "$SOURCE" "$GENERATED_SVG" | |
| export GENERATED_SVG | |
| python3 scripts/developer-guide/prepare_cover_artwork.py \ | |
| "$GENERATED_SVG" \ | |
| --rev-number "$REV_NUMBER" \ | |
| --rev-human-date "$REV_HUMAN_DATE" \ | |
| --rev-date "$REV_DATE" | |
| echo "Rasterizing cover artwork to ${GENERATED_PNG}" >&2 | |
| rsvg-convert -w 2551 -h 3579 "$GENERATED_SVG" -o "$GENERATED_PNG" | |
| ls -l "$GENERATED_PNG" | |
| file "$GENERATED_PNG" | |
| { | |
| echo "COVER_IMAGE_ATTR=$(basename "$GENERATED_PNG")" | |
| echo "GENERATED_COVER_IMAGE=$GENERATED_PNG" | |
| echo "GENERATED_COVER_SVG=$GENERATED_SVG" | |
| } >> "$GITHUB_ENV" | |
| - name: Set up Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.1' | |
| - name: Install Asciidoctor tooling | |
| run: | | |
| gem install --no-document asciidoctor asciidoctor-pdf rouge | |
| - name: Run Asciidoctor lint | |
| run: | | |
| set -euo pipefail | |
| REPORT_DIR="build/developer-guide/reports" | |
| REPORT_FILE="${REPORT_DIR}/asciidoc-lint-report.txt" | |
| mkdir -p "$REPORT_DIR" | |
| set +e | |
| asciidoctor \ | |
| --require rouge \ | |
| --failure-level WARN \ | |
| --verbose \ | |
| --trace \ | |
| -o /dev/null \ | |
| docs/developer-guide/developer-guide.asciidoc \ | |
| 2>&1 | tee "$REPORT_FILE" | |
| STATUS=${PIPESTATUS[0]} | |
| set -e | |
| echo "ASCII_DOC_LINT_REPORT=$REPORT_FILE" >> "$GITHUB_ENV" | |
| echo "ASCII_DOC_LINT_STATUS=$STATUS" >> "$GITHUB_ENV" | |
| if [ "$STATUS" -ne 0 ]; then | |
| echo "Asciidoctor exited with status $STATUS" >&2 | |
| fi | |
| - name: Build Developer Guide HTML and PDF | |
| if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' | |
| run: | | |
| set -euo pipefail | |
| OUTPUT_ROOT="build/developer-guide" | |
| HTML_BUILD_DIR="${OUTPUT_ROOT}/html" | |
| PDF_BUILD_DIR="${OUTPUT_ROOT}/pdf" | |
| PACKAGE_DIR="${OUTPUT_ROOT}/html-package" | |
| COVER_IMAGE_ATTR_VALUE="${COVER_IMAGE_ATTR:-book-cover.svg}" | |
| GENERATED_COVER_IMAGE="${GENERATED_COVER_IMAGE:-}" | |
| GENERATED_COVER_SVG="${GENERATED_COVER_SVG:-}" | |
| echo "Building with cover image attribute: ${COVER_IMAGE_ATTR_VALUE}" >&2 | |
| mkdir -p "$HTML_BUILD_DIR" "$PDF_BUILD_DIR" | |
| asciidoctor \ | |
| -a revdate="$REV_DATE" \ | |
| -a revnumber="$REV_NUMBER" \ | |
| -a cover-image="$COVER_IMAGE_ATTR_VALUE" \ | |
| -D "$HTML_BUILD_DIR" \ | |
| -o developer-guide.html \ | |
| docs/developer-guide/developer-guide.asciidoc | |
| asciidoctor-pdf \ | |
| -a revdate="$REV_DATE" \ | |
| -a revnumber="$REV_NUMBER" \ | |
| -a cover-image="$COVER_IMAGE_ATTR_VALUE" \ | |
| -D "$PDF_BUILD_DIR" \ | |
| -o developer-guide.pdf \ | |
| docs/developer-guide/developer-guide.asciidoc | |
| rm -rf "$PACKAGE_DIR" | |
| mkdir -p "$PACKAGE_DIR" | |
| if [ -n "$GENERATED_COVER_IMAGE" ] && [ -f "$GENERATED_COVER_IMAGE" ]; then | |
| cp "$GENERATED_COVER_IMAGE" "$HTML_BUILD_DIR/$(basename "$GENERATED_COVER_IMAGE")" | |
| fi | |
| cp "$HTML_BUILD_DIR/developer-guide.html" "$PACKAGE_DIR/" | |
| for asset_dir in docs/developer-guide/*; do | |
| base_name="$(basename "$asset_dir")" | |
| if [ -d "$asset_dir" ] && [ "$base_name" != "sketch" ]; then | |
| cp -R "$asset_dir" "$PACKAGE_DIR/" | |
| fi | |
| done | |
| if [ -n "$GENERATED_COVER_IMAGE" ] && [ -f "$GENERATED_COVER_IMAGE" ]; then | |
| cp "$GENERATED_COVER_IMAGE" "$PACKAGE_DIR/$(basename "$GENERATED_COVER_IMAGE")" | |
| fi | |
| (cd "$PACKAGE_DIR" && zip -r "../developer-guide-html.zip" .) | |
| if [ -n "$GENERATED_COVER_IMAGE" ] && [ -f "$GENERATED_COVER_IMAGE" ]; then | |
| rm -f "$GENERATED_COVER_IMAGE" | |
| fi | |
| if [ -n "$GENERATED_COVER_SVG" ] && [ -f "$GENERATED_COVER_SVG" ]; then | |
| rm -f "$GENERATED_COVER_SVG" | |
| fi | |
| - name: Install Vale | |
| run: | | |
| set -euo pipefail | |
| VALE_VERSION="3.13.0" | |
| VALE_ARCHIVE="vale_${VALE_VERSION}_Linux_64-bit.tar.gz" | |
| curl -fsSL -o "$VALE_ARCHIVE" "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/${VALE_ARCHIVE}" | |
| tar -xzf "$VALE_ARCHIVE" | |
| sudo mv vale /usr/local/bin/vale | |
| rm -f "$VALE_ARCHIVE" | |
| - name: Sync Vale styles | |
| run: | | |
| set -euo pipefail | |
| vale sync --config docs/developer-guide/.vale.ini | |
| - name: Run Vale style linter | |
| run: | | |
| set -euo pipefail | |
| REPORT_DIR="build/developer-guide/reports" | |
| REPORT_FILE="${REPORT_DIR}/vale-report.json" | |
| HTML_REPORT="${REPORT_DIR}/vale-report.html" | |
| mkdir -p "$REPORT_DIR" | |
| set +e | |
| vale --config docs/developer-guide/.vale.ini --output=JSON docs/developer-guide > "$REPORT_FILE" | |
| STATUS=$? | |
| set -e | |
| python3 scripts/developer-guide/vale_report_to_html.py --input "$REPORT_FILE" --output "$HTML_REPORT" | |
| echo "VALE_REPORT=$REPORT_FILE" >> "$GITHUB_ENV" | |
| echo "VALE_HTML_REPORT=$HTML_REPORT" >> "$GITHUB_ENV" | |
| echo "VALE_STATUS=$STATUS" >> "$GITHUB_ENV" | |
| if [ "$STATUS" -ne 0 ]; then | |
| echo "Vale exited with status $STATUS" >&2 | |
| fi | |
| - name: Check for unused developer guide images | |
| run: | | |
| set -euo pipefail | |
| REPORT_DIR="build/developer-guide/reports" | |
| JSON_REPORT="${REPORT_DIR}/unused-images.json" | |
| TEXT_REPORT="${REPORT_DIR}/unused-images.txt" | |
| mkdir -p "$REPORT_DIR" | |
| python3 scripts/developer-guide/find_unused_images.py docs/developer-guide --output "$JSON_REPORT" | tee "$TEXT_REPORT" | |
| echo "UNUSED_IMAGES_JSON=$JSON_REPORT" >> "$GITHUB_ENV" | |
| echo "UNUSED_IMAGES_TEXT=$TEXT_REPORT" >> "$GITHUB_ENV" | |
| - name: Summarize AsciiDoc linter findings | |
| id: summarize_asciidoc_lint | |
| run: | | |
| python3 scripts/developer-guide/summarize_reports.py ascii \ | |
| --report "${ASCII_DOC_LINT_REPORT}" \ | |
| --status "${ASCII_DOC_LINT_STATUS:-0}" \ | |
| --output "${GITHUB_OUTPUT}" | |
| - name: Summarize Vale findings | |
| id: summarize_vale | |
| run: | | |
| python3 scripts/developer-guide/summarize_reports.py vale \ | |
| --report "${VALE_REPORT}" \ | |
| --status "${VALE_STATUS:-0}" \ | |
| --output "${GITHUB_OUTPUT}" | |
| - name: Summarize unused image findings | |
| id: summarize_unused_images | |
| run: | | |
| python3 scripts/developer-guide/summarize_reports.py unused-images \ | |
| --report "${UNUSED_IMAGES_JSON}" \ | |
| --output "${GITHUB_OUTPUT}" \ | |
| --details-key details \ | |
| --preview-limit 10 | |
| - name: Upload HTML artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: developer-guide-html | |
| path: build/developer-guide/developer-guide-html.zip | |
| if-no-files-found: error | |
| - name: Upload PDF artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: developer-guide-pdf | |
| path: build/developer-guide/pdf/developer-guide.pdf | |
| if-no-files-found: error | |
| - name: Upload AsciiDoc linter report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: developer-guide-asciidoc-lint | |
| path: ${{ env.ASCII_DOC_LINT_REPORT }} | |
| if-no-files-found: warn | |
| - name: Upload Vale report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: developer-guide-vale-report | |
| path: | | |
| ${{ env.VALE_REPORT }} | |
| ${{ env.VALE_HTML_REPORT }} | |
| if-no-files-found: warn | |
| - name: Upload unused image report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: developer-guide-unused-images | |
| path: | | |
| ${{ env.UNUSED_IMAGES_JSON }} | |
| ${{ env.UNUSED_IMAGES_TEXT }} | |
| if-no-files-found: warn | |
| - name: Comment with artifact download links | |
| if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }} | |
| uses: actions/github-script@v7 | |
| env: | |
| ASCII_SUMMARY: ${{ steps.summarize_asciidoc_lint.outputs.summary }} | |
| VALE_SUMMARY: ${{ steps.summarize_vale.outputs.summary }} | |
| UNUSED_SUMMARY: ${{ steps.summarize_unused_images.outputs.summary }} | |
| UNUSED_DETAILS: ${{ steps.summarize_unused_images.outputs.details }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const marker = '<!-- developer-guide-artifacts -->'; | |
| const { owner, repo } = context.repo; | |
| const runId = context.runId; | |
| const prNumber = context.payload.pull_request?.number; | |
| const runHeadSha = context.payload.pull_request?.head?.sha; | |
| if (prNumber && runHeadSha) { | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner, | |
| repo, | |
| pull_number: prNumber | |
| }); | |
| if (pr.head.sha !== runHeadSha) { | |
| console.log( | |
| `Skipping PR comment update because workflow run head ${runHeadSha} is not the latest PR head ${pr.head.sha}.` | |
| ); | |
| return; | |
| } | |
| } | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner, | |
| repo, | |
| run_id: runId, | |
| per_page: 100 | |
| }); | |
| const artifactLinks = new Map(); | |
| for (const artifact of artifacts.data.artifacts) { | |
| artifactLinks.set( | |
| artifact.name, | |
| `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${artifact.id}` | |
| ); | |
| } | |
| const links = []; | |
| if (artifactLinks.has('developer-guide-html')) { | |
| links.push(`- [Developer Guide HTML package](${artifactLinks.get('developer-guide-html')})`); | |
| } | |
| if (artifactLinks.has('developer-guide-pdf')) { | |
| links.push(`- [Developer Guide PDF](${artifactLinks.get('developer-guide-pdf')})`); | |
| } | |
| if (artifactLinks.has('developer-guide-asciidoc-lint')) { | |
| links.push(`- [AsciiDoc linter report](${artifactLinks.get('developer-guide-asciidoc-lint')})`); | |
| } | |
| if (artifactLinks.has('developer-guide-vale-report')) { | |
| links.push(`- [Vale report](${artifactLinks.get('developer-guide-vale-report')})`); | |
| } | |
| if (artifactLinks.has('developer-guide-unused-images')) { | |
| links.push(`- [Unused image report](${artifactLinks.get('developer-guide-unused-images')})`); | |
| } | |
| if (!links.length) { | |
| console.log('No artifacts found to report.'); | |
| return; | |
| } | |
| const qualityLines = []; | |
| const asciiSummary = process.env.ASCII_SUMMARY?.trim(); | |
| const valeSummary = process.env.VALE_SUMMARY?.trim(); | |
| const unusedSummary = process.env.UNUSED_SUMMARY?.trim(); | |
| const asciiLink = artifactLinks.get('developer-guide-asciidoc-lint'); | |
| const valeLink = artifactLinks.get('developer-guide-vale-report'); | |
| const unusedLink = artifactLinks.get('developer-guide-unused-images'); | |
| if (asciiSummary) { | |
| qualityLines.push(`- AsciiDoc linter: ${asciiSummary}${asciiLink ? ` ([report](${asciiLink}))` : ''}`); | |
| } | |
| if (valeSummary) { | |
| qualityLines.push(`- Vale: ${valeSummary}${valeLink ? ` ([report](${valeLink}))` : ''}`); | |
| } | |
| if (unusedSummary) { | |
| qualityLines.push(`- Image references: ${unusedSummary}${unusedLink ? ` ([report](${unusedLink}))` : ''}`); | |
| } | |
| let unusedDetails = process.env.UNUSED_DETAILS ? process.env.UNUSED_DETAILS.split('\n') : []; | |
| unusedDetails = unusedDetails.filter(Boolean); | |
| const detailsSection = unusedDetails.length | |
| ? `\nUnused image preview:\n\n${unusedDetails.map(line => ` ${line}`).join('\n')}\n` | |
| : ''; | |
| const sections = [ | |
| `${marker}`, | |
| 'Developer Guide build artifacts are available for download from this workflow run:', | |
| '', | |
| links.join('\n') | |
| ]; | |
| if (qualityLines.length) { | |
| sections.push('', 'Developer Guide quality checks:', '', qualityLines.join('\n')); | |
| } | |
| if (detailsSection) { | |
| sections.push(detailsSection.trimEnd()); | |
| } | |
| const body = sections.join('\n') + '\n'; | |
| const comments = await github.rest.issues.listComments({ | |
| owner, | |
| repo, | |
| issue_number: context.issue.number, | |
| per_page: 100 | |
| }); | |
| const existing = comments.data.find(comment => comment.body && 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: context.issue.number, | |
| body | |
| }); | |
| } | |
| - name: Log skipped PR comment | |
| if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }} | |
| run: echo "Skipping PR comment because the workflow run does not have permission to post on forked pull requests." | |
| - name: Attach artifacts to release | |
| if: ${{ github.event_name == 'release' && github.event.action == 'published' }} | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: | | |
| build/developer-guide/developer-guide-html.zip | |
| build/developer-guide/pdf/developer-guide.pdf |