diff --git a/.github/scripts/README.md b/.github/scripts/README.md new file mode 100644 index 000000000..04ec4206a --- /dev/null +++ b/.github/scripts/README.md @@ -0,0 +1,43 @@ +# GitHub Actions Scripts + +This directory contains helper scripts used by the CI/CD pipeline. + +## determine-widget-scope.sh + +Determines which widgets and JS actions should be built and tested based on changed files or manual input. + +### Usage + +```bash +./determine-widget-scope.sh +``` + +### Outputs + +| Output | Description | +| -------------------- | ---------------------------------------------------- | +| `scope` | pnpm filter scope for building | +| `widgets` | JSON array of widgets to BUILD | +| `widgets_to_test` | JSON array of widgets to TEST (used for test matrix) | +| `js_actions_changed` | Boolean flag indicating if JS actions changed | + +### Pipeline Behavior + +| Scenario | Widgets Built | Widgets Tested | JS Actions Built | JS Actions Tested | +| ---------------------- | --------------- | --------------- | ---------------- | ----------------- | +| Only JS actions change | All | None | Yes | Yes | +| Only widgets change | Changed only | Changed only | No | No | +| Both change | Changed widgets | Changed widgets | Yes | Yes | +| Full run (`*-native`) | All | All | Yes | Yes | +| Manual: `js-actions` | All | None | Yes | Yes | + +### Why build all widgets when only JS actions change? + +The JS action tests run against a full test project that requires all widgets to be present. Without building all widgets, the test project would be incomplete and tests would fail. + +## Other Scripts + +- **determine-nt-version.py** - Determines the Native Template version based on Mendix version +- **mxbuild.Dockerfile** - Docker image for mxbuild +- **setup-runtime.sh** - Sets up the Mendix runtime +- **start-runtime-with-verification.sh** - Starts the runtime with health verification diff --git a/.github/scripts/determine-widget-scope.sh b/.github/scripts/determine-widget-scope.sh index 2070918a2..4983cd507 100644 --- a/.github/scripts/determine-widget-scope.sh +++ b/.github/scripts/determine-widget-scope.sh @@ -44,39 +44,65 @@ if [ "$event_name" == "pull_request" ]; then selected_workspaces=$(echo $selected_workspaces | xargs) # Build the final scope and widgets output + # Note: widgets output is used for both BUILDING and the widget TEST MATRIX + # When only JS actions change, widgets_to_test is empty (no widget tests needed) + # but we still need to build all widgets for the test project if [[ -n "$selected_workspaces" ]] && [[ "$js_actions_changed" == "true" ]]; then # Both widgets and JS actions changed # Convert space-separated widget names to JSON array format widget_array=$(echo "$selected_workspaces" | sed 's/ /","/g') echo "scope=--all --include '$selected_workspaces mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT - echo "widgets=[\"$widget_array\",\"mobile-resources-native\",\"nanoflow-actions-native\"]" >> $GITHUB_OUTPUT + echo "widgets=[\"$widget_array\"]" >> $GITHUB_OUTPUT + echo "widgets_to_test=[\"$widget_array\"]" >> $GITHUB_OUTPUT + echo "js_actions_changed=true" >> $GITHUB_OUTPUT elif [[ -n "$selected_workspaces" ]] && [[ "$js_actions_changed" == "false" ]]; then # Only widgets changed widget_array=$(echo "$selected_workspaces" | sed 's/ /","/g') echo "scope=--all --include '$selected_workspaces'" >> $GITHUB_OUTPUT echo "widgets=[\"$widget_array\"]" >> $GITHUB_OUTPUT + echo "widgets_to_test=[\"$widget_array\"]" >> $GITHUB_OUTPUT + echo "js_actions_changed=false" >> $GITHUB_OUTPUT elif [[ -z "$selected_workspaces" ]] && [[ "$js_actions_changed" == "true" ]]; then - # Only JS actions changed - echo "scope=--all --include 'mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT - echo "widgets=[\"mobile-resources-native\",\"nanoflow-actions-native\"]" >> $GITHUB_OUTPUT + # Only JS actions changed - need to build ALL widgets because JS action tests + # require the full test project with all widgets to function properly + # But widget tests should NOT run (empty widgets_to_test) + echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT + echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT + echo "widgets_to_test=[]" >> $GITHUB_OUTPUT + echo "js_actions_changed=true" >> $GITHUB_OUTPUT else # No specific changes detected in widgets or JS actions, run everything echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT - echo "widgets=${all_widgets_and_js}" >> $GITHUB_OUTPUT + echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT + echo "widgets_to_test=${all_widgets}" >> $GITHUB_OUTPUT + echo "js_actions_changed=true" >> $GITHUB_OUTPUT fi else if [ -n "$input_workspace" ] && [ "$input_workspace" != "*-native" ] && [ "$input_workspace" != "js-actions" ]; then + # Specific widget(s) selected selected_workspaces=$(echo "$input_workspace" | sed 's/,/ /g') echo "scope=--all --include '${selected_workspaces}'" >> $GITHUB_OUTPUT echo "widgets=[\"$input_workspace\"]" >> $GITHUB_OUTPUT + echo "widgets_to_test=[\"$input_workspace\"]" >> $GITHUB_OUTPUT + echo "js_actions_changed=false" >> $GITHUB_OUTPUT elif [ "$input_workspace" == "js-actions" ]; then - echo "scope=--all --include 'mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT - echo "widgets=[\"mobile-resources-native\",\"nanoflow-actions-native\"]" >> $GITHUB_OUTPUT + # JS actions selected - need to build ALL widgets because JS action tests + # require the full test project with all widgets to function properly + # But widget tests should NOT run (empty widgets_to_test) + echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT + echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT + echo "widgets_to_test=[]" >> $GITHUB_OUTPUT + echo "js_actions_changed=true" >> $GITHUB_OUTPUT else + # All widgets (*-native) or default - run everything echo "scope=--all --include '*-native mobile-resources-native nanoflow-actions-native'" >> $GITHUB_OUTPUT - echo "widgets=${all_widgets_and_js}" >> $GITHUB_OUTPUT + echo "widgets=${all_widgets}" >> $GITHUB_OUTPUT + echo "widgets_to_test=${all_widgets}" >> $GITHUB_OUTPUT + echo "js_actions_changed=true" >> $GITHUB_OUTPUT fi fi -echo "Determined scope: $(cat $GITHUB_OUTPUT | grep scope= | cut -d= -f2)" -echo "Widgets: $(cat $GITHUB_OUTPUT | grep widgets= | cut -d= -f2)" \ No newline at end of file +echo "Determined scope: $(cat $GITHUB_OUTPUT | grep 'scope=' | cut -d= -f2)" +echo "Widgets to build: $(cat $GITHUB_OUTPUT | grep '^widgets=' | cut -d= -f2)" +echo "Widgets to test: $(cat $GITHUB_OUTPUT | grep 'widgets_to_test=' | cut -d= -f2)" +echo "JS actions changed: $(cat $GITHUB_OUTPUT | grep 'js_actions_changed=' | cut -d= -f2)" \ No newline at end of file diff --git a/.github/workflows/NativePipeline.yml b/.github/workflows/NativePipeline.yml index 1e1f45e19..bcb3c16be 100644 --- a/.github/workflows/NativePipeline.yml +++ b/.github/workflows/NativePipeline.yml @@ -88,6 +88,8 @@ jobs: outputs: scope: ${{ steps.scope.outputs.scope }} widgets: ${{ steps.scope.outputs.widgets }} + widgets_to_test: ${{ steps.scope.outputs.widgets_to_test }} + js_actions_changed: ${{ steps.scope.outputs.js_actions_changed }} steps: - name: "Check out code" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -101,7 +103,9 @@ jobs: - name: "Debug Scope Output" run: | echo "Scope is: ${{ steps.scope.outputs.scope }}" - echo "Widgets or js actions are: ${{ steps.scope.outputs.widgets }}" + echo "Widgets to build: ${{ steps.scope.outputs.widgets }}" + echo "Widgets to test: ${{ steps.scope.outputs.widgets_to_test }}" + echo "JS actions changed: ${{ steps.scope.outputs.js_actions_changed }}" mendix-version: runs-on: ubuntu-24.04 outputs: @@ -215,23 +219,20 @@ jobs: run: pnpm install --frozen-lockfile - name: "Force rebuild resources" run: | - # Build JS actions if needed - if [ "${{ github.event.inputs.workspace }}" = "js-actions" ] || \ + # Build JS actions if needed (when js_actions_changed is true OR when workspace explicitly includes them) + if [ "${{ needs.scope.outputs.js_actions_changed }}" = "true" ] || \ + [ "${{ github.event.inputs.workspace }}" = "js-actions" ] || \ [ "${{ github.event.inputs.workspace }}" = "*-native" ] || \ [ "${{ github.event_name }}" = "schedule" ]; then pnpm --filter=mobile-resources-native run build pnpm --filter=nanoflow-actions-native run build fi - # Build widgets if needed (any specific widget, *-native, or nightly) - if [ "${{ github.event.inputs.workspace }}" != "js-actions" ] || \ - [ "${{ github.event.inputs.workspace }}" = "*-native" ] || \ - [ "${{ github.event_name }}" = "schedule" ]; then - widgets=$(echo '${{ needs.scope.outputs.widgets }}' | jq -r '.[]') - for w in $widgets; do - pnpm --filter=$w run build - done - fi + # Build widgets from scope + widgets=$(echo '${{ needs.scope.outputs.widgets }}' | jq -r '.[]') + for w in $widgets; do + pnpm --filter=$w run build + done - name: "Unit test" run: pnpm -r --filter="${{ needs.scope.outputs.scope }}" run test - name: "Upload JS actions resources artifact" @@ -487,14 +488,14 @@ jobs: android-widget-tests: needs: [scope, mendix-version, project, android-app] - # Run if project succeeds and either android-app succeeds OR we're using custom artifacts (android-app was skipped) - if: ${{ (github.event.inputs.workspace != 'js-actions' || github.event_name == 'schedule') && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }} + # Run if widgets need testing (widgets_to_test is not empty) and project succeeds + if: ${{ needs.scope.outputs.widgets_to_test != '[]' && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }} runs-on: ubuntu-24.04 timeout-minutes: 60 strategy: max-parallel: 5 matrix: - widget: ${{ fromJson(needs.scope.outputs.widgets) }} + widget: ${{ fromJson(needs.scope.outputs.widgets_to_test) }} fail-fast: false steps: - name: "Check out code" @@ -563,14 +564,14 @@ jobs: ios-widget-tests: needs: [scope, mendix-version, project, ios-app] - # Run if project succeeds and either ios-app succeeds OR we're using custom artifacts (ios-app was skipped) - if: ${{ (github.event.inputs.workspace != 'js-actions' || github.event_name == 'schedule') && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }} + # Run if widgets need testing (widgets_to_test is not empty) and project succeeds + if: ${{ needs.scope.outputs.widgets_to_test != '[]' && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }} runs-on: macos-15 timeout-minutes: 60 strategy: max-parallel: 5 matrix: - widget: ${{ fromJson(needs.scope.outputs.widgets) }} + widget: ${{ fromJson(needs.scope.outputs.widgets_to_test) }} fail-fast: false steps: - name: "Force cleanup workspace" @@ -659,8 +660,8 @@ jobs: android-js-tests: needs: [scope, mendix-version, project, android-app] - # Run if project succeeds and either android-app succeeds OR we're using custom artifacts (android-app was skipped) - if: ${{ (github.event.inputs.workspace == '*-native' || github.event_name == 'schedule' || github.event.inputs.workspace == 'js-actions' || contains(needs.scope.outputs.widgets, 'mobile-resources-native') || contains(needs.scope.outputs.widgets, 'nanoflow-actions-native')) && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }} + # Run if JS actions changed and project succeeds and either android-app succeeds OR we're using custom artifacts (android-app was skipped) + if: ${{ needs.scope.outputs.js_actions_changed == 'true' && always() && needs.project.result == 'success' && (needs.android-app.result == 'success' || needs.android-app.result == 'skipped') }} runs-on: ubuntu-24.04 timeout-minutes: 90 steps: @@ -729,8 +730,8 @@ jobs: ios-js-tests: needs: [scope, mendix-version, project, ios-app] - # Run if project succeeds and either ios-app succeeds OR we're using custom artifacts (ios-app was skipped) - if: ${{ (github.event.inputs.workspace == '*-native' || github.event_name == 'schedule' || github.event.inputs.workspace == 'js-actions' || contains(needs.scope.outputs.widgets, 'mobile-resources-native') || contains(needs.scope.outputs.widgets, 'nanoflow-actions-native')) && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }} + # Run if JS actions changed and project succeeds and either ios-app succeeds OR we're using custom artifacts (ios-app was skipped) + if: ${{ needs.scope.outputs.js_actions_changed == 'true' && always() && needs.project.result == 'success' && (needs.ios-app.result == 'success' || needs.ios-app.result == 'skipped') }} runs-on: macos-15 timeout-minutes: 90 steps: @@ -827,7 +828,7 @@ jobs: - name: "Download Android screenshots" run: | - widgets=$(echo '${{ needs.scope.outputs.widgets }}' | jq -r '.[]') + widgets=$(echo '${{ needs.scope.outputs.widgets_to_test }}' | jq -r '.[]') mkdir -p images/actual/android/ for widget in $widgets; do echo "Downloading android-screenshots-${widget}" @@ -841,7 +842,7 @@ jobs: - name: "Download iOS screenshots" run: | - widgets=$(echo '${{ needs.scope.outputs.widgets }}' | jq -r '.[]') + widgets=$(echo '${{ needs.scope.outputs.widgets_to_test }}' | jq -r '.[]') mkdir -p images/actual/ios/ for widget in $widgets; do echo "Downloading ios-screenshots-${widget}" diff --git a/package.json b/package.json index 6236b9c05..e5461d2d7 100644 --- a/package.json +++ b/package.json @@ -102,5 +102,5 @@ "react-native-snap-carousel@3.9.1": "patches/react-native-snap-carousel+3.9.1.patch" } }, - "packageManager": "pnpm@10.13.1" + "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264" }