diff --git a/.github/workflows/npm_release_cli.yml b/.github/workflows/npm_release_cli.yml index 445178b256..13526d53b3 100644 --- a/.github/workflows/npm_release_cli.yml +++ b/.github/workflows/npm_release_cli.yml @@ -9,6 +9,17 @@ on: paths-ignore: - 'packages/**' workflow_dispatch: + inputs: + release_type: + description: 'Release type. "dev" publishes a -next prerelease without bumping package.json. patch/minor/major/prerelease bump package.json, commit + tag to main, then publish as a stable release.' + type: choice + options: + - dev + - patch + - minor + - major + - prerelease + default: patch permissions: read-all @@ -19,9 +30,12 @@ jobs: build: name: Build runs-on: macos-latest + permissions: + contents: write outputs: npm_version: ${{ steps.npm_version_output.outputs.NPM_VERSION }} npm_tag: ${{ steps.npm_version_output.outputs.NPM_TAG }} + is_release: ${{ steps.npm_version_output.outputs.IS_RELEASE }} steps: - name: Harden the runner (Audit all outbound calls) @@ -32,6 +46,7 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: @@ -46,8 +61,18 @@ jobs: NPM_VERSION=$(node -e "console.log(require('./package.json').version);") echo NPM_VERSION=$NPM_VERSION >> $GITHUB_ENV + - name: Bump, commit and tag stable release (manual dispatch) + if: ${{ github.event_name == 'workflow_dispatch' && inputs.release_type != 'dev' }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + npm version ${{ inputs.release_type }} -m "chore: release v%s" + NPM_VERSION=$(node -e "console.log(require('./package.json').version);") + echo NPM_VERSION=$NPM_VERSION >> $GITHUB_ENV + git push origin HEAD:${GITHUB_REF_NAME} --follow-tags + - name: Bump version for dev release - if: ${{ !contains(github.ref, 'refs/tags/') }} + if: ${{ !contains(github.ref, 'refs/tags/') && (github.event_name != 'workflow_dispatch' || inputs.release_type == 'dev') }} run: | NPM_VERSION=$(node ./scripts/get-next-version.js) echo NPM_VERSION=$NPM_VERSION >> $GITHUB_ENV @@ -57,8 +82,14 @@ jobs: id: npm_version_output run: | NPM_TAG=$(node ./scripts/get-npm-tag.js) + if [[ "${GITHUB_REF}" == refs/tags/* ]] || [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.release_type }}" != "dev" ]]; then + IS_RELEASE=true + else + IS_RELEASE=false + fi echo NPM_VERSION=$NPM_VERSION >> $GITHUB_OUTPUT echo NPM_TAG=$NPM_TAG >> $GITHUB_OUTPUT + echo IS_RELEASE=$IS_RELEASE >> $GITHUB_OUTPUT - name: Build nativescript run: npm pack @@ -123,8 +154,8 @@ jobs: github-release: runs-on: ubuntu-latest - # only runs on tagged commits - if: ${{ contains(github.ref, 'refs/tags/') }} + # runs for tag pushes and for manual dispatches that bumped a stable release + if: ${{ needs.build.outputs.is_release == 'true' }} permissions: contents: write needs: @@ -140,6 +171,7 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 + ref: v${{needs.build.outputs.npm_version}} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: @@ -169,6 +201,7 @@ jobs: - uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0 with: + tag: v${{needs.build.outputs.npm_version}} artifacts: "dist/nativescript-*.tgz,dist/nativescript-*.intoto.jsonl" bodyFile: "body.md" prerelease: ${{needs.build.outputs.npm_tag != 'latest'}} diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index ea9cc44ea4..0683d48ddc 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -31,7 +31,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $errors: IErrors, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, private $cleanupService: ICleanupService, - private $runController: IRunController + private $runController: IRunController, ) {} private get $platformsDataService(): IPlatformsDataService { @@ -56,7 +56,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } public async getDeviceInstances( - platform?: string + platform?: string, ): Promise { await this.$devicesService.initialize({ platform, @@ -71,7 +71,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { .filter( (d) => !platform || - d.deviceInfo.platform.toLowerCase() === platform.toLowerCase() + d.deviceInfo.platform.toLowerCase() === platform.toLowerCase(), ); return devices; @@ -80,7 +80,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public async createDeviceDescriptors( devices: Mobile.IDevice[], platform: string, - additionalOptions?: ILiveSyncCommandHelperAdditionalOptions + additionalOptions?: ILiveSyncCommandHelperAdditionalOptions, ): Promise { // Now let's take data for each device: const deviceDescriptors: ILiveSyncDeviceDescriptor[] = devices.map((d) => { @@ -105,7 +105,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { forceRebuildNativeApp: additionalOptions.forceRebuildNativeApp, }, _device: d, - } + }, ); this.$androidBundleValidatorHelper.validateDeviceApiLevel(d, buildData); @@ -115,8 +115,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { additionalOptions.buildPlatform, d.deviceInfo.platform, buildData, - this.$projectData - ) + this.$projectData, + ) : this.$buildController.build.bind(this.$buildController, buildData); const info: ILiveSyncDeviceDescriptor = { @@ -142,14 +142,14 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const availablePlatforms = platform ? [platform] : _.values( - this.$mobileHelper.platformNames.map((p) => p.toLowerCase()) - ); + this.$mobileHelper.platformNames.map((p) => p.toLowerCase()), + ); return availablePlatforms; } public async executeCommandLiveSync( platform?: string, - additionalOptions?: ILiveSyncCommandHelperAdditionalOptions + additionalOptions?: ILiveSyncCommandHelperAdditionalOptions, ) { const devices = await this.getDeviceInstances(platform); await this.executeLiveSyncOperation(devices, platform, additionalOptions); @@ -158,13 +158,13 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public async executeLiveSyncOperation( devices: Mobile.IDevice[], platform: string, - additionalOptions?: ILiveSyncCommandHelperAdditionalOptions + additionalOptions?: ILiveSyncCommandHelperAdditionalOptions, ): Promise { const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore( devices, platform, - additionalOptions + additionalOptions, ); if (this.$options.release) { @@ -204,19 +204,19 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { }) => { const devices = await this.getDeviceInstances(platform); const remainingDevicesToSync = devices.map( - (d) => d.deviceInfo.identifier + (d) => d.deviceInfo.identifier, ); _.remove(remainingDevicesToSync, (d) => d === data.deviceIdentifier); if (remainingDevicesToSync.length === 0 && !data.keepProcessAlive) { process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); } - } + }, ); } public async validatePlatform( - platform: string + platform: string, ): Promise> { const result: IDictionary = {}; @@ -224,12 +224,12 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { for (const availablePlatform of availablePlatforms) { const platformData = this.$platformsDataService.getPlatformData( availablePlatform, - this.$projectData + this.$projectData, ); const platformProjectService = platformData.platformProjectService; const validateOutput = await platformProjectService.validate( this.$projectData, - this.$options + this.$options, ); result[availablePlatform.toLowerCase()] = validateOutput; } @@ -240,7 +240,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private async executeLiveSyncOperationCore( devices: Mobile.IDevice[], platform: string, - additionalOptions?: ILiveSyncCommandHelperAdditionalOptions + additionalOptions?: ILiveSyncCommandHelperAdditionalOptions, ): Promise<{ liveSyncInfo: ILiveSyncInfo; deviceDescriptors: ILiveSyncDeviceDescriptor[]; @@ -248,11 +248,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { if (!devices || !devices.length) { if (platform) { this.$errors.fail( - "Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again." + "Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again.", ); } else { this.$errors.fail( - "Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified." + "Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified.", ); } } @@ -273,7 +273,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const deviceDescriptors = await this.createDeviceDescriptors( devices, platform, - additionalOptions + additionalOptions, ); const liveSyncInfo = this.getLiveSyncData(this.$projectData.projectDir); @@ -282,7 +282,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private async runInRelease( platform: string, - deviceDescriptors: ILiveSyncDeviceDescriptor[] + deviceDescriptors: ILiveSyncDeviceDescriptor[], ): Promise { await this.$devicesService.initialize({ platform, @@ -296,7 +296,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { for (const deviceDescriptor of deviceDescriptors) { const device = this.$devicesService.getDeviceByIdentifier( - deviceDescriptor.identifier + deviceDescriptor.identifier, ); await device.applicationManager.startApplication({ appId: diff --git a/test/controllers/run-controller.ts b/test/controllers/run-controller.ts index 36251a12aa..4143c9a0b9 100644 --- a/test/controllers/run-controller.ts +++ b/test/controllers/run-controller.ts @@ -69,13 +69,12 @@ function getFullSyncResult(): ILiveSyncResultInfo { } function mockDevicesService(injector: IInjector, devices: Mobile.IDevice[]) { - const devicesService: Mobile.IDevicesService = injector.resolve( - "devicesService" - ); + const devicesService: Mobile.IDevicesService = + injector.resolve("devicesService"); devicesService.execute = async ( action: (device: Mobile.IDevice) => Promise, canExecute?: (dev: Mobile.IDevice) => boolean, - options?: { allowNoDevices?: boolean } + options?: { allowNoDevices?: boolean }, ) => { for (const d of devices) { if (canExecute(d)) { @@ -137,7 +136,7 @@ function createTestInjector() { devicesService.getDevicesForPlatform = () => [{ identifier: "myTestDeviceId1" }]; devicesService.getPlatformsFromDeviceDescriptors = ( - devices: ILiveSyncDeviceDescriptor[] + devices: ILiveSyncDeviceDescriptor[], ) => devices.map((d) => map[d.identifier].device.deviceInfo.platform); devicesService.on = () => ({}); @@ -206,20 +205,17 @@ describe("RunController", () => { describe("watch", () => { const testCases = [ { - name: - "should prepare only ios platform when only ios devices are connected", + name: "should prepare only ios platform when only ios devices are connected", connectedDevices: [iOSDeviceDescriptor], expectedPreparedPlatforms: ["ios"], }, { - name: - "should prepare only android platform when only android devices are connected", + name: "should prepare only android platform when only android devices are connected", connectedDevices: [androidDeviceDescriptor], expectedPreparedPlatforms: ["android"], }, { - name: - "should prepare both platforms when ios and android devices are connected", + name: "should prepare both platforms when ios and android devices are connected", connectedDevices: [iOSDeviceDescriptor, androidDeviceDescriptor], expectedPreparedPlatforms: ["ios", "android"], }, @@ -229,15 +225,14 @@ describe("RunController", () => { it(testCase.name, async () => { mockDevicesService( injector, - testCase.connectedDevices.map((d) => map[d.identifier].device) + testCase.connectedDevices.map((d) => map[d.identifier].device), ); const preparedPlatforms: string[] = []; - const prepareController: PrepareController = injector.resolve( - "prepareController" - ); + const prepareController: PrepareController = + injector.resolve("prepareController"); prepareController.prepare = async ( - currentPrepareData: PrepareData + currentPrepareData: PrepareData, ) => { preparedPlatforms.push(currentPrepareData.platform); return { @@ -253,7 +248,7 @@ describe("RunController", () => { assert.deepStrictEqual( preparedPlatforms, - testCase.expectedPreparedPlatforms + testCase.expectedPreparedPlatforms, ); }); }); @@ -263,41 +258,35 @@ describe("RunController", () => { describe("stopRunOnDevices", () => { const testCases = [ { - name: - "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", currentDeviceIdentifiers: ["device1", "device2", "device3"], expectedDeviceIdentifiers: ["device1", "device2", "device3"], }, { - name: - "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", currentDeviceIdentifiers: ["device1"], expectedDeviceIdentifiers: ["device1"], }, { - name: - "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", currentDeviceIdentifiers: ["device1"], expectedDeviceIdentifiers: ["device1"], deviceIdentifiersToBeStopped: ["device1"], }, { - name: - "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", currentDeviceIdentifiers: ["device1", "device2", "device3"], expectedDeviceIdentifiers: ["device1", "device3"], deviceIdentifiersToBeStopped: ["device1", "device3"], }, { - name: - "does not raise liveSyncStopped event for device, which is not currently being liveSynced", + name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", currentDeviceIdentifiers: ["device1", "device2", "device3"], expectedDeviceIdentifiers: ["device1"], deviceIdentifiersToBeStopped: ["device1", "device4"], }, { - name: - "stops LiveSync operation for all devices when stop method is called with empty array", + name: "stops LiveSync operation for all devices when stop method is called with empty array", currentDeviceIdentifiers: ["device1", "device2", "device3"], expectedDeviceIdentifiers: ["device1", "device2", "device3"], deviceIdentifiersToBeStopped: [], @@ -307,14 +296,14 @@ describe("RunController", () => { for (const testCase of testCases) { it(testCase.name, async () => { const liveSyncProcessDataService = injector.resolve( - "liveSyncProcessDataService" + "liveSyncProcessDataService", ); (liveSyncProcessDataService).persistData( projectDir, testCase.currentDeviceIdentifiers.map( - (identifier) => { identifier } + (identifier) => { identifier }, ), - ["ios"] + ["ios"], ); const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; @@ -322,7 +311,7 @@ describe("RunController", () => { runController.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { assert.equal(data.projectDir, projectDir); emittedDeviceIdentifiersForLiveSyncStoppedEvent.push( - data.deviceIdentifier + data.deviceIdentifier, ); }); @@ -333,7 +322,7 @@ describe("RunController", () => { assert.deepStrictEqual( emittedDeviceIdentifiersForLiveSyncStoppedEvent, - testCase.expectedDeviceIdentifiers + testCase.expectedDeviceIdentifiers, ); }); }