diff --git a/.github/workflows/apps_automated_android.yml b/.github/workflows/apps_automated_android.yml index abbd1fd8bf..ff4a3eb088 100644 --- a/.github/workflows/apps_automated_android.yml +++ b/.github/workflows/apps_automated_android.yml @@ -31,11 +31,11 @@ jobs: node-version: 23.5.0 - name: Derive appropriate SHAs for base and head for `nx affected` commands - uses: nrwl/nx-set-shas@826660b82addbef3abff5fa871492ebad618c9e1 # v4.3.3 + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 with: main-branch-name: 'main' - - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'temurin' java-version: '21' @@ -65,7 +65,7 @@ jobs: sudo udevadm trigger --name-match=kvm - name: Run tests on Android Emulator - uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # v2.35.0 + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0 with: api-level: 35 arch: x86_64 diff --git a/.github/workflows/apps_automated_ios.yml b/.github/workflows/apps_automated_ios.yml index 9305cd87fd..7dfb51f331 100644 --- a/.github/workflows/apps_automated_ios.yml +++ b/.github/workflows/apps_automated_ios.yml @@ -34,7 +34,7 @@ jobs: node-version: 23.5.0 - name: Derive appropriate SHAs for base and head for `nx affected` commands - uses: nrwl/nx-set-shas@826660b82addbef3abff5fa871492ebad618c9e1 # v4.3.3 + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 with: main-branch-name: 'main' @@ -52,10 +52,9 @@ jobs: run: npx nx run-many --target=test --configuration=ci --projects=core - name: Start iOS Simulator - uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd # v4 + uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 # v5 with: model: 'iPhone 16 Pro' - os_version: '18.4' - name: Run tests on iOS Simulator run: npx nx test apps-automated -c=ios diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 6446b8aac4..6b3ebd28cc 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,4 +19,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 \ No newline at end of file + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 \ No newline at end of file diff --git a/.github/workflows/npm_release_tns_core.yml b/.github/workflows/npm_release_tns_core.yml index fd9d2a9681..c4f405ee63 100644 --- a/.github/workflows/npm_release_tns_core.yml +++ b/.github/workflows/npm_release_tns_core.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 with: egress-policy: audit diff --git a/.github/workflows/npm_release_types.yml b/.github/workflows/npm_release_types.yml index 05f325d34d..4aa5c151e5 100644 --- a/.github/workflows/npm_release_types.yml +++ b/.github/workflows/npm_release_types.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 with: egress-policy: audit diff --git a/.github/workflows/npm_release_webpack.yml b/.github/workflows/npm_release_webpack.yml index 52fe661082..8d78e4e624 100644 --- a/.github/workflows/npm_release_webpack.yml +++ b/.github/workflows/npm_release_webpack.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 with: egress-policy: audit diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index f61061b811..a4a2873931 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/secure_nx_release.yml b/.github/workflows/secure_nx_release.yml index 35b7fe5e8f..d30ba120a2 100644 --- a/.github/workflows/secure_nx_release.yml +++ b/.github/workflows/secure_nx_release.yml @@ -9,8 +9,18 @@ on: - '*-*' workflow_dispatch: inputs: - dist-tag: - description: "npm dist-tag to use (e.g. latest | next | canary)" + release-type: + description: "Version bump (patch/minor/major publish to npm 'latest'; prerelease uses 'preid')" + required: false + type: choice + options: + - prerelease + - patch + - minor + - major + default: prerelease + preid: + description: "Prerelease identifier (used only when release-type=prerelease; also becomes the npm dist-tag, e.g. next | canary)" required: false type: string default: next @@ -45,10 +55,11 @@ jobs: env: # Optional: provide Nx Cloud token if used in this repo NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + NEXT_PRERELEASE_PROJECT_ALLOWLIST: core,webpack5 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 with: egress-policy: audit @@ -82,16 +93,28 @@ jobs: set -euo pipefail if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then - dist_tag="${{ inputs['dist-tag'] }}" + release_type="${{ inputs['release-type'] }}" + preid="${{ inputs['preid'] }}" scope="${{ inputs['release-group'] }}" dry_run="${{ inputs['dry-run'] }}" mode="dispatch" + + # npm dist-tag follows release type: prerelease -> preid, stable -> latest + if [[ "$release_type" == "prerelease" ]]; then + dist_tag="$preid" + else + dist_tag="latest" + fi elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + release_type="" + preid="" dist_tag="" scope="" dry_run="false" mode="tag" else + release_type="prerelease" + preid="next" dist_tag="next" scope="" dry_run="false" @@ -99,6 +122,8 @@ jobs: fi echo "mode=${mode}" >> "$GITHUB_OUTPUT" + echo "release_type=${release_type}" >> "$GITHUB_OUTPUT" + echo "preid=${preid}" >> "$GITHUB_OUTPUT" echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT" echo "scope=${scope}" >> "$GITHUB_OUTPUT" echo "dry_run=${dry_run}" >> "$GITHUB_OUTPUT" @@ -124,8 +149,8 @@ jobs: fi fi - # Only consider libs under packages/* and exclude items configured as non-releaseable. - affected_json=$(npx nx show projects --affected --base "$base" --head "$head" --type lib --projects "packages/*" --exclude "ui-mobile-base,types-minimal,winter-tc,types,types-ios,types-android" --json) + # Only consider main-branch prerelease libraries allowed for automatic next publishes. + affected_json=$(npx nx show projects --affected --base "$base" --head "$head" --type lib --projects "$NEXT_PRERELEASE_PROJECT_ALLOWLIST" --json) affected_list=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(a.join(","));});') affected_count=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(String(a.length));});') @@ -194,42 +219,59 @@ jobs: if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count == '0' }} run: echo "No affected release projects on main; skipping version + publish." - - name: nx release version (dispatch) + # Orchestrated release: bumps versions, generates changelogs, commits + tags in one shot. + # The orchestrator does not accept --git-* flags (config-driven only); push is handled in the next step. + # --skip-publish keeps publishing as a separate step below so the OIDC token-clearing logic still runs. + - name: nx release version + changelog (dispatch) if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} shell: bash run: | set -euo pipefail scope="${{ steps.ctx.outputs.scope }}" + projects_arg=() if [[ -n "$scope" ]]; then projects_arg=(--projects "$scope") - else - projects_arg=() fi - npx nx release version prerelease \ - --preid "${{ steps.ctx.outputs.dist_tag }}" \ + release_type="${{ steps.ctx.outputs.release_type }}" + preid_arg=() + if [[ "$release_type" == "prerelease" ]]; then + preid_arg=(--preid "${{ steps.ctx.outputs.preid }}") + fi + + npx nx release "$release_type" \ + "${preid_arg[@]}" \ "${projects_arg[@]}" \ - --git-commit \ - --git-push \ + --skip-publish \ --verbose - - name: nx release version (dispatch, dry-run) + - name: Push release commit and tags (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + run: git push --follow-tags + + - name: nx release version + changelog (dispatch, dry-run) if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run }} shell: bash run: | set -euo pipefail scope="${{ steps.ctx.outputs.scope }}" + projects_arg=() if [[ -n "$scope" ]]; then projects_arg=(--projects "$scope") - else - projects_arg=() fi - npx nx release version prerelease \ - --preid "${{ steps.ctx.outputs.dist_tag }}" \ + release_type="${{ steps.ctx.outputs.release_type }}" + preid_arg=() + if [[ "$release_type" == "prerelease" ]]; then + preid_arg=(--preid "${{ steps.ctx.outputs.preid }}") + fi + + npx nx release "$release_type" \ + "${preid_arg[@]}" \ "${projects_arg[@]}" \ + --skip-publish \ --verbose \ --dry-run @@ -337,7 +379,6 @@ jobs: - name: nx release publish (token, dispatch) if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN == 'true' }} - shell: bash env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} NPM_CONFIG_PROVENANCE: true @@ -353,7 +394,6 @@ jobs: - name: nx release publish (token, dispatch dry-run) if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN == 'true' }} - shell: bash env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} NPM_CONFIG_PROVENANCE: true @@ -403,9 +443,25 @@ jobs: - name: Summary if: always() run: | + mode="${{ steps.ctx.outputs.mode }}" + echo "Nx Release completed." - echo "- mode: ${{ steps.ctx.outputs.mode }}" + echo "- mode: ${mode}" + echo "- release-type: '${{ steps.ctx.outputs.release_type }}'" + echo "- preid: '${{ steps.ctx.outputs.preid }}'" echo "- dist-tag: ${{ steps.ctx.outputs.mode == 'tag' && steps.taginfo.outputs.dist_tag || steps.ctx.outputs.dist_tag }}" echo "- scope: '${{ steps.ctx.outputs.scope }}'" + echo "- next-prerelease-allowlist: ${NEXT_PRERELEASE_PROJECT_ALLOWLIST}" + if [[ "$mode" == "main" ]]; then + echo "- projects: ${{ steps.affected.outputs.projects }}" + elif [[ "$mode" == "dispatch" ]]; then + if [[ -n "${{ steps.ctx.outputs.scope }}" ]]; then + echo "- projects: ${{ steps.ctx.outputs.scope }}" + else + echo "- projects: all configured release projects" + fi + else + echo "- project: ${{ steps.taginfo.outputs.project }}" + fi echo "- dry-run: ${{ steps.ctx.outputs.dry_run }}" echo "- use-token: ${{ vars.USE_NPM_TOKEN == 'true' }}" diff --git a/apps/automated/src/http/http-tests.ts b/apps/automated/src/http/http-tests.ts index e554862118..5272f8759a 100644 --- a/apps/automated/src/http/http-tests.ts +++ b/apps/automated/src/http/http-tests.ts @@ -2,7 +2,7 @@ import { ImageSource } from '@nativescript/core'; import * as TKUnit from '../tk-unit'; import * as http from '@nativescript/core/http'; import * as fs from '@nativescript/core/file-system'; -import { addHeader } from '@nativescript/core/http/http-request'; +import { requestInternal, addHeader, BaseHttpContent } from '@nativescript/core/http/http-request-internal'; export var test_getString_isDefined = function () { TKUnit.assert(typeof http.getString !== 'undefined', 'Method http.getString() should be defined!'); @@ -329,6 +329,89 @@ export var test_request_requestShouldTimeout = function (done) { }); }; +export var test_requestInternal_responseStatusCodeShouldBeDefined = function (done) { + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }).then( + function (response) { + //// Argument (response) is HttpResponse! + var statusCode = response.statusCode; + try { + TKUnit.assert(typeof statusCode !== 'undefined', 'response.statusCode should be defined!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + +export var test_requestInternal_responseContentShouldExposeNativeContentFunctions = function (done) { + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }).then( + function (response) { + try { + TKUnit.assert(typeof response.content.toNativeImage === 'function' && typeof response.content.toNativeString === 'function', `response.content should expose native content functions!`); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + +export var test_requestInternal_responseContentShouldExposeHandlerFunctions = function (done) { + const responseHandler = { + toDummy1: () => 'dummy1', + toDummy2: () => 'dummy2', + }; + + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }, responseHandler).then( + function (response) { + try { + TKUnit.assert(typeof response.content.toDummy1 === 'function' && typeof response.content.toDummy2 === 'function', `response.content should expose content handler functions!`); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + +export var test_requestInternal_responseHandlerShouldBeAvailable = function (done) { + const suffix = '-nsformatted'; + const responseHandler = { + toFormattedString: function (this: BaseHttpContent) { + return this.toNativeString() + suffix; + }, + }; + + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }, responseHandler).then( + function (response) { + const value = response.content.toFormattedString(); + try { + TKUnit.assert(typeof value === 'string' && value.endsWith(suffix), `response.content.toFormattedString should return the response string appended with ${suffix} at the end!`); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + export var test_request_responseStatusCodeShouldBeDefined = function (done) { var result: http.HttpResponse; diff --git a/apps/automated/src/ui/image/image-tests.ts b/apps/automated/src/ui/image/image-tests.ts index 875c889e5b..20e129e235 100644 --- a/apps/automated/src/ui/image/image-tests.ts +++ b/apps/automated/src/ui/image/image-tests.ts @@ -1,7 +1,7 @@ import { Image } from '@nativescript/core/ui/image'; import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; import { GridLayout } from '@nativescript/core/ui/layouts/grid-layout'; -import { PropertyChangeData, Utils } from '@nativescript/core'; +import { PropertyChangeData } from '@nativescript/core'; import * as utils from '@nativescript/core/utils'; import * as TKUnit from '../../tk-unit'; import { getColor } from '../../ui-helper'; @@ -27,8 +27,6 @@ if (__ANDROID__) { appHelpers.initImageCache(Application.android.startActivity, appHelpers.CacheMode.memory); // use memory cache only. } -const expectLayoutRequest = __APPLE__ && Utils.SDK_VERSION >= 18; - export const test_Image_Members = function () { const image = new ImageModule.Image(); TKUnit.assert(types.isUndefined(image.src), 'Image.src is defined'); @@ -269,17 +267,17 @@ export const test_SettingImageSourceWhenSizedToParentDoesNotRequestLayout = ios( let mainPage = helper.getCurrentPage(); mainPage.content = host; - TKUnit.waitUntilReady(() => host.isLoaded); + + const nativeHostView = host.nativeViewProtected as UIView; + + // Check if native view layer is still marked as dirty before proceeding + TKUnit.waitUntilReady(() => host.isLoaded && nativeHostView?.layer && !nativeHostView.layer.needsLayout()); let called = false; image.requestLayout = () => (called = true); image.src = '~/assets/logo.png'; - if (expectLayoutRequest) { - TKUnit.assertTrue(called, 'image.requestLayout should be called.'); - } else { - TKUnit.assertFalse(called, 'image.requestLayout should not be called.'); - } + TKUnit.assertFalse(called, 'image.requestLayout should not be called.'); }); export const test_SettingImageSourceWhenFixedWidthAndHeightDoesNotRequestLayout = ios(() => { @@ -291,17 +289,17 @@ export const test_SettingImageSourceWhenFixedWidthAndHeightDoesNotRequestLayout let mainPage = helper.getCurrentPage(); mainPage.content = host; - TKUnit.waitUntilReady(() => host.isLoaded); + + const nativeHostView = host.nativeViewProtected as UIView; + + // Check if native view layer is still marked as dirty before proceeding + TKUnit.waitUntilReady(() => host.isLoaded && nativeHostView?.layer && !nativeHostView.layer.needsLayout()); let called = false; image.requestLayout = () => (called = true); image.src = '~/assets/logo.png'; - if (expectLayoutRequest) { - TKUnit.assertTrue(called, 'image.requestLayout should be called.'); - } else { - TKUnit.assertFalse(called, 'image.requestLayout should not be called.'); - } + TKUnit.assertFalse(called, 'image.requestLayout should not be called.'); }); export const test_SettingImageSourceWhenSizedToContentShouldInvalidate = ios(() => { diff --git a/apps/automated/src/ui/label/label-tests.ts b/apps/automated/src/ui/label/label-tests.ts index d5d382f490..9696aca3fe 100644 --- a/apps/automated/src/ui/label/label-tests.ts +++ b/apps/automated/src/ui/label/label-tests.ts @@ -6,8 +6,6 @@ import * as helper from '../../ui-helper'; const testDir = 'ui/label'; -const expectLayoutRequest = __APPLE__ && Utils.SDK_VERSION >= 18; - export class LabelTest extends testModule.UITest