From 93ab65cab9f66a8cadfb975fc09dc49a9425728f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 21 Oct 2025 18:12:27 +0200 Subject: [PATCH 1/6] Fix codeowners for biometrics v2 (#16962) --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 255ddc08c80..ae5d62a90c2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -180,6 +180,8 @@ libs/common/src/key-management @bitwarden/team-key-management-dev libs/node @bitwarden/team-key-management-dev apps/desktop/desktop_native/core/src/biometric/ @bitwarden/team-key-management-dev +apps/desktop/desktop_native/core/src/biometric_v2/ @bitwarden/team-key-management-dev +apps/desktop/desktop_native/core/src/secure_memory/ @bitwarden/team-key-management-dev apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-key-management-dev apps/browser/src/background/nativeMessaging.background.ts @bitwarden/team-key-management-dev apps/desktop/src/services/biometric-message-handler.service.ts @bitwarden/team-key-management-dev From 1794803debcb87bfb8b447729aeda4c3c044c931 Mon Sep 17 00:00:00 2001 From: Kyle Denney <4227399+kdenney@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:18:15 -0500 Subject: [PATCH 2/6] [PM-24032] upgrade nav button updates (#16933) * [PM-24032] upgrade nav button post-upgrade action * fixing broken stories * added component tests * new behavior for the nav button when self hosted * fix stories --- .../upgrade-nav-button.component.html | 2 +- .../upgrade-nav-button.component.spec.ts | 162 ++++++++++++++++++ .../upgrade-nav-button.component.ts | 40 ++++- .../upgrade-nav-button.stories.ts | 21 +++ 4 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.spec.ts diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html index 115c0be86a2..2ffcd14fab0 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html @@ -5,7 +5,7 @@ `, @@ -53,6 +55,8 @@ class MockStateService { }) class MockProductSwitcher {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "dynamic-avatar", template: ``, @@ -68,6 +72,8 @@ class MockDynamicAvatar implements Partial { ), ); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() text?: string; diff --git a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts index 31689914f13..bf5c2754d4e 100644 --- a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts +++ b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts @@ -14,6 +14,8 @@ import { DialogService, NavigationModule } from "@bitwarden/components"; import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module"; import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "org-switcher", templateUrl: "org-switcher.component.html", @@ -43,20 +45,28 @@ export class OrgSwitcherComponent { * const smFilter = (org: Organization) => org.canAccessSecretsManager * // */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() filter: (org: Organization) => boolean = () => true; /** * Is `true` if the expanded content is visible */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() open = false; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() openChange = new EventEmitter(); /** * Visibility of the New Organization button */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() hideNewButton = false; diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts index 38e7d12f278..873b306a450 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts @@ -16,6 +16,8 @@ import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-s import { NavigationProductSwitcherComponent } from "./navigation-switcher.component"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-upgrade-nav-button", template: "
Upgrade Nav Button
", diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts index 8a02fdd7647..ab835b67545 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts @@ -3,6 +3,8 @@ import { map, Observable } from "rxjs"; import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-switcher.service"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "navigation-product-switcher", templateUrl: "./navigation-switcher.component.html", diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index fe2821e3d2c..faf1b796b00 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -37,6 +37,8 @@ class MockOrganizationService implements Partial { return MockOrganizationService._orgs.asObservable(); } + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set mockOrgs(orgs: Organization[]) { MockOrganizationService._orgs.next(orgs); @@ -54,6 +56,8 @@ class MockProviderService implements Partial { return MockProviderService._providers.asObservable(); } + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set mockProviders(providers: Provider[]) { MockProviderService._providers.next(providers); @@ -93,6 +97,8 @@ class MockConfigService implements Partial { } } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "story-layout", template: ``, @@ -100,6 +106,8 @@ class MockConfigService implements Partial { }) class StoryLayoutComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "story-content", template: ``, diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts index 5a6572e15be..0157e7b321a 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts @@ -6,12 +6,16 @@ import { MenuComponent } from "@bitwarden/components"; import { ProductSwitcherService } from "./shared/product-switcher.service"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "product-switcher-content", templateUrl: "./product-switcher-content.component.html", standalone: false, }) export class ProductSwitcherContentComponent { + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ViewChild("menu") menu: MenuComponent; diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts index 5dd29815ef2..114d95097a2 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts @@ -5,6 +5,8 @@ import { AfterViewInit, ChangeDetectorRef, Component, Input } from "@angular/cor import { IconButtonType } from "@bitwarden/components/src/icon-button/icon-button.component"; import { ProductSwitcherService } from "./shared/product-switcher.service"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "product-switcher", templateUrl: "./product-switcher.component.html", @@ -14,6 +16,8 @@ export class ProductSwitcherComponent implements AfterViewInit { /** * Passed to the product switcher's `bitIconButton` */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() buttonType: IconButtonType = "main"; diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index 66b6a6fb3cf..4c6af713464 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -37,6 +37,8 @@ class MockOrganizationService implements Partial { return MockOrganizationService._orgs.asObservable(); } + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set mockOrgs(orgs: Organization[]) { MockOrganizationService._orgs.next(orgs); @@ -54,6 +56,8 @@ class MockProviderService implements Partial { return MockProviderService._providers.asObservable(); } + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set mockProviders(providers: Provider[]) { MockProviderService._providers.next(providers); @@ -93,6 +97,8 @@ class MockConfigService implements Partial { } } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "story-layout", template: ``, @@ -100,6 +106,8 @@ class MockConfigService implements Partial { }) class StoryLayoutComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "story-content", template: ``, diff --git a/apps/web/src/app/layouts/toggle-width.component.ts b/apps/web/src/app/layouts/toggle-width.component.ts index 411fc73b175..25b8790bb4a 100644 --- a/apps/web/src/app/layouts/toggle-width.component.ts +++ b/apps/web/src/app/layouts/toggle-width.component.ts @@ -4,6 +4,8 @@ import { Component } from "@angular/core"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { NavigationModule } from "@bitwarden/components"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-toggle-width", template: `({ alias: "appA11yTitle" }); + readonly title = input.required({ alias: "appA11yTitle" }); constructor(private el: ElementRef) { const originalTitle = this.el.nativeElement.getAttribute("title"); diff --git a/libs/components/src/anon-layout/anon-layout-wrapper.component.ts b/libs/components/src/anon-layout/anon-layout-wrapper.component.ts index a17e11b424c..553da0c541b 100644 --- a/libs/components/src/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/components/src/anon-layout/anon-layout-wrapper.component.ts @@ -46,6 +46,8 @@ export interface AnonLayoutWrapperData { hideBackgroundIllustration?: boolean; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ templateUrl: "anon-layout-wrapper.component.html", imports: [AnonLayoutComponent, RouterModule], diff --git a/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts b/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts index 7fc022a5ad9..76fcc8976c7 100644 --- a/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/components/src/anon-layout/anon-layout-wrapper.stories.ts @@ -103,6 +103,8 @@ type Story = StoryObj; // Default Example +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-default-primary-outlet-example-component", template: "

Primary Outlet Example:
your primary component goes here

", @@ -110,6 +112,8 @@ type Story = StoryObj; }) export class DefaultPrimaryOutletExampleComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-default-secondary-outlet-example-component", template: "

Secondary Outlet Example:
your secondary component goes here

", @@ -117,6 +121,8 @@ export class DefaultPrimaryOutletExampleComponent {} }) export class DefaultSecondaryOutletExampleComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-default-env-selector-outlet-example-component", template: "

Env Selector Outlet Example:
your env selector component goes here

", @@ -192,6 +198,8 @@ const changedData: AnonLayoutWrapperData = { pageIcon: RegistrationCheckEmailIcon, }; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-dynamic-content-example-component", template: ` diff --git a/libs/components/src/anon-layout/anon-layout.component.ts b/libs/components/src/anon-layout/anon-layout.component.ts index c58b8d7e164..596a54f8825 100644 --- a/libs/components/src/anon-layout/anon-layout.component.ts +++ b/libs/components/src/anon-layout/anon-layout.component.ts @@ -27,6 +27,8 @@ import { TypographyModule } from "../typography"; export type AnonLayoutMaxWidth = "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "auth-anon-layout", templateUrl: "./anon-layout.component.html", diff --git a/libs/components/src/app/app.component.ts b/libs/components/src/app/app.component.ts index ed8cf595d5e..c2829956219 100644 --- a/libs/components/src/app/app.component.ts +++ b/libs/components/src/app/app.component.ts @@ -1,5 +1,7 @@ import { Component } from "@angular/core"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-root", template: "", diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index af5034c49d5..7a19908cbbf 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -39,6 +39,8 @@ const template = ` `; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-promise-example", template, @@ -84,6 +86,8 @@ class PromiseExampleComponent { }; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-observable-example", template, diff --git a/libs/components/src/async-actions/standalone.stories.ts b/libs/components/src/async-actions/standalone.stories.ts index 150bdb8813b..52c1e990c23 100644 --- a/libs/components/src/async-actions/standalone.stories.ts +++ b/libs/components/src/async-actions/standalone.stories.ts @@ -20,6 +20,8 @@ const template = /*html*/ ` `; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template, selector: "app-promise-example", @@ -37,6 +39,8 @@ class PromiseExampleComponent { }; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template, selector: "app-action-resolves-quickly", @@ -55,6 +59,8 @@ class ActionResolvesQuicklyComponent { }; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template, selector: "app-observable-example", @@ -66,6 +72,8 @@ class ObservableExampleComponent { }; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template, selector: "app-rejected-promise-example", diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 8ece033c73d..38f85bd7b1e 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -19,6 +19,8 @@ const SizeClasses: Record = { * A variance in color across the avatar component is important as it is used in Account Switching as a * visual indicator to recognize which of a personal or work account a user is logged into. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-avatar", template: ` diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index b9d9a666261..e3d1403be43 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -8,6 +8,8 @@ function transformMaxItems(value: number | undefined) { return value == undefined ? undefined : Math.max(1, value); } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-badge-list", templateUrl: "badge-list.component.html", diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts index 37be13ef15a..8a953b30226 100644 --- a/libs/components/src/badge/badge.component.ts +++ b/libs/components/src/badge/badge.component.ts @@ -55,6 +55,8 @@ const hoverStyles: Record = { * > `NOTE:` The `disabled` state only applies to buttons. * */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "span[bitBadge], a[bitBadge], button[bitBadge]", providers: [{ provide: FocusableElement, useExisting: BadgeComponent }], diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index 632bccae953..f258ed0c48c 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -23,6 +23,8 @@ const defaultIcon: Record = { * - Avoid stacking multiple banners. * - Banners can contain a button or anchor that uses the `bitLink` directive with `linkType="secondary"`. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-banner", templateUrl: "./banner.component.html", @@ -40,6 +42,8 @@ export class BannerComponent implements OnInit { readonly useAlertRole = input(true); readonly showClose = input(true); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() onClose = new EventEmitter(); ngOnInit(): void { diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts index 783cb2655f7..6c79b449a28 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -1,6 +1,8 @@ import { Component, EventEmitter, Output, TemplateRef, input, viewChild } from "@angular/core"; import { QueryParamsHandling } from "@angular/router"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-breadcrumb", templateUrl: "./breadcrumb.component.html", @@ -14,6 +16,8 @@ export class BreadcrumbComponent { readonly queryParamsHandling = input(); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() click = new EventEmitter(); diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts index 3c24f91be99..f0e4c4557c3 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -15,6 +15,8 @@ import { BreadcrumbComponent } from "./breadcrumb.component"; * Bitwarden uses this component to indicate the user's current location in a set of data organized in * containers (Collections, Folders, or Projects). */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-breadcrumbs", templateUrl: "./breadcrumbs.component.html", @@ -25,6 +27,8 @@ export class BreadcrumbsComponent { private breadcrumbs: BreadcrumbComponent[] = []; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ContentChildren(BreadcrumbComponent) protected set breadcrumbList(value: QueryList) { this.breadcrumbs = value.toArray(); diff --git a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts index 989b72adbd5..3064ffe3cb4 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts @@ -18,6 +18,8 @@ interface Breadcrumb { route: string; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: "", }) diff --git a/libs/components/src/button/button.component.spec.ts b/libs/components/src/button/button.component.spec.ts index 1651b6cf12a..745be014a0c 100644 --- a/libs/components/src/button/button.component.spec.ts +++ b/libs/components/src/button/button.component.spec.ts @@ -56,6 +56,8 @@ describe("Button", () => { }); }); +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-app", template: ` diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 47612685c16..350d493f832 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -57,6 +57,8 @@ const buttonStyles: Record = { unstyled: [], }; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "button[bitButton], a[bitButton]", templateUrl: "button.component.html", @@ -97,7 +99,7 @@ export class ButtonComponent implements ButtonLikeAbstraction { .concat(buttonSizeStyles[this.size() || "default"]); } - protected disabledAttr = computed(() => { + protected readonly disabledAttr = computed(() => { const disabled = this.disabled() != null && this.disabled() !== false; return disabled || this.loading(); }); @@ -110,7 +112,7 @@ export class ButtonComponent implements ButtonLikeAbstraction { * We can't use `disabledAttr` for this, because it returns `true` when `loading` is `true`. * We only want to show disabled styles during loading if `showLoadingStyles` is `true`. */ - protected showDisabledStyles = computed(() => { + protected readonly showDisabledStyles = computed(() => { return this.showLoadingStyle() || (this.disabledAttr() && this.loading() === false); }); @@ -134,11 +136,11 @@ export class ButtonComponent implements ButtonLikeAbstraction { * This pattern of converting a signal to an observable and back to a signal is not * recommended. TODO -- find better way to use debounce with signals (CL-596) */ - protected showLoadingStyle = toSignal( + protected readonly showLoadingStyle = toSignal( toObservable(this.loading).pipe(debounce((isLoading) => interval(isLoading ? 75 : 0))), ); - disabled = model(false); + readonly disabled = model(false); private el = inject(ElementRef); constructor() { diff --git a/libs/components/src/callout/callout.component.ts b/libs/components/src/callout/callout.component.ts index c15bc132035..bda5d4585d7 100644 --- a/libs/components/src/callout/callout.component.ts +++ b/libs/components/src/callout/callout.component.ts @@ -28,6 +28,8 @@ let nextId = 0; * sparingly, as they command a large amount of visual attention. Avoid using more than 1 callout in * the same location. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-callout", templateUrl: "callout.component.html", diff --git a/libs/components/src/checkbox/checkbox.component.ts b/libs/components/src/checkbox/checkbox.component.ts index d8df53943f3..61d5bb7251c 100644 --- a/libs/components/src/checkbox/checkbox.component.ts +++ b/libs/components/src/checkbox/checkbox.component.ts @@ -3,6 +3,8 @@ import { NgControl, Validators } from "@angular/forms"; import { BitFormControlAbstraction } from "../form-control"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "input[type=checkbox][bitCheckbox]", template: "", diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index ee3e4ab402d..6355581b251 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -28,6 +28,8 @@ const template = /*html*/ ` `; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-example", template, @@ -38,10 +40,14 @@ class ExampleComponent { checkbox: [false, Validators.requiredTrue], }); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set checked(value: boolean) { this.formObj.patchValue({ checkbox: value }); } + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set disabled(disable: boolean) { if (disable) { this.formObj.disable(); diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index 14f740f3bbe..78f12c400b5 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -35,6 +35,8 @@ export type ChipSelectOption = Option & { /** * `` is a select element that is commonly used to filter items in lists or tables. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-chip-select", templateUrl: "chip-select.component.html", @@ -49,6 +51,8 @@ export type ChipSelectOption = Option & { }) export class ChipSelectComponent implements ControlValueAccessor, AfterViewInit { readonly menu = viewChild(MenuComponent); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ViewChildren(MenuItemDirective) menuItems?: QueryList; readonly chipSelectButton = viewChild>("chipSelectButton"); @@ -63,6 +67,8 @@ export class ChipSelectComponent implements ControlValueAccessor, A // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. /** The select options to render */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ required: true }) get options(): ChipSelectOption[] { return this._options; @@ -75,6 +81,8 @@ export class ChipSelectComponent implements ControlValueAccessor, A /** Disables the entire chip */ // TODO: Skipped for signal migration because: // Your application code writes to the input. This prevents migration. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ transform: booleanAttribute }) disabled = false; /** Chip will stretch to full width of its container */ @@ -83,7 +91,7 @@ export class ChipSelectComponent implements ControlValueAccessor, A /** * We have `:focus-within` and `:focus-visible` but no `:focus-visible-within` */ - protected focusVisibleWithin = signal(false); + protected readonly focusVisibleWithin = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { this.focusVisibleWithin.set(target.matches("[data-fvw-target]:focus-visible")); diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index 3a35eaab333..bd7f3beb403 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -9,6 +9,8 @@ type CharacterType = "letter" | "emoji" | "special" | "number"; * the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as * `danger`. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-color-password", template: `@for (character of passwordCharArray(); track $index; let i = $index) { @@ -21,11 +23,11 @@ type CharacterType = "letter" | "emoji" | "special" | "number"; }`, }) export class ColorPasswordComponent { - password = input(""); - showCount = input(false); + readonly password = input(""); + readonly showCount = input(false); // Convert to an array to handle cases that strings have special characters, i.e.: emoji. - passwordCharArray = computed(() => { + readonly passwordCharArray = computed(() => { return Array.from(this.password() ?? ""); }); diff --git a/libs/components/src/container/container.component.ts b/libs/components/src/container/container.component.ts index 9f6a4cbef94..40b19b16598 100644 --- a/libs/components/src/container/container.component.ts +++ b/libs/components/src/container/container.component.ts @@ -3,6 +3,8 @@ import { Component } from "@angular/core"; /** * bit-container is a minimally styled component that limits the max width of its content to the tailwind theme variable '4xl'. '4xl' is equal to the value of 56rem */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-container", templateUrl: "container.component.html", diff --git a/libs/components/src/copy-click/copy-click.directive.spec.ts b/libs/components/src/copy-click/copy-click.directive.spec.ts index 321a18596e4..ed3640b7d6d 100644 --- a/libs/components/src/copy-click/copy-click.directive.spec.ts +++ b/libs/components/src/copy-click/copy-click.directive.spec.ts @@ -9,6 +9,8 @@ import { ToastService, CopyClickListener, COPY_CLICK_LISTENER } from "../"; import { CopyClickDirective } from "./copy-click.directive"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` @@ -25,9 +27,17 @@ import { CopyClickDirective } from "./copy-click.directive"; imports: [CopyClickDirective], }) class TestCopyClickComponent { + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ViewChild("noToast") noToastButton!: ElementRef; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ViewChild("infoToast") infoToastButton!: ElementRef; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ViewChild("successToast") successToastButton!: ElementRef; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ViewChild("toastWithLabel") toastWithLabelButton!: ElementRef; } diff --git a/libs/components/src/copy-click/copy-click.directive.ts b/libs/components/src/copy-click/copy-click.directive.ts index 3eb075a2b5c..f12880f8781 100644 --- a/libs/components/src/copy-click/copy-click.directive.ts +++ b/libs/components/src/copy-click/copy-click.directive.ts @@ -26,11 +26,11 @@ export const COPY_CLICK_LISTENER = new InjectionToken("CopyCl selector: "[appCopyClick]", }) export class CopyClickDirective { - private _showToast = computed(() => { + private readonly _showToast = computed(() => { return this.showToast() !== undefined; }); - private toastVariant = computed(() => { + private readonly toastVariant = computed(() => { const showToast = this.showToast(); // When the `showToast` is set without a value, an empty string will be passed if (showToast === "" || showToast === undefined) { @@ -68,7 +68,7 @@ export class CopyClickDirective { * * ``` */ - showToast = input(); + readonly showToast = input(); @HostListener("click") onClick() { const valueToCopy = this.valueToCopy(); diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index caa7a86a2a8..9bcb704a7fd 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -21,6 +21,8 @@ interface Animal { animal: string; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` @@ -62,6 +64,8 @@ class StoryDialogComponent { } } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` @@ -91,6 +95,8 @@ class StoryDialogContentComponent { } } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` >("scrollBottom"); + private readonly scrollableBody = viewChild.required(CdkScrollable); + private readonly scrollBottom = viewChild.required>("scrollBottom"); protected dialogRef = inject(DialogRef, { optional: true }); protected bodyHasScrolledFrom = hasScrolledFrom(this.scrollableBody); diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts index 4c04f0e29c9..eed031d76fb 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts @@ -26,6 +26,8 @@ const DEFAULT_COLOR: Record = { danger: "tw-text-danger", }; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ templateUrl: "./simple-configurable-dialog.component.html", imports: [ diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts index 036ef1177e6..7d1b5974d51 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts @@ -10,6 +10,8 @@ import { CalloutModule } from "../../../callout"; import { I18nMockService } from "../../../utils/i18n-mock.service"; import { DialogModule } from "../../dialog.module"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` @for (group of dialogs; track group) { diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts index 85f1bed8cf5..cd44a79c271 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts @@ -9,6 +9,8 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai }) export class IconDirective {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-simple-dialog", templateUrl: "./simple-dialog.component.html", @@ -16,12 +18,14 @@ export class IconDirective {} imports: [DialogTitleContainerDirective, TypographyDirective], }) export class SimpleDialogComponent { + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ContentChild(IconDirective) icon!: IconDirective; /** * Optional flag to hide the dialog's center icon. Defaults to false. */ - hideIcon = input(false, { transform: booleanAttribute }); + readonly hideIcon = input(false, { transform: booleanAttribute }); get hasIcon() { return this.icon != null; diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts index ce9f7b46fef..5c94a959f25 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts @@ -15,6 +15,8 @@ interface Animal { animal: string; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` @@ -57,6 +59,8 @@ class StoryDialogComponent { } } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` @@ -87,6 +91,8 @@ class SimpleDialogContent { } } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` @@ -116,6 +122,8 @@ class NonDismissableWithPrimaryButtonContent { } } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` diff --git a/libs/components/src/disclosure/disclosure.component.ts b/libs/components/src/disclosure/disclosure.component.ts index 2d73d7d8ad6..60e886f1dcc 100644 --- a/libs/components/src/disclosure/disclosure.component.ts +++ b/libs/components/src/disclosure/disclosure.component.ts @@ -34,12 +34,16 @@ let nextId = 0; * ``` * */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-disclosure", template: ``, }) export class DisclosureComponent { /** Emits the visibility of the disclosure content */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() openChange = new EventEmitter(); private _open?: boolean; @@ -48,6 +52,8 @@ export class DisclosureComponent { */ // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ transform: booleanAttribute }) set open(isOpen: boolean) { this._open = isOpen; this.openChange.emit(isOpen); diff --git a/libs/components/src/drawer/drawer-header.component.ts b/libs/components/src/drawer/drawer-header.component.ts index 36addcd2bea..006c48e091d 100644 --- a/libs/components/src/drawer/drawer-header.component.ts +++ b/libs/components/src/drawer/drawer-header.component.ts @@ -24,7 +24,7 @@ export class DrawerHeaderComponent { /** * The title to display */ - title = input.required(); + readonly title = input.required(); /** We don't want to set the HTML title attribute with `this.title` */ @HostBinding("attr.title") diff --git a/libs/components/src/drawer/drawer-host.directive.ts b/libs/components/src/drawer/drawer-host.directive.ts index 64eea6a9c06..7804d111ed6 100644 --- a/libs/components/src/drawer/drawer-host.directive.ts +++ b/libs/components/src/drawer/drawer-host.directive.ts @@ -10,7 +10,7 @@ import { Directive, signal } from "@angular/core"; selector: "[bitDrawerHost]", }) export class DrawerHostDirective { - private _portal = signal | undefined>(undefined); + private readonly _portal = signal | undefined>(undefined); /** The portal to display */ portal = this._portal.asReadonly(); diff --git a/libs/components/src/drawer/drawer.component.ts b/libs/components/src/drawer/drawer.component.ts index 7a3c764b16f..042d1eace79 100644 --- a/libs/components/src/drawer/drawer.component.ts +++ b/libs/components/src/drawer/drawer.component.ts @@ -25,7 +25,7 @@ import { DrawerService } from "./drawer.service"; }) export class DrawerComponent { private drawerHost = inject(DrawerService); - private portal = viewChild.required(CdkPortal); + private readonly portal = viewChild.required(CdkPortal); /** * Whether or not the drawer is open. @@ -33,7 +33,7 @@ export class DrawerComponent { * Note: Does not support implicit boolean transform due to Angular limitation. Must be bound explicitly `[open]="true"` instead of just `open`. * https://github.com/angular/angular/issues/55166#issuecomment-2032150999 **/ - open = model(false); + readonly open = model(false); /** * The ARIA role of the drawer. @@ -43,7 +43,7 @@ export class DrawerComponent { * - [navigation](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/navigation_role) * - For drawers that primary contain links to other content. */ - role = input<"complementary" | "navigation">("complementary"); + readonly role = input<"complementary" | "navigation">("complementary"); constructor() { effect( diff --git a/libs/components/src/drawer/drawer.service.ts b/libs/components/src/drawer/drawer.service.ts index dd8575efee8..71b3ff967d7 100644 --- a/libs/components/src/drawer/drawer.service.ts +++ b/libs/components/src/drawer/drawer.service.ts @@ -3,7 +3,7 @@ import { Injectable, signal } from "@angular/core"; @Injectable({ providedIn: "root" }) export class DrawerService { - private _portal = signal | undefined>(undefined); + private readonly _portal = signal | undefined>(undefined); /** The portal to display */ portal = this._portal.asReadonly(); diff --git a/libs/components/src/form-control/form-control.component.ts b/libs/components/src/form-control/form-control.component.ts index be1a071bcab..642d280bdcb 100644 --- a/libs/components/src/form-control/form-control.component.ts +++ b/libs/components/src/form-control/form-control.component.ts @@ -8,6 +8,8 @@ import { TypographyDirective } from "../typography/typography.directive"; import { BitFormControlAbstraction } from "./form-control.abstraction"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-form-control", templateUrl: "form-control.component.html", diff --git a/libs/components/src/form-control/label.component.ts b/libs/components/src/form-control/label.component.ts index 224e00c31da..57f9e338bb6 100644 --- a/libs/components/src/form-control/label.component.ts +++ b/libs/components/src/form-control/label.component.ts @@ -6,6 +6,8 @@ import { FormControlComponent } from "./form-control.component"; // Increments for each instance of this component let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-label", templateUrl: "label.component.html", diff --git a/libs/components/src/form-field/error-summary.component.ts b/libs/components/src/form-field/error-summary.component.ts index 1b0b5bb3cbf..cf26fd1e21f 100644 --- a/libs/components/src/form-field/error-summary.component.ts +++ b/libs/components/src/form-field/error-summary.component.ts @@ -3,6 +3,8 @@ import { AbstractControl, UntypedFormGroup } from "@angular/forms"; import { I18nPipe } from "@bitwarden/ui-common"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-error-summary", template: ` @if (errorCount > 0) { diff --git a/libs/components/src/form-field/error.component.ts b/libs/components/src/form-field/error.component.ts index ed9d7eafe8d..9d931a64db2 100644 --- a/libs/components/src/form-field/error.component.ts +++ b/libs/components/src/form-field/error.component.ts @@ -5,6 +5,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic // Increments for each instance of this component let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-error", template: ` {{ displayError }}`, diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 26038caa466..ccfdcec4132 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -22,6 +22,8 @@ import { inputBorderClasses } from "../input/input.directive"; import { BitErrorComponent } from "./error.component"; import { BitFormFieldControl } from "./form-field-control"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-form-field", templateUrl: "./form-field.component.html", @@ -42,11 +44,13 @@ export class BitFormFieldComponent implements AfterContentChecked { /** If `true`, remove the bottom border for `readonly` inputs */ // TODO: Skipped for signal migration because: // Your application code writes to the input. This prevents migration. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ transform: booleanAttribute }) disableReadOnlyBorder = false; - protected prefixHasChildren = signal(false); - protected suffixHasChildren = signal(false); + protected readonly prefixHasChildren = signal(false); + protected readonly suffixHasChildren = signal(false); get inputBorderClasses(): string { const shouldFocusBorderAppear = this.defaultContentIsFocused(); @@ -87,7 +91,7 @@ export class BitFormFieldComponent implements AfterContentChecked { * This is necessary because the `tw-group/bit-form-field` wraps the input and any prefix/suffix * buttons */ - protected defaultContentIsFocused = signal(false); + protected readonly defaultContentIsFocused = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { this.defaultContentIsFocused.set(target.matches("[data-default-content] *:focus-visible")); diff --git a/libs/components/src/form-field/password-input-toggle.directive.ts b/libs/components/src/form-field/password-input-toggle.directive.ts index 251878f5cef..ba5539250a6 100644 --- a/libs/components/src/form-field/password-input-toggle.directive.ts +++ b/libs/components/src/form-field/password-input-toggle.directive.ts @@ -27,6 +27,8 @@ export class BitPasswordInputToggleDirective implements AfterContentInit, OnChan * Whether the input is toggled to show the password. */ readonly toggled = model(false); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() toggledChange = new EventEmitter(); @HostBinding("attr.title") title = this.i18nService.t("toggleVisibility"); diff --git a/libs/components/src/form-field/password-input-toggle.spec.ts b/libs/components/src/form-field/password-input-toggle.spec.ts index 2b82fad9876..2a90622513a 100644 --- a/libs/components/src/form-field/password-input-toggle.spec.ts +++ b/libs/components/src/form-field/password-input-toggle.spec.ts @@ -13,6 +13,8 @@ import { BitFormFieldComponent } from "./form-field.component"; import { FormFieldModule } from "./form-field.module"; import { BitPasswordInputToggleDirective } from "./password-input-toggle.directive"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-form-field", template: ` diff --git a/libs/components/src/icon-button/icon-button.component.ts b/libs/components/src/icon-button/icon-button.component.ts index d712d5cb2b8..f1edee7c089 100644 --- a/libs/components/src/icon-button/icon-button.component.ts +++ b/libs/components/src/icon-button/icon-button.component.ts @@ -81,6 +81,8 @@ const sizes: Record = { * Similar to the main button components, spacing between multiple icon buttons should be .5rem. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "button[bitIconButton]:not(button[bitButton])", templateUrl: "icon-button.component.html", @@ -143,7 +145,7 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE return [this.icon(), "!tw-m-0"]; } - protected disabledAttr = computed(() => { + protected readonly disabledAttr = computed(() => { const disabled = this.disabled() != null && this.disabled() !== false; return disabled || this.loading(); }); @@ -156,7 +158,7 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE * We can't use `disabledAttr` for this, because it returns `true` when `loading` is `true`. * We only want to show disabled styles during loading if `showLoadingStyles` is `true`. */ - protected showDisabledStyles = computed(() => { + protected readonly showDisabledStyles = computed(() => { return this.showLoadingStyle() || (this.disabledAttr() && this.loading() === false); }); @@ -174,7 +176,7 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE * This pattern of converting a signal to an observable and back to a signal is not * recommended. TODO -- find better way to use debounce with signals (CL-596) */ - protected showLoadingStyle = toSignal( + protected readonly showLoadingStyle = toSignal( toObservable(this.loading).pipe(debounce((isLoading) => interval(isLoading ? 75 : 0))), ); diff --git a/libs/components/src/icon-tile/icon-tile.component.ts b/libs/components/src/icon-tile/icon-tile.component.ts index 54e92f9f004..68eed007df0 100644 --- a/libs/components/src/icon-tile/icon-tile.component.ts +++ b/libs/components/src/icon-tile/icon-tile.component.ts @@ -56,6 +56,8 @@ const shapeStyles: Record> = { * - Create visual hierarchy in lists or cards * - Show app or service icons in a consistent format */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-icon-tile", templateUrl: "icon-tile.component.html", diff --git a/libs/components/src/icon/icon.component.ts b/libs/components/src/icon/icon.component.ts index 1ef7185c88b..f57a3627383 100644 --- a/libs/components/src/icon/icon.component.ts +++ b/libs/components/src/icon/icon.component.ts @@ -3,6 +3,8 @@ import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; import { Icon, isIcon } from "@bitwarden/assets/svg"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-icon", host: { diff --git a/libs/components/src/input/input.directive.ts b/libs/components/src/input/input.directive.ts index 465736f7baa..ce34b70e88b 100644 --- a/libs/components/src/input/input.directive.ts +++ b/libs/components/src/input/input.directive.ts @@ -74,6 +74,8 @@ export class BitInputDirective implements BitFormFieldControl { // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. @HostBinding() + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() get required() { return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false; diff --git a/libs/components/src/item/item-action.component.ts b/libs/components/src/item/item-action.component.ts index acbc805cf90..b7ce1705f7c 100644 --- a/libs/components/src/item/item-action.component.ts +++ b/libs/components/src/item/item-action.component.ts @@ -1,5 +1,7 @@ import { Component } from "@angular/core"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-item-action", imports: [], diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index 1f4ac73c77e..9c24d010946 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -29,7 +29,7 @@ import { TypographyModule } from "../typography"; export class ItemContentComponent implements AfterContentChecked { readonly endSlot = viewChild>("endSlot"); - protected endSlotHasChildren = signal(false); + protected readonly endSlotHasChildren = signal(false); /** * Determines whether text will truncate or wrap. diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index f6f2c3d7e35..a3fa8386325 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -22,7 +22,7 @@ export class ItemComponent { /** * We have `:focus-within` and `:focus-visible` but no `:focus-visible-within` */ - protected focusVisibleWithin = signal(false); + protected readonly focusVisibleWithin = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { this.focusVisibleWithin.set(target.matches("[data-fvw-target]:focus-visible")); diff --git a/libs/components/src/layout/layout.component.ts b/libs/components/src/layout/layout.component.ts index 54b0341603c..1b357424205 100644 --- a/libs/components/src/layout/layout.component.ts +++ b/libs/components/src/layout/layout.component.ts @@ -12,6 +12,8 @@ import { SharedModule } from "../shared"; import { ScrollLayoutHostDirective } from "./scroll-layout.directive"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-layout", templateUrl: "layout.component.html", @@ -34,7 +36,7 @@ export class LayoutComponent { protected sideNavService = inject(SideNavService); protected drawerPortal = inject(DrawerService).portal; - private mainContent = viewChild.required>("main"); + private readonly mainContent = viewChild.required>("main"); protected focusMainContent() { this.mainContent().nativeElement.focus(); } @@ -45,7 +47,7 @@ export class LayoutComponent { * * @see https://github.com/angular/components/issues/10247#issuecomment-384060265 **/ - private skipLink = viewChild.required>("skipLink"); + private readonly skipLink = viewChild.required>("skipLink"); handleKeydown(ev: KeyboardEvent) { if (isNothingFocused()) { ev.preventDefault(); diff --git a/libs/components/src/layout/scroll-layout.directive.ts b/libs/components/src/layout/scroll-layout.directive.ts index cb2c2a4e431..35a4c5b63d8 100644 --- a/libs/components/src/layout/scroll-layout.directive.ts +++ b/libs/components/src/layout/scroll-layout.directive.ts @@ -17,7 +17,7 @@ import { filter, fromEvent, Observable, switchMap } from "rxjs"; **/ @Injectable({ providedIn: "root" }) export class ScrollLayoutService { - scrollableRef = signal | null>(null); + readonly scrollableRef = signal | null>(null); scrollableRef$ = toObservable(this.scrollableRef); } diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index 7c93b185a79..e6de8ac8402 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -99,7 +99,7 @@ export class AnchorLinkDirective extends LinkDirective { export class ButtonLinkDirective extends LinkDirective { private el = inject(ElementRef); - disabled = input(false, { transform: booleanAttribute }); + readonly disabled = input(false, { transform: booleanAttribute }); @HostBinding("class") get classList() { return ["before:-tw-inset-y-[0.25rem]"] diff --git a/libs/components/src/menu/menu-divider.component.ts b/libs/components/src/menu/menu-divider.component.ts index 194506ee50f..3bf992d3688 100644 --- a/libs/components/src/menu/menu-divider.component.ts +++ b/libs/components/src/menu/menu-divider.component.ts @@ -1,5 +1,7 @@ import { Component } from "@angular/core"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-menu-divider", templateUrl: "./menu-divider.component.html", diff --git a/libs/components/src/menu/menu-item.directive.ts b/libs/components/src/menu/menu-item.directive.ts index 088e2a4293f..e19e208f7b0 100644 --- a/libs/components/src/menu/menu-item.directive.ts +++ b/libs/components/src/menu/menu-item.directive.ts @@ -3,6 +3,8 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { NgClass } from "@angular/common"; import { Component, ElementRef, HostBinding, Input } from "@angular/core"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "[bitMenuItem]", templateUrl: "menu-item.component.html", @@ -42,6 +44,8 @@ export class MenuItemDirective implements FocusableOption { // TODO: Skipped for signal migration because: // This input overrides a field from a superclass, while the superclass field // is not migrated. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ transform: coerceBooleanProperty }) disabled?: boolean = false; constructor(public elementRef: ElementRef) {} diff --git a/libs/components/src/menu/menu.component.spec.ts b/libs/components/src/menu/menu.component.spec.ts index 3153fd4eb37..59ad7c6dbc8 100644 --- a/libs/components/src/menu/menu.component.spec.ts +++ b/libs/components/src/menu/menu.component.spec.ts @@ -68,6 +68,8 @@ describe("Menu", () => { }); }); +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-app", template: ` diff --git a/libs/components/src/menu/menu.component.ts b/libs/components/src/menu/menu.component.ts index 3cc4de9f90f..f32a790ef69 100644 --- a/libs/components/src/menu/menu.component.ts +++ b/libs/components/src/menu/menu.component.ts @@ -12,6 +12,8 @@ import { import { MenuItemDirective } from "./menu-item.directive"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-menu", templateUrl: "./menu.component.html", @@ -20,6 +22,8 @@ import { MenuItemDirective } from "./menu-item.directive"; }) export class MenuComponent implements AfterContentInit { readonly templateRef = viewChild.required(TemplateRef); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() closed = new EventEmitter(); readonly menuItems = contentChildren(MenuItemDirective, { descendants: true }); keyManager?: FocusKeyManager; diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index 917bb7a9e3d..4e755d1deda 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -34,6 +34,8 @@ import { SelectItemView } from "./models/select-item-view"; // Increments for each instance of this component let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-multi-select", templateUrl: "./multi-select.component.html", @@ -64,6 +66,8 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro readonly loading = input(false); // TODO: Skipped for signal migration because: // Your application code writes to the input. This prevents migration. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ transform: booleanAttribute }) disabled?: boolean; // Internal tracking of selected items @@ -79,6 +83,8 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro /**Implemented as part of NG_VALUE_ACCESSOR */ private notifyOnTouched?: () => void; + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() onItemsConfirmed = new EventEmitter(); constructor( @@ -208,6 +214,8 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. @HostBinding("attr.required") + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() get required() { return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false; diff --git a/libs/components/src/navigation/nav-base.component.ts b/libs/components/src/navigation/nav-base.component.ts index 1ca40545cbb..9092ac4447c 100644 --- a/libs/components/src/navigation/nav-base.component.ts +++ b/libs/components/src/navigation/nav-base.component.ts @@ -61,5 +61,7 @@ export abstract class NavBaseComponent { /** * Fires when main content is clicked */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() mainContentClicked: EventEmitter = new EventEmitter(); } diff --git a/libs/components/src/navigation/nav-divider.component.ts b/libs/components/src/navigation/nav-divider.component.ts index 52fb433c54d..2f33883fd58 100644 --- a/libs/components/src/navigation/nav-divider.component.ts +++ b/libs/components/src/navigation/nav-divider.component.ts @@ -3,6 +3,8 @@ import { Component } from "@angular/core"; import { SideNavService } from "./side-nav.service"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-nav-divider", templateUrl: "./nav-divider.component.html", diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 1fa03ef52be..e3bb02bb75a 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -19,6 +19,8 @@ import { NavBaseComponent } from "./nav-base.component"; import { NavGroupAbstraction, NavItemComponent } from "./nav-item.component"; import { SideNavService } from "./side-nav.service"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-nav-group", templateUrl: "./nav-group.component.html", @@ -51,6 +53,8 @@ export class NavGroupComponent extends NavBaseComponent { */ readonly hideIfEmpty = input(false, { transform: booleanAttribute }); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() openChange = new EventEmitter(); diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index 8bfd8007ac0..29ad169bba9 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -12,6 +12,8 @@ import { I18nMockService } from "../utils/i18n-mock.service"; import { NavGroupComponent } from "./nav-group.component"; import { NavigationModule } from "./navigation.module"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: "", }) diff --git a/libs/components/src/navigation/nav-item.component.ts b/libs/components/src/navigation/nav-item.component.ts index 8d57a752702..5b5709ebebb 100644 --- a/libs/components/src/navigation/nav-item.component.ts +++ b/libs/components/src/navigation/nav-item.component.ts @@ -13,6 +13,8 @@ export abstract class NavGroupAbstraction { abstract setOpen(open: boolean): void; } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-nav-item", templateUrl: "./nav-item.component.html", diff --git a/libs/components/src/navigation/nav-logo.component.ts b/libs/components/src/navigation/nav-logo.component.ts index 96258740d74..0602e8b753c 100644 --- a/libs/components/src/navigation/nav-logo.component.ts +++ b/libs/components/src/navigation/nav-logo.component.ts @@ -8,6 +8,8 @@ import { BitIconComponent } from "../icon/icon.component"; import { SideNavService } from "./side-nav.service"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-nav-logo", templateUrl: "./nav-logo.component.html", diff --git a/libs/components/src/navigation/side-nav.component.ts b/libs/components/src/navigation/side-nav.component.ts index fe428c7011f..b373a89d47e 100644 --- a/libs/components/src/navigation/side-nav.component.ts +++ b/libs/components/src/navigation/side-nav.component.ts @@ -11,6 +11,8 @@ import { SideNavService } from "./side-nav.service"; export type SideNavVariant = "primary" | "secondary"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-side-nav", templateUrl: "side-nav.component.html", diff --git a/libs/components/src/no-items/no-items.component.ts b/libs/components/src/no-items/no-items.component.ts index 517abfc9533..c6e52a1f83d 100644 --- a/libs/components/src/no-items/no-items.component.ts +++ b/libs/components/src/no-items/no-items.component.ts @@ -7,6 +7,8 @@ import { BitIconComponent } from "../icon/icon.component"; /** * Component for displaying a message when there are no items to display. Expects title, description and button slots. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-no-items", templateUrl: "./no-items.component.html", diff --git a/libs/components/src/popover/popover.component.ts b/libs/components/src/popover/popover.component.ts index 78bc2abe369..024a293ada3 100644 --- a/libs/components/src/popover/popover.component.ts +++ b/libs/components/src/popover/popover.component.ts @@ -5,6 +5,8 @@ import { IconButtonModule } from "../icon-button/icon-button.module"; import { SharedModule } from "../shared/shared.module"; import { TypographyModule } from "../typography"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-popover", imports: [A11yModule, IconButtonModule, SharedModule, TypographyModule], @@ -14,5 +16,7 @@ import { TypographyModule } from "../typography"; export class PopoverComponent { readonly templateRef = viewChild.required(TemplateRef); readonly title = input(""); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() closed = new EventEmitter(); } diff --git a/libs/components/src/progress/progress.component.ts b/libs/components/src/progress/progress.component.ts index cd1d55ac9eb..c04b7c90609 100644 --- a/libs/components/src/progress/progress.component.ts +++ b/libs/components/src/progress/progress.component.ts @@ -20,6 +20,8 @@ const BackgroundClasses: Record = { /** * Progress indicators may be used to visually indicate progress or to visually measure some other value, such as a password strength indicator. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-progress", templateUrl: "./progress.component.html", diff --git a/libs/components/src/radio-button/radio-button.component.spec.ts b/libs/components/src/radio-button/radio-button.component.spec.ts index b47c1823160..3ce5ebae65e 100644 --- a/libs/components/src/radio-button/radio-button.component.spec.ts +++ b/libs/components/src/radio-button/radio-button.component.spec.ts @@ -53,6 +53,8 @@ describe("RadioButton", () => { }); }); +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-component", template: ` diff --git a/libs/components/src/radio-button/radio-button.component.ts b/libs/components/src/radio-button/radio-button.component.ts index 84ed240b3f8..a004e7e07e2 100644 --- a/libs/components/src/radio-button/radio-button.component.ts +++ b/libs/components/src/radio-button/radio-button.component.ts @@ -7,6 +7,8 @@ import { RadioInputComponent } from "./radio-input.component"; let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-radio-button", templateUrl: "radio-button.component.html", diff --git a/libs/components/src/radio-button/radio-group.component.spec.ts b/libs/components/src/radio-button/radio-group.component.spec.ts index ff01b9323f7..94cc7bc25cb 100644 --- a/libs/components/src/radio-button/radio-group.component.spec.ts +++ b/libs/components/src/radio-button/radio-group.component.spec.ts @@ -63,6 +63,8 @@ describe("RadioGroupComponent", () => { }); }); +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-app", template: ` diff --git a/libs/components/src/radio-button/radio-group.component.ts b/libs/components/src/radio-button/radio-group.component.ts index baa2cd58d80..dd44623b313 100644 --- a/libs/components/src/radio-button/radio-group.component.ts +++ b/libs/components/src/radio-button/radio-group.component.ts @@ -8,6 +8,8 @@ import { BitLabel } from "../form-control/label.component"; let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-radio-group", templateUrl: "radio-group.component.html", diff --git a/libs/components/src/radio-button/radio-input.component.ts b/libs/components/src/radio-button/radio-input.component.ts index e32dc5c572d..47bb799d88e 100644 --- a/libs/components/src/radio-button/radio-input.component.ts +++ b/libs/components/src/radio-button/radio-input.component.ts @@ -5,6 +5,8 @@ import { BitFormControlAbstraction } from "../form-control"; let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "input[type=radio][bitRadio]", template: "", diff --git a/libs/components/src/resize-observer/resize-observer.directive.ts b/libs/components/src/resize-observer/resize-observer.directive.ts index 5943636f450..68b859c3191 100644 --- a/libs/components/src/resize-observer/resize-observer.directive.ts +++ b/libs/components/src/resize-observer/resize-observer.directive.ts @@ -13,6 +13,8 @@ export class ResizeObserverDirective implements OnDestroy { } }); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() resize = new EventEmitter(); diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 65568b241fb..576031d50a7 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -18,6 +18,8 @@ let nextId = 0; /** * Do not nest Search components inside another `
`, as they already contain their own standalone `` element for searching. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-search", templateUrl: "./search.component.html", @@ -45,10 +47,12 @@ export class SearchComponent implements ControlValueAccessor, FocusableElement { // Use `type="text"` for Safari to improve rendering performance protected inputType = isBrowserSafariApi() ? ("text" as const) : ("search" as const); - protected isInputFocused = signal(false); - protected isFormHovered = signal(false); + protected readonly isInputFocused = signal(false); + protected readonly isFormHovered = signal(false); - protected showResetButton = computed(() => this.isInputFocused() || this.isFormHovered()); + protected readonly showResetButton = computed( + () => this.isInputFocused() || this.isFormHovered(), + ); readonly disabled = model(); readonly placeholder = input(); diff --git a/libs/components/src/section/section-header.component.ts b/libs/components/src/section/section-header.component.ts index c96f9486b52..27577590f41 100644 --- a/libs/components/src/section/section-header.component.ts +++ b/libs/components/src/section/section-header.component.ts @@ -2,6 +2,8 @@ import { Component } from "@angular/core"; import { TypographyModule } from "../typography"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-section-header", templateUrl: "./section-header.component.html", diff --git a/libs/components/src/section/section.component.ts b/libs/components/src/section/section.component.ts index f65c492a0c1..1ad80610637 100644 --- a/libs/components/src/section/section.component.ts +++ b/libs/components/src/section/section.component.ts @@ -2,6 +2,8 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { CommonModule } from "@angular/common"; import { Component, input } from "@angular/core"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-section", imports: [CommonModule], diff --git a/libs/components/src/select/option.component.ts b/libs/components/src/select/option.component.ts index ae75fe6514d..b981d0e0bdc 100644 --- a/libs/components/src/select/option.component.ts +++ b/libs/components/src/select/option.component.ts @@ -2,6 +2,8 @@ import { Component, booleanAttribute, input } from "@angular/core"; import { MappedOptionComponent } from "./option"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-option", template: ``, diff --git a/libs/components/src/select/select.component.spec.ts b/libs/components/src/select/select.component.spec.ts index cfc69636224..21ae50611a7 100644 --- a/libs/components/src/select/select.component.spec.ts +++ b/libs/components/src/select/select.component.spec.ts @@ -9,6 +9,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { SelectComponent } from "./select.component"; import { SelectModule } from "./select.module"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ imports: [SelectModule, ReactiveFormsModule], template: ` diff --git a/libs/components/src/select/select.component.ts b/libs/components/src/select/select.component.ts index 90b39e9c13a..f6358ccf6c6 100644 --- a/libs/components/src/select/select.component.ts +++ b/libs/components/src/select/select.component.ts @@ -34,6 +34,8 @@ import { OptionComponent } from "./option.component"; let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-select", templateUrl: "select.component.html", @@ -50,10 +52,12 @@ export class SelectComponent implements BitFormFieldControl, ControlValueAcce readonly items = model[] | undefined>(); readonly placeholder = input(this.i18nService.t("selectPlaceholder")); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() closed = new EventEmitter(); - protected selectedValue = signal(undefined); - selectedOption: Signal | null | undefined> = computed(() => + protected readonly selectedValue = signal(undefined); + readonly selectedOption: Signal | null | undefined> = computed(() => this.findSelectedOption(this.items(), this.selectedValue()), ); protected searchInputId = `bit-select-search-input-${nextId++}`; @@ -70,6 +74,8 @@ export class SelectComponent implements BitFormFieldControl, ControlValueAcce } } + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ContentChildren(OptionComponent) protected set options(value: QueryList>) { if (value == null || value.length == 0) { @@ -94,6 +100,8 @@ export class SelectComponent implements BitFormFieldControl, ControlValueAcce } // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() get disabled() { return this._disabled ?? this.ngControl?.disabled ?? false; @@ -166,6 +174,8 @@ export class SelectComponent implements BitFormFieldControl, ControlValueAcce // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. @HostBinding("attr.required") + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() get required() { return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false; diff --git a/libs/components/src/skeleton/skeleton-group.component.ts b/libs/components/src/skeleton/skeleton-group.component.ts index 8895397ae89..c1d236eafb9 100644 --- a/libs/components/src/skeleton/skeleton-group.component.ts +++ b/libs/components/src/skeleton/skeleton-group.component.ts @@ -7,6 +7,8 @@ import { Component } from "@angular/core"; * Pass skeleton loaders into the start, default, and end content slots. The content within each slot * is fully customizable. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-skeleton-group", templateUrl: "./skeleton-group.component.html", diff --git a/libs/components/src/skeleton/skeleton-text.component.ts b/libs/components/src/skeleton/skeleton-text.component.ts index 1bc88bd3204..a3d2c4a8451 100644 --- a/libs/components/src/skeleton/skeleton-text.component.ts +++ b/libs/components/src/skeleton/skeleton-text.component.ts @@ -10,6 +10,8 @@ import { SkeletonComponent } from "./skeleton.component"; * Customize the number of lines represented with the `lines` input. Customize the width * by applying a class to the `bit-skeleton-text` element (i.e. `tw-w-1/2`). */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-skeleton-text", templateUrl: "./skeleton-text.component.html", @@ -27,5 +29,5 @@ export class SkeletonTextComponent { /** * Array-transformed version of the `lines` to loop over */ - protected linesArray = computed(() => [...Array(this.lines()).keys()]); + protected readonly linesArray = computed(() => [...Array(this.lines()).keys()]); } diff --git a/libs/components/src/skeleton/skeleton.component.ts b/libs/components/src/skeleton/skeleton.component.ts index a9d83dd80a5..56de186c2e4 100644 --- a/libs/components/src/skeleton/skeleton.component.ts +++ b/libs/components/src/skeleton/skeleton.component.ts @@ -10,6 +10,8 @@ import { Component, input } from "@angular/core"; * * If you're looking to represent lines of text, use the `bit-skeleton-text` helper component. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-skeleton", templateUrl: "./skeleton.component.html", diff --git a/libs/components/src/spinner/spinner.component.ts b/libs/components/src/spinner/spinner.component.ts index c18e95d70fa..d0c2b90a165 100644 --- a/libs/components/src/spinner/spinner.component.ts +++ b/libs/components/src/spinner/spinner.component.ts @@ -3,6 +3,8 @@ import { Component, HostBinding, Input, booleanAttribute } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-spinner", templateUrl: "spinner.component.html", @@ -13,21 +15,29 @@ export class SpinnerComponent { /** * The size of the spinner. Defaults to `large`. */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() size: "fill" | "small" | "large" = "large"; /** * Disable the default color of the spinner, inherits the text color. */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ transform: booleanAttribute }) noColor = false; /** * Accessibility title. Defaults to `Loading`. */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() title = this.i18nService.t("loading"); /** * Display text for screen readers. */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input({ transform: booleanAttribute }) sr = true; @HostBinding("class") get classList() { diff --git a/libs/components/src/stepper/step.component.ts b/libs/components/src/stepper/step.component.ts index 6d558964d89..2d42dcb23cb 100644 --- a/libs/components/src/stepper/step.component.ts +++ b/libs/components/src/stepper/step.component.ts @@ -1,6 +1,8 @@ import { CdkStep, CdkStepper } from "@angular/cdk/stepper"; import { Component, input } from "@angular/core"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-step", templateUrl: "step.component.html", @@ -8,7 +10,7 @@ import { Component, input } from "@angular/core"; standalone: true, }) export class StepComponent extends CdkStep { - subLabel = input(); + readonly subLabel = input(); constructor(stepper: CdkStepper) { super(stepper); diff --git a/libs/components/src/stepper/stepper.component.ts b/libs/components/src/stepper/stepper.component.ts index befd4b40cf3..abef0dc3f9a 100644 --- a/libs/components/src/stepper/stepper.component.ts +++ b/libs/components/src/stepper/stepper.component.ts @@ -12,6 +12,8 @@ import { StepComponent } from "./step.component"; * The `` component extends the * [Angular CdkStepper](https://material.angular.io/cdk/stepper/api#CdkStepper) component */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-stepper", templateUrl: "stepper.component.html", @@ -44,6 +46,8 @@ export class StepperComponent extends CdkStepper { // overriding CdkStepper orientation input so we can default to vertical // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() override get orientation() { return this.internalOrientation || "vertical"; diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index 7f6a6c42f32..60353a28fb6 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -7,6 +7,8 @@ import { ScrollLayoutDirective } from "../../../layout"; import { SectionComponent } from "../../../section"; import { TableDataSource, TableModule } from "../../../table"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "dialog-virtual-scroll-block", standalone: true, diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts index eb9f2fccac9..8f49415e0dd 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts @@ -7,6 +7,8 @@ import { DialogService } from "../../../dialog"; import { I18nMockService } from "../../../utils/i18n-mock.service"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-kitchen-sink-form", imports: [KitchenSinkSharedModule], diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index a96964db8d5..f96217ffb63 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -8,6 +8,8 @@ import { KitchenSinkForm } from "./kitchen-sink-form.component"; import { KitchenSinkTable } from "./kitchen-sink-table.component"; import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ imports: [KitchenSinkSharedModule], template: ` @@ -85,6 +87,8 @@ class KitchenSinkDialog { constructor(public dialogRef: DialogRef) {} } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-main", imports: [KitchenSinkSharedModule, KitchenSinkTable, KitchenSinkToggleList, KitchenSinkForm], @@ -175,7 +179,7 @@ class KitchenSinkDialog { export class KitchenSinkMainComponent { constructor(public dialogService: DialogService) {} - protected drawerOpen = signal(false); + protected readonly drawerOpen = signal(false); openDialog() { this.dialogService.open(KitchenSinkDialog); diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts index 302d9f6c0a8..d8d2e20f9ae 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts @@ -2,6 +2,8 @@ import { Component } from "@angular/core"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-kitchen-sink-table", imports: [KitchenSinkSharedModule], diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts index ec8787af1bd..18846ce2831 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts @@ -2,6 +2,8 @@ import { Component } from "@angular/core"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-kitchen-sink-toggle-list", imports: [KitchenSinkSharedModule], diff --git a/libs/components/src/switch/switch.component.spec.ts b/libs/components/src/switch/switch.component.spec.ts index 4d24dbcd150..241035501fa 100644 --- a/libs/components/src/switch/switch.component.spec.ts +++ b/libs/components/src/switch/switch.component.spec.ts @@ -13,6 +13,8 @@ describe("SwitchComponent", () => { let switchComponent: SwitchComponent; let inputEl: HTMLInputElement; + // TODO: Fix this the next time the file is edited. + // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-host", imports: [FormsModule, BitLabel, ReactiveFormsModule, SwitchModule], @@ -70,6 +72,8 @@ describe("SwitchComponent", () => { }); it("should update checked when selected input changes outside of a form", async () => { + // TODO: Fix this the next time the file is edited. + // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-selected-host", template: `Element`, diff --git a/libs/components/src/switch/switch.component.ts b/libs/components/src/switch/switch.component.ts index f08ed9f46ef..52b726ac353 100644 --- a/libs/components/src/switch/switch.component.ts +++ b/libs/components/src/switch/switch.component.ts @@ -21,6 +21,8 @@ let nextId = 0; /** * Switch component for toggling between two states. Switch actions are meant to take place immediately and are not to be used in a form where saving/submiting actions are required. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-switch", providers: [ @@ -46,19 +48,19 @@ export class SwitchComponent implements ControlValueAccessor, AfterViewInit { /** * Model signal for selected state binding when used outside of a form */ - protected selected = model(false); + protected readonly selected = model(false); /** * Model signal for disabled binding when used outside of a form */ - protected disabled = model(false); - protected disabledReasonText = input(null); + protected readonly disabled = model(false); + protected readonly disabledReasonText = input(null); - private hintComponent = contentChild(BitHintComponent); + private readonly hintComponent = contentChild(BitHintComponent); private disabledReasonTextId = `bit-switch-disabled-text-${nextId++}`; - private describedByIds = computed(() => { + private readonly describedByIds = computed(() => { const ids: string[] = []; if (this.disabledReasonText() && this.disabled()) { diff --git a/libs/components/src/table/sortable.component.ts b/libs/components/src/table/sortable.component.ts index 6d5f72dd379..b46c1ee9fbd 100644 --- a/libs/components/src/table/sortable.component.ts +++ b/libs/components/src/table/sortable.component.ts @@ -5,6 +5,8 @@ import { Component, HostBinding, OnInit, input } from "@angular/core"; import type { SortDirection, SortFn } from "./table-data-source"; import { TableComponent } from "./table.component"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "th[bitSortable]", template: ` diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index 2f9436baa0b..fcdd401a1a2 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -44,6 +44,8 @@ export class BitRowDef { * * Utilizes virtual scrolling to render large datasets. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-table-scroll", templateUrl: "./table-scroll.component.html", diff --git a/libs/components/src/table/table.component.ts b/libs/components/src/table/table.component.ts index 7fdd0c40e91..fff36472a14 100644 --- a/libs/components/src/table/table.component.ts +++ b/libs/components/src/table/table.component.ts @@ -21,6 +21,8 @@ export class TableBodyDirective { constructor(public readonly template: TemplateRef) {} } +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-table", templateUrl: "./table.component.html", diff --git a/libs/components/src/tabs/shared/tab-header.component.ts b/libs/components/src/tabs/shared/tab-header.component.ts index 9a35e5a846f..aa0272aa917 100644 --- a/libs/components/src/tabs/shared/tab-header.component.ts +++ b/libs/components/src/tabs/shared/tab-header.component.ts @@ -3,6 +3,8 @@ import { Component } from "@angular/core"; /** * Component used for styling the tab header/background for both content and navigation tabs */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-header", host: { diff --git a/libs/components/src/tabs/shared/tab-list-item.directive.ts b/libs/components/src/tabs/shared/tab-list-item.directive.ts index c8b96141ecd..bc70fdf6e4b 100644 --- a/libs/components/src/tabs/shared/tab-list-item.directive.ts +++ b/libs/components/src/tabs/shared/tab-list-item.directive.ts @@ -13,6 +13,8 @@ export class TabListItemDirective implements FocusableOption { // TODO: Skipped for signal migration because: // This input overrides a field from a superclass, while the superclass field // is not migrated. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() disabled = false; @HostBinding("attr.disabled") diff --git a/libs/components/src/tabs/tab-group/tab-body.component.ts b/libs/components/src/tabs/tab-group/tab-body.component.ts index 2dec4ca180d..7382c8576c3 100644 --- a/libs/components/src/tabs/tab-group/tab-body.component.ts +++ b/libs/components/src/tabs/tab-group/tab-body.component.ts @@ -1,6 +1,8 @@ import { TemplatePortal, CdkPortalOutlet } from "@angular/cdk/portal"; import { Component, effect, HostBinding, input } from "@angular/core"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-body", templateUrl: "tab-body.component.html", diff --git a/libs/components/src/tabs/tab-group/tab-group.component.ts b/libs/components/src/tabs/tab-group/tab-group.component.ts index aad60d876ec..cd9d876945c 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.ts +++ b/libs/components/src/tabs/tab-group/tab-group.component.ts @@ -27,6 +27,8 @@ import { TabComponent } from "./tab.component"; /** Used to generate unique ID's for each tab component */ let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-group", templateUrl: "./tab-group.component.html", @@ -57,14 +59,16 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { readonly preserveContent = input(false); /** Error if no `TabComponent` is supplied. (`contentChildren`, used to query for all the tabs, doesn't support `required`) */ - private _tab = contentChild.required(TabComponent); + private readonly _tab = contentChild.required(TabComponent); - protected tabs = contentChildren(TabComponent); + protected readonly tabs = contentChildren(TabComponent); readonly tabLabels = viewChildren(TabListItemDirective); /** The index of the active tab. */ // TODO: Skipped for signal migration because: // Accessor inputs cannot be migrated as they are too complex. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() get selectedIndex(): number | null { return this._selectedIndex; @@ -75,9 +79,13 @@ export class TabGroupComponent implements AfterContentChecked, AfterViewInit { private _selectedIndex: number | null = null; /** Output to enable support for two-way binding on `[(selectedIndex)]` */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() readonly selectedIndexChange: EventEmitter = new EventEmitter(); /** Event emitted when the tab selection has changed. */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() readonly selectedTabChange: EventEmitter = new EventEmitter(); diff --git a/libs/components/src/tabs/tab-group/tab.component.ts b/libs/components/src/tabs/tab-group/tab.component.ts index 2eb63cce29d..c021f1aff20 100644 --- a/libs/components/src/tabs/tab-group/tab.component.ts +++ b/libs/components/src/tabs/tab-group/tab.component.ts @@ -11,6 +11,8 @@ import { import { TabLabelDirective } from "./tab-label.directive"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab", templateUrl: "./tab.component.html", @@ -33,6 +35,8 @@ export class TabComponent implements OnInit { readonly contentTabIndex = input(); readonly implicitContent = viewChild.required(TemplateRef); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @ContentChild(TabLabelDirective) templateLabel?: TabLabelDirective; private _contentPortal: TemplatePortal | null = null; diff --git a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts index 402c05bfef7..e94118f5f4b 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts @@ -16,6 +16,8 @@ import { TabListItemDirective } from "../shared/tab-list-item.directive"; import { TabNavBarComponent } from "./tab-nav-bar.component"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-link", templateUrl: "tab-link.component.html", @@ -38,6 +40,8 @@ export class TabLinkComponent implements FocusableOption, AfterViewInit { // TODO: Skipped for signal migration because: // This input overrides a field from a superclass, while the superclass field // is not migrated. + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() disabled = false; @HostListener("keydown", ["$event"]) onKeyDown(event: KeyboardEvent) { diff --git a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts index 26082b1f17e..c7eec302125 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts @@ -6,6 +6,8 @@ import { TabListContainerDirective } from "../shared/tab-list-container.directiv import { TabLinkComponent } from "./tab-link.component"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-nav-bar", templateUrl: "tab-nav-bar.component.html", diff --git a/libs/components/src/tabs/tabs.stories.ts b/libs/components/src/tabs/tabs.stories.ts index 1f3f5e353b9..e822e4c72eb 100644 --- a/libs/components/src/tabs/tabs.stories.ts +++ b/libs/components/src/tabs/tabs.stories.ts @@ -12,30 +12,40 @@ import { I18nMockService } from "../utils"; import { TabGroupComponent } from "./tab-group/tab-group.component"; import { TabsModule } from "./tabs.module"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-active-dummy", template: "Router - Active selected", }) class ActiveDummyComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-item-2-dummy", template: "Router - Item 2 selected", }) class ItemTwoDummyComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-item-3-dummy", template: "Router - Item 3 selected", }) class ItemThreeDummyComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-disabled-dummy", template: "Router - Disabled selected", }) class DisabledDummyComponent {} +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-item-with-child-counter-dummy", template: "Router - Item With Child Counter selected", diff --git a/libs/components/src/toast/toast-container.component.ts b/libs/components/src/toast/toast-container.component.ts index 128cf82d355..e455428db9f 100644 --- a/libs/components/src/toast/toast-container.component.ts +++ b/libs/components/src/toast/toast-container.component.ts @@ -1,6 +1,8 @@ import { Component, OnInit, viewChild } from "@angular/core"; import { ToastContainerDirective, ToastrService } from "ngx-toastr"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-toast-container", templateUrl: "toast-container.component.html", diff --git a/libs/components/src/toast/toast.component.ts b/libs/components/src/toast/toast.component.ts index 1aeca24d0e2..2bc0feebb72 100644 --- a/libs/components/src/toast/toast.component.ts +++ b/libs/components/src/toast/toast.component.ts @@ -25,6 +25,8 @@ const variants: Record = { }, }; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-toast", templateUrl: "toast.component.html", @@ -49,6 +51,8 @@ export class ToastComponent { readonly progressWidth = input(0); /** Emits when the user presses the close button */ + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() onClose = new EventEmitter(); protected get iconClass(): string { diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts index 89c8bffad5c..f7eeadce971 100644 --- a/libs/components/src/toast/toast.stories.ts +++ b/libs/components/src/toast/toast.stories.ts @@ -17,12 +17,16 @@ import { ToastOptions, ToastService } from "./toast.service"; const toastServiceExampleTemplate = ` `; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "toast-service-example", template: toastServiceExampleTemplate, imports: [ButtonModule], }) export class ToastServiceExampleComponent { + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() toastOptions?: ToastOptions; diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts index 3b7665f1d64..5f96cb9128d 100644 --- a/libs/components/src/toast/toastr.component.ts +++ b/libs/components/src/toast/toastr.component.ts @@ -7,6 +7,8 @@ import { ToastComponent } from "./toast.component"; /** * Toasts are ephemeral notifications. They most often communicate the result of a user action. Due to their ephemeral nature, long messages and critical alerts should not utilize toasts. */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ template: ` { }); }); +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-app", template: ` diff --git a/libs/components/src/toggle-group/toggle-group.component.ts b/libs/components/src/toggle-group/toggle-group.component.ts index 16c4063f9e2..bde8f0ea9b6 100644 --- a/libs/components/src/toggle-group/toggle-group.component.ts +++ b/libs/components/src/toggle-group/toggle-group.component.ts @@ -10,6 +10,8 @@ import { let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-toggle-group", templateUrl: "./toggle-group.component.html", @@ -20,6 +22,8 @@ export class ToggleGroupComponent { readonly fullWidth = input(undefined, { transform: booleanAttribute }); readonly selected = model(); + // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals + // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() selectedChange = new EventEmitter(); @HostBinding("attr.role") role = "radiogroup"; diff --git a/libs/components/src/toggle-group/toggle.component.spec.ts b/libs/components/src/toggle-group/toggle.component.spec.ts index 1eec7e54e38..d3bc2e0eeed 100644 --- a/libs/components/src/toggle-group/toggle.component.spec.ts +++ b/libs/components/src/toggle-group/toggle.component.spec.ts @@ -45,6 +45,8 @@ describe("Toggle", () => { }); }); +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-component", template: ` diff --git a/libs/components/src/toggle-group/toggle.component.ts b/libs/components/src/toggle-group/toggle.component.ts index 0301fe17979..62e886ca572 100644 --- a/libs/components/src/toggle-group/toggle.component.ts +++ b/libs/components/src/toggle-group/toggle.component.ts @@ -14,6 +14,8 @@ import { ToggleGroupComponent } from "./toggle-group.component"; let nextId = 0; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-toggle", templateUrl: "./toggle.component.html", @@ -31,8 +33,8 @@ export class ToggleComponent implements AfterContentChecked, AfterViewIn @HostBinding("tabIndex") tabIndex = "-1"; @HostBinding("class") classList = ["tw-group/toggle", "tw-flex", "tw-min-w-16"]; - protected bitBadgeContainerHasChidlren = signal(false); - protected labelTitle = signal(null); + protected readonly bitBadgeContainerHasChidlren = signal(false); + protected readonly labelTitle = signal(null); get name() { return this.groupComponent.name; diff --git a/libs/components/src/tooltip/tooltip.component.ts b/libs/components/src/tooltip/tooltip.component.ts index 6b240507311..34c67015004 100644 --- a/libs/components/src/tooltip/tooltip.component.ts +++ b/libs/components/src/tooltip/tooltip.component.ts @@ -22,6 +22,8 @@ export const TOOLTIP_DATA = new InjectionToken("TOOLTIP_DATA"); /** * tooltip component used internally by the tooltip.directive. Not meant to be used explicitly */ +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tooltip", templateUrl: "./tooltip.component.html", diff --git a/libs/components/src/tooltip/tooltip.directive.ts b/libs/components/src/tooltip/tooltip.directive.ts index 153fecfe7bf..b2c1621d710 100644 --- a/libs/components/src/tooltip/tooltip.directive.ts +++ b/libs/components/src/tooltip/tooltip.directive.ts @@ -39,7 +39,7 @@ export class TooltipDirective implements OnInit { */ readonly tooltipPosition = input("above-center"); - private isVisible = signal(false); + private readonly isVisible = signal(false); private overlayRef: OverlayRef | undefined; private elementRef = inject(ElementRef); private overlay = inject(Overlay); diff --git a/libs/components/src/tooltip/tooltip.spec.ts b/libs/components/src/tooltip/tooltip.spec.ts index 57e05e4f65f..b6a49acbc77 100644 --- a/libs/components/src/tooltip/tooltip.spec.ts +++ b/libs/components/src/tooltip/tooltip.spec.ts @@ -12,6 +12,8 @@ import { Observable, Subject } from "rxjs"; import { TooltipDirective } from "./tooltip.directive"; +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ standalone: true, imports: [TooltipDirective], From 63cdae92be538caca3b13c20cd744ba48667b6d4 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:06:55 +0200 Subject: [PATCH 5/6] [PM-2348] Close popup window before process reload (#16795) * Close popup window before process reload * unit test coverage --- .../browser/src/background/main.background.ts | 7 +++ .../browser/browser-popup-utils.spec.ts | 62 +++++++++++++++++++ .../platform/browser/browser-popup-utils.ts | 22 +++++++ apps/browser/src/popup/app.component.ts | 9 +++ 4 files changed, 100 insertions(+) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c4c412732c9..bcceac6fb84 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -300,6 +300,7 @@ import { BrowserActionsService } from "../platform/actions/browser-actions.servi import { DefaultBadgeBrowserApi } from "../platform/badge/badge-browser-api"; import { BadgeService } from "../platform/badge/badge.service"; import { BrowserApi } from "../platform/browser/browser-api"; +import BrowserPopupUtils from "../platform/browser/browser-popup-utils"; import { flagEnabled } from "../platform/flags"; import { IpcBackgroundService } from "../platform/ipc/ipc-background.service"; import { IpcContentScriptManagerService } from "../platform/ipc/ipc-content-script-manager.service"; @@ -1237,6 +1238,12 @@ export default class MainBackground { const systemUtilsServiceReloadCallback = async () => { await this.taskSchedulerService.clearAllScheduledTasks(); + + // Close browser action popup before reloading to prevent zombie popup with invalidated context. + // The 'reloadProcess' message is sent by ProcessReloadService before this callback runs, + // and popups will close themselves upon receiving it. Poll to verify popup is actually closed. + await BrowserPopupUtils.waitForAllPopupsClose(); + BrowserApi.reloadExtension(); }; diff --git a/apps/browser/src/platform/browser/browser-popup-utils.spec.ts b/apps/browser/src/platform/browser/browser-popup-utils.spec.ts index 9f9a6e313c8..e4165348c6e 100644 --- a/apps/browser/src/platform/browser/browser-popup-utils.spec.ts +++ b/apps/browser/src/platform/browser/browser-popup-utils.spec.ts @@ -337,6 +337,68 @@ describe("BrowserPopupUtils", () => { }); }); + describe("waitForAllPopupsClose", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("should resolve immediately if no popups are open", async () => { + jest.spyOn(BrowserApi, "isPopupOpen").mockResolvedValue(false); + + const promise = BrowserPopupUtils.waitForAllPopupsClose(); + jest.advanceTimersByTime(100); + + await expect(promise).resolves.toBeUndefined(); + expect(BrowserApi.isPopupOpen).toHaveBeenCalledTimes(1); + }); + + it("should resolve after timeout if popup never closes when using custom timeout", async () => { + jest.spyOn(BrowserApi, "isPopupOpen").mockResolvedValue(true); + + const promise = BrowserPopupUtils.waitForAllPopupsClose(500); + + // Advance past the timeout + jest.advanceTimersByTime(600); + + await expect(promise).resolves.toBeUndefined(); + }); + + it("should resolve after timeout if popup never closes when using default timeout", async () => { + jest.spyOn(BrowserApi, "isPopupOpen").mockResolvedValue(true); + + const promise = BrowserPopupUtils.waitForAllPopupsClose(); + + // Advance past the default timeout + jest.advanceTimersByTime(1100); + + await expect(promise).resolves.toBeUndefined(); + }); + + it("should stop polling after popup closes before timeout", async () => { + let callCount = 0; + jest.spyOn(BrowserApi, "isPopupOpen").mockImplementation(async () => { + callCount++; + return callCount <= 2; + }); + + const promise = BrowserPopupUtils.waitForAllPopupsClose(1000); + + // Advance to when popup closes (300ms) + jest.advanceTimersByTime(300); + + await expect(promise).resolves.toBeUndefined(); + + // Advance further to ensure no more calls are made + jest.advanceTimersByTime(1000); + + expect(BrowserApi.isPopupOpen).toHaveBeenCalledTimes(3); + }); + }); + describe("isSingleActionPopoutOpen", () => { const windowOptions = { id: 1, diff --git a/apps/browser/src/platform/browser/browser-popup-utils.ts b/apps/browser/src/platform/browser/browser-popup-utils.ts index aebb3e92113..cd55f6361a0 100644 --- a/apps/browser/src/platform/browser/browser-popup-utils.ts +++ b/apps/browser/src/platform/browser/browser-popup-utils.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { filter, firstValueFrom, interval, of, switchMap, takeWhile, timeout } from "rxjs"; import { ScrollOptions } from "./abstractions/browser-popup-utils.abstractions"; import { BrowserApi } from "./browser-api"; @@ -212,6 +213,27 @@ export default class BrowserPopupUtils { } } + /** + * Waits for all browser action popups to close, polling up to the specified timeout. + * Used before extension reload to prevent zombie popups with invalidated contexts. + * + * @param timeoutMs - Maximum time to wait in milliseconds. Defaults to 1 second. + * @returns Promise that resolves when all popups are closed or timeout is reached. + */ + static async waitForAllPopupsClose(timeoutMs = 1000): Promise { + await firstValueFrom( + interval(100).pipe( + switchMap(() => BrowserApi.isPopupOpen()), + takeWhile((isOpen) => isOpen, true), + filter((isOpen) => !isOpen), + timeout({ + first: timeoutMs, + with: () => of(true), + }), + ), + ); + } + /** * Identifies if a single action window is open based on the passed popoutKey. * Will focus the existing window, and close any other windows that might exist diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 998531488d3..b85da665fa0 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -36,6 +36,7 @@ import { LogoutReason, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +import { BrowserApi } from "@bitwarden/browser/platform/browser/browser-api"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -58,6 +59,7 @@ import { } from "@bitwarden/components"; import { BiometricsService, BiometricStateService, KeyService } from "@bitwarden/key-management"; +import BrowserPopupUtils from "../platform/browser/browser-popup-utils"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; @@ -286,6 +288,13 @@ export class AppComponent implements OnInit, OnDestroy { await this.biometricStateService.updateLastProcessReload(); window.location.reload(); }, 2000); + } else { + // Close browser action popup before extension reload to prevent zombie popup with invalidated context. + // This issue occurs in Chromium-based browsers (Chrome, Vivaldi, etc.) where chrome.runtime.reload() + // invalidates extension contexts before popup can close naturally + if (BrowserPopupUtils.inPopup(window)) { + BrowserApi.closePopup(window); + } } } else if (msg.command === "reloadPopup") { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. From 8beb1c6ab05f6c298d77e8345eded95be3ad27b8 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Tue, 21 Oct 2025 13:13:45 -0400 Subject: [PATCH 6/6] Clean up workflow files from Zizmor output (#16690) --- .../workflows/alert-ddg-files-modified.yml | 5 +- .github/workflows/auto-branch-updater.yml | 11 +- .github/workflows/build-browser.yml | 62 +++-- .github/workflows/build-cli.yml | 76 ++++-- .github/workflows/build-desktop.yml | 243 ++++++++++-------- .github/workflows/build-web.yml | 59 +++-- .github/workflows/chromatic.yml | 3 +- .github/workflows/crowdin-pull.yml | 1 + .github/workflows/deploy-web.yml | 130 +++++----- .github/workflows/lint-crowdin-config.yml | 1 + .github/workflows/lint.yml | 6 +- .github/workflows/locales-lint.yml | 6 +- .github/workflows/nx.yml | 3 +- .github/workflows/publish-cli.yml | 28 +- .github/workflows/publish-desktop.yml | 72 ++++-- .github/workflows/publish-web.yml | 32 ++- .github/workflows/release-browser.yml | 22 +- .github/workflows/release-cli.yml | 2 + .github/workflows/release-desktop.yml | 14 +- .github/workflows/release-web.yml | 8 +- .github/workflows/repository-management.yml | 100 +++---- .../retrieve-current-desktop-rollout.yml | 4 +- .github/workflows/staged-rollout-desktop.yml | 12 +- .../workflows/test-browser-interactions.yml | 1 + .github/workflows/test.yml | 10 +- .github/workflows/version-auto-bump.yml | 7 +- 26 files changed, 536 insertions(+), 382 deletions(-) diff --git a/.github/workflows/alert-ddg-files-modified.yml b/.github/workflows/alert-ddg-files-modified.yml index d799cc2e248..84cd67ecd5b 100644 --- a/.github/workflows/alert-ddg-files-modified.yml +++ b/.github/workflows/alert-ddg-files-modified.yml @@ -17,6 +17,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false - name: Get changed files id: changed-files @@ -68,9 +69,11 @@ jobs: - name: Comment on PR if monitored files changed if: steps.changed-files.outputs.monitored == 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + _MONITORED_FILES: ${{ steps.changed-files.outputs.monitored_files }} with: script: | - const changedFiles = `${{ steps.changed-files.outputs.monitored_files }}`.split(' ').filter(file => file.trim() !== ''); + const changedFiles = `$_MONITORED_FILES`.split(' ').filter(file => file.trim() !== ''); const message = ` ⚠️🦆 **DuckDuckGo Integration files have been modified in this PR:** diff --git a/.github/workflows/auto-branch-updater.yml b/.github/workflows/auto-branch-updater.yml index 3f67388fd0c..ceebfb7e466 100644 --- a/.github/workflows/auto-branch-updater.yml +++ b/.github/workflows/auto-branch-updater.yml @@ -27,17 +27,20 @@ jobs: steps: - name: Setup id: setup - run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT + run: echo "branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: 'eu-web-${{ steps.setup.outputs.branch }}' fetch-depth: 0 + persist-credentials: true - name: Merge ${{ steps.setup.outputs.branch }} + env: + _BRANCH: ${{ steps.setup.outputs.branch }} run: | - git config --local user.email "${{ env._BOT_EMAIL }}" - git config --local user.name "${{ env._BOT_NAME }}" - git merge origin/${{ steps.setup.outputs.branch }} + git config --local user.email "$_BOT_EMAIL" + git config --local user.name "$_BOT_NAME" + git merge "origin/$_BRANCH" git push diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index bece680b9d0..e3a49e414f9 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -58,15 +58,16 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Get Package Version id: gen_vars run: | - repo_url=https://github.com/$GITHUB_REPOSITORY.git + repo_url="https://github.com/$GITHUB_REPOSITORY.git" adj_build_num=${GITHUB_SHA:0:7} - echo "repo_url=$repo_url" >> $GITHUB_OUTPUT - echo "adj_build_number=$adj_build_num" >> $GITHUB_OUTPUT + echo "repo_url=$repo_url" >> "$GITHUB_OUTPUT" + echo "adj_build_number=$adj_build_num" >> "$GITHUB_OUTPUT" - name: Get Node Version id: retrieve-node-version @@ -74,13 +75,13 @@ jobs: run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Check secrets id: check-secrets run: | has_secrets=${{ secrets.AZURE_CLIENT_ID != '' }} - echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + echo "has_secrets=$has_secrets" >> "$GITHUB_OUTPUT" locales-test: @@ -96,6 +97,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Testing locales - extName length run: | @@ -105,12 +107,14 @@ jobs: echo "============" echo "extName string must be 40 characters or less" echo - for locale in $(ls src/_locales/); do - string_length=$(jq '.extName.message | length' src/_locales/$locale/messages.json) - if [[ $string_length -gt 40 ]]; then - echo "$locale: $string_length" - found_error=true - fi + + for locale_path in src/_locales/*/messages.json; do + locale=$(basename "$(dirname "$locale_path")") + string_length=$(jq '.extName.message | length' "$locale_path") + if [ "$string_length" -gt 40 ]; then + echo "$locale: $string_length" + found_error=true + fi done if $found_error; then @@ -145,6 +149,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -246,6 +251,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -301,13 +307,13 @@ jobs: TARGET_DIR='./browser-source/apps/browser' while IFS=' ' read -r RESULT; do FILES+=("$RESULT") - done < <(find $TARGET_DIR -size +5M) + done < <(find "$TARGET_DIR" -size +5M) # Validate results and provide messaging if [[ ${#FILES[@]} -ne 0 ]]; then echo "File(s) exceeds size limit: 5MB" - for FILE in ${FILES[@]}; do - echo "- $(du --si $FILE)" + for FILE in "${FILES[@]}"; do + echo "- $(du --si "$FILE")" done echo "ERROR Firefox rejects extension uploads that contain files larger than 5MB" # Invoke failure @@ -357,6 +363,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -389,34 +396,34 @@ jobs: ACCOUNT_NAME: bitwardenci CONTAINER_NAME: profiles run: | - mkdir -p $HOME/secrets + mkdir -p "$HOME/secrets" - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ --name bitwarden_desktop_appstore.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ + --file "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" \ --output none - name: Get certificates run: | - mkdir -p $HOME/certificates + mkdir -p "$HOME/certificates" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | - jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 + jq -r .value | base64 -d > "$HOME/certificates/bitwarden-desktop-key.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-app-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/macdev-cert | - jq -r .value | base64 -d > $HOME/certificates/macdev-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/macdev-cert.p12" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main @@ -425,9 +432,9 @@ jobs: env: KEYCHAIN_PASSWORD: ${{ steps.get-kv-secrets.outputs.KEYCHAIN-PASSWORD }} run: | - security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain - security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -lut 1200 build.keychain security import "$HOME/certificates/bitwarden-desktop-key.p12" -k build.keychain -P "" \ @@ -448,7 +455,7 @@ jobs: security import "$HOME/certificates/macdev-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain - name: NPM setup run: npm ci @@ -507,6 +514,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index d1df280f764..839181c6107 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -62,26 +62,27 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Get Package Version id: retrieve-package-version run: | PKG_VERSION=$(jq -r .version package.json) - echo "package_version=$PKG_VERSION" >> $GITHUB_OUTPUT + echo "package_version=$PKG_VERSION" >> "$GITHUB_OUTPUT" - name: Get Node Version id: retrieve-node-version working-directory: ./ run: | NODE_NVMRC=$(cat .nvmrc) - NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + NODE_VERSION="${NODE_NVMRC/v/''}" + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Check secrets id: check-secrets run: | has_secrets=${{ secrets.AZURE_CLIENT_ID != '' }} - echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + echo "has_secrets=$has_secrets" >> "$GITHUB_OUTPUT" cli: @@ -116,12 +117,17 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Setup Unix Vars run: | - echo "LOWER_RUNNER_OS=$(echo $RUNNER_OS | awk '{print tolower($0)}')" >> $GITHUB_ENV - echo "SHORT_RUNNER_OS=$(echo $RUNNER_OS | awk '{print substr($0, 1, 3)}' | \ - awk '{print tolower($0)}')" >> $GITHUB_ENV + LOWER_RUNNER_OS="$(printf '%s' "$RUNNER_OS" | awk '{print tolower($0)}')" + SHORT_RUNNER_OS="$(printf '%s' "$RUNNER_OS" | awk '{print substr($0, 1, 3)}' | awk '{print tolower($0)}')" + + { + echo "LOWER_RUNNER_OS=$LOWER_RUNNER_OS" + echo "SHORT_RUNNER_OS=$SHORT_RUNNER_OS" + } >> "$GITHUB_ENV" - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -155,7 +161,9 @@ jobs: npm link ../sdk-internal - name: Build & Package Unix - run: npm run dist:${{ matrix.license_type.build_prefix }}:${{ env.SHORT_RUNNER_OS }}${{ matrix.os.target_suffix }} --quiet + env: + _SHORT_RUNNER_OS: ${{ env.SHORT_RUNNER_OS }} + run: npm run "dist:${{ matrix.license_type.build_prefix }}:$_SHORT_RUNNER_OS${{ matrix.os.target_suffix }}" --quiet - name: Login to Azure if: ${{ matrix.os.base == 'mac' && needs.setup.outputs.has_secrets == 'true' }} @@ -168,10 +176,10 @@ jobs: - name: Get certificates if: ${{ matrix.os.base == 'mac' && needs.setup.outputs.has_secrets == 'true' }} run: | - mkdir -p $HOME/certificates + mkdir -p "$HOME/certificates" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-app-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-app-cert.p12" - name: Get Azure Key Vault secrets id: get-kv-secrets @@ -189,33 +197,39 @@ jobs: env: KEYCHAIN_PASSWORD: ${{ steps.get-kv-secrets.outputs.KEYCHAIN-PASSWORD }} run: | - security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain - security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -lut 1200 build.keychain security import "$HOME/certificates/devid-app-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain - name: Sign binary if: ${{ matrix.os.base == 'mac' && needs.setup.outputs.has_secrets == 'true' }} env: MACOS_CERTIFICATE_NAME: "Developer ID Application: 8bit Solutions LLC" - run: codesign --sign "$MACOS_CERTIFICATE_NAME" --verbose=3 --force --options=runtime --entitlements ./entitlements.plist --timestamp ./dist/${{ matrix.license_type.build_prefix }}/${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}/bw + _LOWER_RUNNER_OS: ${{ env.LOWER_RUNNER_OS }} + run: codesign --sign "$MACOS_CERTIFICATE_NAME" --verbose=3 --force --options=runtime --entitlements ./entitlements.plist --timestamp "./dist/${{ matrix.license_type.build_prefix }}/$_LOWER_RUNNER_OS${{ matrix.os.target_suffix }}/bw" - name: Zip Unix + env: + _LOWER_RUNNER_OS: ${{ env.LOWER_RUNNER_OS }} + _PACKAGE_VERSION: ${{ env._PACKAGE_VERSION }} run: | - cd ./dist/${{ matrix.license_type.build_prefix }}/${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }} - zip ../../bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip ./bw + cd "./dist/${{ matrix.license_type.build_prefix }}/$_LOWER_RUNNER_OS${{ matrix.os.target_suffix }}" + zip "../../bw${{ matrix.license_type.artifact_prefix }}-$_LOWER_RUNNER_OS${{ matrix.os.target_suffix }}-$_PACKAGE_VERSION.zip" ./bw - name: Set up private auth key if: ${{ matrix.os.base == 'mac' && needs.setup.outputs.has_secrets == 'true' }} + env: + _APP_STORE_CONNECT_AUTH_KEY: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} run: | mkdir ~/private_keys cat << EOF > ~/private_keys/AuthKey_6TV9MKN3GP.p8 - ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} + $_APP_STORE_CONNECT_AUTH_KEY EOF - name: Notarize app @@ -224,22 +238,26 @@ jobs: APP_STORE_CONNECT_TEAM_ISSUER: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-TEAM-ISSUER }} APP_STORE_CONNECT_AUTH_KEY: 6TV9MKN3GP APP_STORE_CONNECT_AUTH_KEY_PATH: ~/private_keys/AuthKey_6TV9MKN3GP.p8 + _LOWER_RUNNER_OS: ${{ env.LOWER_RUNNER_OS }} run: | echo "Create keychain profile" xcrun notarytool store-credentials "notarytool-profile" --key-id "$APP_STORE_CONNECT_AUTH_KEY" --key "$APP_STORE_CONNECT_AUTH_KEY_PATH" --issuer "$APP_STORE_CONNECT_TEAM_ISSUER" - codesign --sign "Developer ID Application: 8bit Solutions LLC" --verbose=3 --force --options=runtime --timestamp ./dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip + codesign --sign "Developer ID Application: 8bit Solutions LLC" --verbose=3 --force --options=runtime --timestamp "./dist/bw${{ matrix.license_type.artifact_prefix }}-$_LOWER_RUNNER_OS${{ matrix.os.target_suffix }}-$_PACKAGE_VERSION.zip" echo "Notarize app" - xcrun notarytool submit ./dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip --keychain-profile "notarytool-profile" --wait + xcrun notarytool submit "./dist/bw${{ matrix.license_type.artifact_prefix }}-$_LOWER_RUNNER_OS${{ matrix.os.target_suffix }}-$_PACKAGE_VERSION.zip" --keychain-profile "notarytool-profile" --wait - name: Version Test + env: + _PACKAGE_VERSION: ${{ env._PACKAGE_VERSION }} + _LOWER_RUNNER_OS: ${{ env.LOWER_RUNNER_OS }} run: | - unzip "./dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip" -d "./test" + unzip "./dist/bw${{ matrix.license_type.artifact_prefix }}-$_LOWER_RUNNER_OS${{ matrix.os.target_suffix }}-$_PACKAGE_VERSION.zip" -d "./test" testVersion=$(./test/bw -v) echo "version: $_PACKAGE_VERSION" echo "testVersion: $testVersion" - if [[ $testVersion != $_PACKAGE_VERSION ]]; then + if [[ $testVersion != "$_PACKAGE_VERSION" ]]; then echo "Version test failed." exit 1 fi @@ -291,6 +309,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Install AST run: dotnet tool install --global AzureSignTool --version 4.0.1 @@ -429,11 +448,13 @@ jobs: - name: Package Chocolatey shell: pwsh if: ${{ matrix.license_type.build_prefix == 'bit' }} + env: + _PACKAGE_VERSION: ${{ env._PACKAGE_VERSION }} run: | Copy-Item -Path stores/chocolatey -Destination dist/chocolatey -Recurse Copy-Item dist/${{ matrix.license_type.build_prefix }}/windows/bw.exe -Destination dist/chocolatey/tools Copy-Item ${{ github.workspace }}/LICENSE.txt -Destination dist/chocolatey/tools - choco pack dist/chocolatey/bitwarden-cli.nuspec --version ${{ env._PACKAGE_VERSION }} --out dist/chocolatey + choco pack dist/chocolatey/bitwarden-cli.nuspec --version "$env:_PACKAGE_VERSION" --out dist/chocolatey - name: Zip Windows shell: cmd @@ -466,7 +487,9 @@ jobs: if-no-files-found: error - name: Zip NPM Build Artifact - run: Get-ChildItem -Path .\build | Compress-Archive -DestinationPath .\bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip + env: + _PACKAGE_VERSION: ${{ env._PACKAGE_VERSION }} + run: Get-ChildItem -Path .\build | Compress-Archive -DestinationPath ".\bitwarden-cli-${env:_PACKAGE_VERSION}-npm-build.zip" - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' @@ -490,8 +513,11 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Print environment + env: + _PACKAGE_VERSION: ${{ env._PACKAGE_VERSION }} run: | whoami echo "GitHub ref: $GITHUB_REF" @@ -505,9 +531,11 @@ jobs: path: apps/cli/dist/snap - name: Setup Snap Package + env: + _PACKAGE_VERSION: ${{ env._PACKAGE_VERSION }} run: | cp -r stores/snap/* -t dist/snap - sed -i s/__version__/${{ env._PACKAGE_VERSION }}/g dist/snap/snapcraft.yaml + sed -i "s/__version__/$_PACKAGE_VERSION/g" "dist/snap/snapcraft.yaml" cd dist/snap ls -alth diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 8ba5f981453..51a0938552c 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -58,6 +58,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Verify run: | @@ -90,35 +91,38 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: true - name: Get Package Version id: retrieve-version run: | PKG_VERSION=$(jq -r .version src/package.json) echo "Setting version number to $PKG_VERSION" - echo "package_version=$PKG_VERSION" >> $GITHUB_OUTPUT + echo "package_version=$PKG_VERSION" >> "$GITHUB_OUTPUT" - name: Increment Version id: increment-version run: | - BUILD_NUMBER=$(expr 3000 + $GITHUB_RUN_NUMBER) + BUILD_NUMBER=$((3000 + GITHUB_RUN_NUMBER)) echo "Setting build number to $BUILD_NUMBER" - echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT + echo "build_number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT" - name: Get Version Channel id: release-channel + env: + _PACKAGE_VERSION: ${{ steps.retrieve-version.outputs.package_version }} run: | - case "${{ steps.retrieve-version.outputs.package_version }}" in + case "$_PACKAGE_VERSION" in *"alpha"*) - echo "channel=alpha" >> $GITHUB_OUTPUT + echo "channel=alpha" >> "$GITHUB_OUTPUT" echo "[!] We do not yet support 'alpha'" exit 1 ;; *"beta"*) - echo "channel=beta" >> $GITHUB_OUTPUT + echo "channel=beta" >> "$GITHUB_OUTPUT" ;; *) - echo "channel=latest" >> $GITHUB_OUTPUT + echo "channel=latest" >> "$GITHUB_OUTPUT" ;; esac @@ -126,15 +130,15 @@ jobs: id: branch-check run: | if [[ $(git ls-remote --heads origin rc) ]]; then - echo "rc_branch_exists=1" >> $GITHUB_OUTPUT + echo "rc_branch_exists=1" >> "$GITHUB_OUTPUT" else - echo "rc_branch_exists=0" >> $GITHUB_OUTPUT + echo "rc_branch_exists=0" >> "$GITHUB_OUTPUT" fi if [[ $(git ls-remote --heads origin hotfix-rc-desktop) ]]; then - echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT + echo "hotfix_branch_exists=1" >> "$GITHUB_OUTPUT" else - echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT + echo "hotfix_branch_exists=0" >> "$GITHUB_OUTPUT" fi - name: Get Node Version @@ -143,13 +147,13 @@ jobs: run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Check secrets id: check-secrets run: | has_secrets=${{ secrets.AZURE_CLIENT_ID != '' }} - echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + echo "has_secrets=$has_secrets" >> "$GITHUB_OUTPUT" linux: name: Linux Build @@ -172,6 +176,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -321,6 +326,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -427,6 +433,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -533,21 +540,21 @@ jobs: - name: Rename appx files for store if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | - Copy-Item "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx" ` - -Destination "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx" - Copy-Item "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx" ` - -Destination "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx" - Copy-Item "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx" ` - -Destination "./dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx" + Copy-Item "./dist/Bitwarden-$env:_PACKAGE_VERSION-ia32.appx" ` + -Destination "./dist/Bitwarden-$env:_PACKAGE_VERSION-ia32-store.appx" + Copy-Item "./dist/Bitwarden-$env:_PACKAGE_VERSION-x64.appx" ` + -Destination "./dist/Bitwarden-$env:_PACKAGE_VERSION-x64-store.appx" + Copy-Item "./dist/Bitwarden-$env:_PACKAGE_VERSION-arm64.appx" ` + -Destination "./dist/Bitwarden-$env:_PACKAGE_VERSION-arm64-store.appx" - name: Package for Chocolatey if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | Copy-Item -Path ./stores/chocolatey -Destination ./dist/chocolatey -Recurse - Copy-Item -Path ./dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe ` + Copy-Item -Path ./dist/nsis-web/Bitwarden-Installer-$env:_PACKAGE_VERSION.exe ` -Destination ./dist/chocolatey - $checksum = checksum -t sha256 ./dist/chocolatey/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe + $checksum = checksum -t sha256 ./dist/chocolatey/Bitwarden-Installer-$env:_PACKAGE_VERSION.exe $chocoInstall = "./dist/chocolatey/tools/chocolateyinstall.ps1" (Get-Content $chocoInstall).replace('__version__', "$env:_PACKAGE_VERSION").replace('__checksum__', $checksum) | Set-Content $chocoInstall choco pack ./dist/chocolatey/bitwarden.nuspec --version "$env:_PACKAGE_VERSION" --out ./dist/chocolatey @@ -555,12 +562,12 @@ jobs: - name: Fix NSIS artifact names for auto-updater if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | - Rename-Item -Path .\dist\nsis-web\Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z ` - -NewName bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z - Rename-Item -Path .\dist\nsis-web\Bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z ` - -NewName bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z - Rename-Item -Path .\dist\nsis-web\Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z ` - -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z + Rename-Item -Path .\dist\nsis-web\Bitwarden-$env:_PACKAGE_VERSION-ia32.nsis.7z ` + -NewName bitwarden-$env:_PACKAGE_VERSION-ia32.nsis.7z + Rename-Item -Path .\dist\nsis-web\Bitwarden-$env:_PACKAGE_VERSION-x64.nsis.7z ` + -NewName bitwarden-$env:_PACKAGE_VERSION-x64.nsis.7z + Rename-Item -Path .\dist\nsis-web\Bitwarden-$env:_PACKAGE_VERSION-arm64.nsis.7z ` + -NewName bitwarden-$env:_PACKAGE_VERSION-arm64.nsis.7z - name: Upload portable exe artifact uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 @@ -573,7 +580,7 @@ jobs: if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: - name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe + name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}..exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error @@ -919,6 +926,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -974,40 +982,40 @@ jobs: ACCOUNT_NAME: bitwardenci CONTAINER_NAME: profiles run: | - mkdir -p $HOME/secrets + mkdir -p "$HOME/secrets" - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ --name bitwarden_desktop_appstore.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ + --file "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" \ --output none - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ --name bitwarden_desktop_autofill_app_store_2024.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --file "$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile" \ --output none - name: Get certificates if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | - mkdir -p $HOME/certificates + mkdir -p "$HOME/certificates" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | - jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 + jq -r .value | base64 -d > "$HOME/certificates/bitwarden-desktop-key.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-app-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/macdev-cert | - jq -r .value | base64 -d > $HOME/certificates/macdev-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/macdev-cert.p12" - name: Log out from Azure if: ${{ needs.setup.outputs.has_secrets == 'true' }} @@ -1018,9 +1026,9 @@ jobs: env: KEYCHAIN_PASSWORD: ${{ steps.get-kv-secrets.outputs.KEYCHAIN-PASSWORD }} run: | - security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain - security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -lut 1200 build.keychain security import "$HOME/certificates/bitwarden-desktop-key.p12" -k build.keychain -P "" \ @@ -1041,22 +1049,22 @@ jobs: security import "$HOME/certificates/macdev-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain - name: Set up provisioning profiles if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile + cp "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" \ + "$GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile" - mkdir -p $HOME/Library/MobileDevice/Provisioning\ Profiles - export APP_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_appstore.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` - export AUTOFILL_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" + APP_UUID=$(grep UUID -A1 -a "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" | grep -io "[-A-Z0-9]\{36\}") + AUTOFILL_UUID=$(grep UUID -A1 -a "$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile" | grep -io "[-A-Z0-9]\{36\}") - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $HOME/Library/MobileDevice/Provisioning\ Profiles/$APP_UUID.provisionprofile - cp $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ - $HOME/Library/MobileDevice/Provisioning\ Profiles/$AUTOFILL_UUID.provisionprofile + cp "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" \ + "$HOME/Library/MobileDevice/Provisioning Profiles/$APP_UUID.provisionprofile" + cp "$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile" \ + "$HOME/Library/MobileDevice/Provisioning Profiles/$AUTOFILL_UUID.provisionprofile" - name: Increment version shell: pwsh @@ -1145,6 +1153,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -1197,39 +1206,39 @@ jobs: ACCOUNT_NAME: bitwardenci CONTAINER_NAME: profiles run: | - mkdir -p $HOME/secrets + mkdir -p "$HOME/secrets" - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ --name bitwarden_desktop_developer_id.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile \ + --file "$HOME/secrets/bitwarden_desktop_developer_id.provisionprofile" \ --output none - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ --name bitwarden_desktop_autofill_developer_id.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile \ + --file "$HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile" \ --output none - name: Get certificates run: | - mkdir -p $HOME/certificates + mkdir -p "$HOME/certificates" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | - jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 + jq -r .value | base64 -d > "$HOME/certificates/bitwarden-desktop-key.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-app-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/macdev-cert | - jq -r .value | base64 -d > $HOME/certificates/macdev-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/macdev-cert.p12" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main @@ -1238,9 +1247,9 @@ jobs: env: KEYCHAIN_PASSWORD: ${{ steps.get-kv-secrets.outputs.KEYCHAIN-PASSWORD }} run: | - security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain - security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -lut 1200 build.keychain security import "$HOME/certificates/bitwarden-desktop-key.p12" -k build.keychain -P "" \ @@ -1252,21 +1261,21 @@ jobs: security import "$HOME/certificates/devid-installer-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain - name: Set up provisioning profiles run: | - cp $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile \ - $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_developer_id.provisionprofile + cp "$HOME/secrets/bitwarden_desktop_developer_id.provisionprofile" \ + "$GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_developer_id.provisionprofile" - mkdir -p $HOME/Library/MobileDevice/Provisioning\ Profiles - export APP_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` - export AUTOFILL_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" + APP_UUID=$(grep UUID -A1 -a "$HOME/secrets/bitwarden_desktop_developer_id.provisionprofile" | grep -io "[-A-Z0-9]\{36\}") + AUTOFILL_UUID=$(grep UUID -A1 -a "$HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile" | grep -io "[-A-Z0-9]\{36\}") - cp $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile \ - $HOME/Library/MobileDevice/Provisioning\ Profiles/$APP_UUID.provisionprofile - cp $HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile \ - $HOME/Library/MobileDevice/Provisioning\ Profiles/$AUTOFILL_UUID.provisionprofile + cp "$HOME/secrets/bitwarden_desktop_developer_id.provisionprofile" \ + "$HOME/Library/MobileDevice/Provisioning Profiles/$APP_UUID.provisionprofile" + cp "$HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile" \ + "$HOME/Library/MobileDevice/Provisioning Profiles/$AUTOFILL_UUID.provisionprofile" - name: Increment version shell: pwsh @@ -1327,20 +1336,22 @@ jobs: - name: Unzip Safari artifact run: | - SAFARI_DIR=$(find $GITHUB_WORKSPACE/browser-build-artifacts -name 'dist-safari-*.zip') - echo $SAFARI_DIR - unzip $SAFARI_DIR/dist-safari.zip -d $GITHUB_WORKSPACE/browser-build-artifacts + SAFARI_DIR=$(find "$GITHUB_WORKSPACE/browser-build-artifacts" -name 'dist-safari-*.zip') + echo "$SAFARI_DIR" + unzip "$SAFARI_DIR/dist-safari.zip" -d "$GITHUB_WORKSPACE/browser-build-artifacts" - name: Load Safari extension for .dmg run: | mkdir PlugIns - cp -r $GITHUB_WORKSPACE/browser-build-artifacts/Safari/dmg/build/Release/safari.appex PlugIns/safari.appex + cp -r "$GITHUB_WORKSPACE/browser-build-artifacts/Safari/dmg/build/Release/safari.appex" PlugIns/safari.appex - name: Set up private auth key + env: + _APP_STORE_CONNECT_AUTH_KEY: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} run: | mkdir ~/private_keys cat << EOF > ~/private_keys/AuthKey_6TV9MKN3GP.p8 - ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} + $_APP_STORE_CONNECT_AUTH_KEY EOF - name: Build application (dist) @@ -1403,6 +1414,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -1462,39 +1474,39 @@ jobs: ACCOUNT_NAME: bitwardenci CONTAINER_NAME: profiles run: | - mkdir -p $HOME/secrets + mkdir -p "$HOME/secrets" - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ --name bitwarden_desktop_appstore.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ + --file "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" \ --output none - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ --name bitwarden_desktop_autofill_app_store_2024.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --file "$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile" \ --output none - name: Get certificates run: | - mkdir -p $HOME/certificates + mkdir -p "$HOME/certificates" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | - jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 + jq -r .value | base64 -d > "$HOME/certificates/bitwarden-desktop-key.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/appstore-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-app-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-app-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-app-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-installer-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/devid-installer-cert.p12" az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/macdev-cert | - jq -r .value | base64 -d > $HOME/certificates/macdev-cert.p12 + jq -r .value | base64 -d > "$HOME/certificates/macdev-cert.p12" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main @@ -1503,9 +1515,9 @@ jobs: env: KEYCHAIN_PASSWORD: ${{ steps.get-kv-secrets.outputs.KEYCHAIN-PASSWORD }} run: | - security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain - security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -lut 1200 build.keychain security import "$HOME/certificates/bitwarden-desktop-key.p12" -k build.keychain -P "" \ @@ -1517,21 +1529,21 @@ jobs: security import "$HOME/certificates/appstore-installer-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain - name: Set up provisioning profiles run: | - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile + cp "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" \ + "$GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile" - mkdir -p $HOME/Library/MobileDevice/Provisioning\ Profiles - export APP_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_appstore.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` - export AUTOFILL_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" + APP_UUID=$(grep UUID -A1 -a "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" | grep -io "[-A-Z0-9]\{36\}") + AUTOFILL_UUID=$(grep UUID -A1 -a "$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile" | grep -io "[-A-Z0-9]\{36\}") - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $HOME/Library/MobileDevice/Provisioning\ Profiles/$APP_UUID.provisionprofile - cp $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ - $HOME/Library/MobileDevice/Provisioning\ Profiles/$AUTOFILL_UUID.provisionprofile + cp "$HOME/secrets/bitwarden_desktop_appstore.provisionprofile" \ + "$HOME/Library/MobileDevice/Provisioning Profiles/$APP_UUID.provisionprofile" + cp "$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile" \ + "$HOME/Library/MobileDevice/Provisioning Profiles/$AUTOFILL_UUID.provisionprofile" - name: Increment version shell: pwsh @@ -1593,20 +1605,22 @@ jobs: - name: Unzip Safari artifact run: | - SAFARI_DIR=$(find $GITHUB_WORKSPACE/browser-build-artifacts -name 'dist-safari-*.zip') - echo $SAFARI_DIR - unzip $SAFARI_DIR/dist-safari.zip -d $GITHUB_WORKSPACE/browser-build-artifacts + SAFARI_DIR=$(find "$GITHUB_WORKSPACE/browser-build-artifacts" -name 'dist-safari-*.zip') + echo "$SAFARI_DIR" + unzip "$SAFARI_DIR/dist-safari.zip" -d "$GITHUB_WORKSPACE/browser-build-artifacts" - name: Load Safari extension for App Store run: | mkdir PlugIns - cp -r $GITHUB_WORKSPACE/browser-build-artifacts/Safari/mas/build/Release/safari.appex PlugIns/safari.appex + cp -r "$GITHUB_WORKSPACE/browser-build-artifacts/Safari/mas/build/Release/safari.appex" "PlugIns/safari.appex" - name: Set up private auth key + env: + _APP_STORE_CONNECT_AUTH_KEY: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} run: | mkdir ~/private_keys cat << EOF > ~/private_keys/AuthKey_6TV9MKN3GP.p8 - ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} + $_APP_STORE_CONNECT_AUTH_KEY EOF - name: Build application for App Store @@ -1645,6 +1659,8 @@ jobs: if: | github.event_name != 'pull_request_target' && (inputs.testflight_distribute || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop') + env: + _APP_STORE_CONNECT_TEAM_ISSUER: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-TEAM-ISSUER }} run: | brew install gsed @@ -1652,7 +1668,7 @@ jobs: cat << EOF > ~/secrets/appstoreconnect-fastlane.json { - "issuer_id": "${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-TEAM-ISSUER }}", + "issuer_id": "$_APP_STORE_CONNECT_TEAM_ISSUER", "key_id": "6TV9MKN3GP", "key": "$KEY_WITHOUT_NEWLINES" } @@ -1671,14 +1687,14 @@ jobs: GIT_CHANGE="$(git show -s --format=%s)" - BRANCH=$(echo $BRANCH | sed 's/refs\/heads\///') + BRANCH=$(echo "$BRANCH" | sed 's/refs\/heads\///') CHANGELOG="$BRANCH: $GIT_CHANGE" fastlane pilot upload \ --app_identifier "com.bitwarden.desktop" \ --changelog "$CHANGELOG" \ - --api_key_path $HOME/secrets/appstoreconnect-fastlane.json \ + --api_key_path "$HOME/secrets/appstoreconnect-fastlane.json" \ --pkg "$(find ./dist/mas-universal/Bitwarden*.pkg)" - name: Post message to a Slack channel @@ -1724,6 +1740,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index d623f08ebac..6733eeca1b4 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -67,23 +67,24 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Get GitHub sha as version id: version - run: echo "value=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT + run: echo "value=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT" - name: Get Node Version id: retrieve-node-version run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Check secrets id: check-secrets run: | has_secrets=${{ secrets.AZURE_CLIENT_ID != '' }} - echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT + echo "has_secrets=$has_secrets" >> "$GITHUB_OUTPUT" build-containers: @@ -137,6 +138,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Get Latest Server Version id: latest-server-version @@ -147,8 +149,10 @@ jobs: - name: Set Server Ref id: set-server-ref + env: + _SERVER_VERSION: ${{ steps.latest-server-version.outputs.version }} run: | - SERVER_REF="${{ steps.latest-server-version.outputs.version }}" + SERVER_REF="$_SERVER_VERSION" echo "Latest server release version: $SERVER_REF" if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then SERVER_REF="$GITHUB_REF" @@ -158,7 +162,7 @@ jobs: SERVER_REF="refs/heads/main" fi echo "Server ref: $SERVER_REF" - echo "server_ref=$SERVER_REF" >> $GITHUB_OUTPUT + echo "server_ref=$SERVER_REF" >> "$GITHUB_OUTPUT" - name: Check out Server repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -166,18 +170,19 @@ jobs: path: server repository: bitwarden/server ref: ${{ steps.set-server-ref.outputs.server_ref }} + persist-credentials: false - name: Check Branch to Publish env: PUBLISH_BRANCHES: "main,rc,hotfix-rc-web" id: publish-branch-check run: | - IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES + IFS="," read -a publish_branches <<< "$PUBLISH_BRANCHES" if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " ]]; then - echo "is_publish_branch=true" >> $GITHUB_ENV + echo "is_publish_branch=true" >> "$GITHUB_ENV" else - echo "is_publish_branch=false" >> $GITHUB_ENV + echo "is_publish_branch=false" >> "$GITHUB_ENV" fi - name: Add Git metadata to build version @@ -217,11 +222,13 @@ jobs: - name: Log into Prod container registry if: ${{ needs.setup.outputs.has_secrets == 'true' }} - run: az acr login -n ${_AZ_REGISTRY%.azurecr.io} + run: az acr login -n "${_AZ_REGISTRY%.azurecr.io}" ########## Generate image tag and build Docker image ########## - name: Generate container image tag id: tag + env: + _TAG_EXTENSION: ${{ github.event.inputs.custom_tag_extension }} run: | if [[ "${GITHUB_EVENT_NAME}" == "pull_request" || "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s/[^a-zA-Z0-9]/-/g") # Sanitize branch name to alphanumeric only @@ -231,7 +238,7 @@ jobs: if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then SANITIZED_REPO_NAME=$(echo "$_GITHUB_PR_REPO_NAME" | sed "s/[^a-zA-Z0-9]/-/g") # Sanitize repo name to alphanumeric only - IMAGE_TAG=$SANITIZED_REPO_NAME-$IMAGE_TAG # Add repo name to the tag + IMAGE_TAG="$SANITIZED_REPO_NAME-$IMAGE_TAG" # Add repo name to the tag IMAGE_TAG=${IMAGE_TAG:0:128} # Limit to 128 characters, as that's the max length for Docker image tags fi @@ -239,13 +246,13 @@ jobs: IMAGE_TAG=dev fi - TAG_EXTENSION=${{ github.event.inputs.custom_tag_extension }} + TAG_EXTENSION="$_TAG_EXTENSION" if [[ $TAG_EXTENSION ]]; then - IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION + IMAGE_TAG="$IMAGE_TAG-$TAG_EXTENSION" fi - echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" ########## Build Image ########## - name: Generate image full name @@ -253,7 +260,7 @@ jobs: env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} PROJECT_NAME: ${{ matrix.image_name }} - run: echo "name=$_AZ_REGISTRY/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT + run: echo "name=$_AZ_REGISTRY/${PROJECT_NAME}:${IMAGE_TAG}" >> "$GITHUB_OUTPUT" - name: Build Docker image id: build-container @@ -276,7 +283,7 @@ jobs: if: ${{ needs.setup.outputs.has_secrets == 'true' }} env: IMAGE_NAME: ${{ steps.image-name.outputs.name }} - run: docker push $IMAGE_NAME + run: docker push "$IMAGE_NAME" - name: Zip project working-directory: apps/web @@ -284,10 +291,10 @@ jobs: IMAGE_NAME: ${{ steps.image-name.outputs.name }} run: | mkdir build - docker run --rm --volume $(pwd)/build:/temp --entrypoint sh \ - $IMAGE_NAME -c "cp -r ./ /temp" + docker run --rm --volume "$(pwd)/build":/temp --entrypoint sh \ + "$IMAGE_NAME" -c "cp -r ./ /temp" - zip -r web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip build + zip -r web-$_VERSION-${{ matrix.artifact_name }}.zip build - name: Upload ${{ matrix.artifact_name }} artifact uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 @@ -306,12 +313,13 @@ jobs: DIGEST: ${{ steps.build-container.outputs.digest }} TAGS: ${{ steps.image-name.outputs.name }} run: | - IFS="," read -a tags <<< "${TAGS}" - images="" - for tag in "${tags[@]}"; do - images+="${tag}@${DIGEST} " + IFS=',' read -r -a tags_array <<< "${TAGS}" + images=() + for tag in "${tags_array[@]}"; do + images+=("${tag}@${DIGEST}") done - cosign sign --yes ${images} + cosign sign --yes "${images[@]}" + echo "images=${images[*]}" >> "$GITHUB_OUTPUT" - name: Scan Docker image if: ${{ needs.setup.outputs.has_secrets == 'true' }} @@ -324,14 +332,14 @@ jobs: - name: Upload Grype results to GitHub if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 + uses: github/codeql-action/upload-sarif@573acd9552f33577783abde4acb66a1058e762e5 # codeql-bundle-v2.23.1 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }} ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} - name: Log out of Docker - run: docker logout $_AZ_REGISTRY + run: docker logout "$_AZ_REGISTRY" - name: Log out from Azure if: ${{ needs.setup.outputs.has_secrets == 'true' }} @@ -352,6 +360,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 2b7b6394f24..133f5b730b8 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -35,6 +35,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 + persist-credentials: false - name: Get changed files id: get-changed-files-for-chromatic @@ -54,7 +55,7 @@ jobs: run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 0b891203855..3be294145ec 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -59,6 +59,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: token: ${{ steps.app-token.outputs.token }} + persist-credentials: false - name: Download translations uses: bitwarden/gh-actions/crowdin@main diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index d3788dc77b9..26a83e89773 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -74,56 +74,58 @@ jobs: steps: - name: Configure id: config + env: + _ENVIRONMENT: ${{ inputs.environment }} run: | - ENV_NAME_LOWER=$(echo "${{ inputs.environment }}" | awk '{print tolower($0)}') - echo "configuring the Web deploy for ${{ inputs.environment }}" - echo "environment=${{ inputs.environment }}" >> $GITHUB_OUTPUT + ENV_NAME_LOWER=$(echo "$_ENVIRONMENT" | awk '{print tolower($0)}') + echo "configuring the Web deploy for _ENVIRONMENT" + echo "environment=$_ENVIRONMENT" >> "$GITHUB_OUTPUT" - case ${{ inputs.environment }} in + case $_ENVIRONMENT in "USQA") - echo "azure_login_client_key_name=AZURE_CLIENT_ID_USQA" >> $GITHUB_OUTPUT - echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_USQA" >> $GITHUB_OUTPUT - echo "retrieve_secrets_keyvault=bw-webvault-rlktusqa-kv" >> $GITHUB_OUTPUT - echo "environment_artifact=web-*-cloud-QA.zip" >> $GITHUB_OUTPUT - echo "environment_name=Web Vault - US QA Cloud" >> $GITHUB_OUTPUT - echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack_channel_name=alerts-deploy-qa" >> $GITHUB_OUTPUT + echo "azure_login_client_key_name=AZURE_CLIENT_ID_USQA" >> "$GITHUB_OUTPUT" + echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_USQA" >> "$GITHUB_OUTPUT" + echo "retrieve_secrets_keyvault=bw-webvault-rlktusqa-kv" >> "$GITHUB_OUTPUT" + echo "environment_artifact=web-*-cloud-QA.zip" >> "$GITHUB_OUTPUT" + echo "environment_name=Web Vault - US QA Cloud" >> "$GITHUB_OUTPUT" + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> "$GITHUB_OUTPUT" + echo "slack_channel_name=alerts-deploy-qa" >> "$GITHUB_OUTPUT" ;; "EUQA") - echo "azure_login_client_key_name=AZURE_CLIENT_ID_EUQA" >> $GITHUB_OUTPUT - echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_EUQA" >> $GITHUB_OUTPUT - echo "retrieve_secrets_keyvault=webvaulteu-westeurope-qa" >> $GITHUB_OUTPUT - echo "environment_artifact=web-*-cloud-euqa.zip" >> $GITHUB_OUTPUT - echo "environment_name=Web Vault - EU QA Cloud" >> $GITHUB_OUTPUT - echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack_channel_name=alerts-deploy-qa" >> $GITHUB_OUTPUT + echo "azure_login_client_key_name=AZURE_CLIENT_ID_EUQA" >> "$GITHUB_OUTPUT" + echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_EUQA" >> "$GITHUB_OUTPUT" + echo "retrieve_secrets_keyvault=webvaulteu-westeurope-qa" >> "$GITHUB_OUTPUT" + echo "environment_artifact=web-*-cloud-euqa.zip" >> "$GITHUB_OUTPUT" + echo "environment_name=Web Vault - EU QA Cloud" >> "$GITHUB_OUTPUT" + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> "$GITHUB_OUTPUT" + echo "slack_channel_name=alerts-deploy-qa" >> "$GITHUB_OUTPUT" ;; "USPROD") - echo "azure_login_client_key_name=AZURE_CLIENT_ID_USPROD" >> $GITHUB_OUTPUT - echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_USPROD" >> $GITHUB_OUTPUT - echo "retrieve_secrets_keyvault=bw-webvault-klrt-kv" >> $GITHUB_OUTPUT - echo "environment_artifact=web-*-cloud-COMMERCIAL.zip" >> $GITHUB_OUTPUT - echo "environment_name=Web Vault - US Production Cloud" >> $GITHUB_OUTPUT - echo "environment_url=http://vault.bitwarden.com" >> $GITHUB_OUTPUT - echo "slack_channel_name=alerts-deploy-prd" >> $GITHUB_OUTPUT + echo "azure_login_client_key_name=AZURE_CLIENT_ID_USPROD" >> "$GITHUB_OUTPUT" + echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_USPROD" >> "$GITHUB_OUTPUT" + echo "retrieve_secrets_keyvault=bw-webvault-klrt-kv" >> "$GITHUB_OUTPUT" + echo "environment_artifact=web-*-cloud-COMMERCIAL.zip" >> "$GITHUB_OUTPUT" + echo "environment_name=Web Vault - US Production Cloud" >> "$GITHUB_OUTPUT" + echo "environment_url=http://vault.bitwarden.com" >> "$GITHUB_OUTPUT" + echo "slack_channel_name=alerts-deploy-prd" >> "$GITHUB_OUTPUT" ;; "EUPROD") - echo "azure_login_client_key_name=AZURE_CLIENT_ID_EUPROD" >> $GITHUB_OUTPUT - echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_EUPROD" >> $GITHUB_OUTPUT - echo "retrieve_secrets_keyvault=webvault-westeurope-prod" >> $GITHUB_OUTPUT - echo "environment_artifact=web-*-cloud-euprd.zip" >> $GITHUB_OUTPUT - echo "environment_name=Web Vault - EU Production Cloud" >> $GITHUB_OUTPUT - echo "environment_url=http://vault.bitwarden.eu" >> $GITHUB_OUTPUT - echo "slack_channel_name=alerts-deploy-prd" >> $GITHUB_OUTPUT + echo "azure_login_client_key_name=AZURE_CLIENT_ID_EUPROD" >> "$GITHUB_OUTPUT" + echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_EUPROD" >> "$GITHUB_OUTPUT" + echo "retrieve_secrets_keyvault=webvault-westeurope-prod" >> "$GITHUB_OUTPUT" + echo "environment_artifact=web-*-cloud-euprd.zip" >> "$GITHUB_OUTPUT" + echo "environment_name=Web Vault - EU Production Cloud" >> "$GITHUB_OUTPUT" + echo "environment_url=http://vault.bitwarden.eu" >> "$GITHUB_OUTPUT" + echo "slack_channel_name=alerts-deploy-prd" >> "$GITHUB_OUTPUT" ;; "USDEV") - echo "azure_login_client_key_name=AZURE_CLIENT_ID_USDEV" >> $GITHUB_OUTPUT - echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_USDEV" >> $GITHUB_OUTPUT - echo "retrieve_secrets_keyvault=webvault-eastus-dev" >> $GITHUB_OUTPUT - echo "environment_artifact=web-*-cloud-usdev.zip" >> $GITHUB_OUTPUT - echo "environment_name=Web Vault - US Development Cloud" >> $GITHUB_OUTPUT - echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack_channel_name=alerts-deploy-dev" >> $GITHUB_OUTPUT + echo "azure_login_client_key_name=AZURE_CLIENT_ID_USDEV" >> "$GITHUB_OUTPUT" + echo "azure_login_subscription_id_key_name=AZURE_SUBSCRIPTION_ID_USDEV" >> "$GITHUB_OUTPUT" + echo "retrieve_secrets_keyvault=webvault-eastus-dev" >> "$GITHUB_OUTPUT" + echo "environment_artifact=web-*-cloud-usdev.zip" >> "$GITHUB_OUTPUT" + echo "environment_name=Web Vault - US Development Cloud" >> "$GITHUB_OUTPUT" + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> "$GITHUB_OUTPUT" + echo "slack_channel_name=alerts-deploy-dev" >> "$GITHUB_OUTPUT" ;; esac @@ -131,12 +133,14 @@ jobs: env: BUILD_WEB_RUN_ID: ${{ inputs.build-web-run-id }} GH_TOKEN: ${{ github.token }} + _ENVIRONMENT: ${{ inputs.environment }} + _BRANCH_OR_TAG: ${{ inputs.branch-or-tag }} run: | BRANCH_OR_TAG_LOWER="" if [[ "$BUILD_WEB_RUN_ID" == "" ]]; then - BRANCH_OR_TAG_LOWER=$(echo ${{ inputs.branch-or-tag }} | awk '{print tolower($0)}') + BRANCH_OR_TAG_LOWER=$(echo "$_BRANCH_OR_TAG" | awk '{print tolower($0)}') else - BRANCH_OR_TAG_LOWER=$(gh api /repos/bitwarden/clients/actions/runs/$BUILD_WEB_RUN_ID/artifacts --jq '.artifacts[0].workflow_run.head_branch' | awk '{print tolower($0)}') + BRANCH_OR_TAG_LOWER=$(gh api "/repos/bitwarden/clients/actions/runs/$BUILD_WEB_RUN_ID/artifacts" --jq '.artifacts[0].workflow_run.head_branch' | awk '{print tolower($0)}') fi echo "Branch/Tag: $BRANCH_OR_TAG_LOWER" @@ -151,23 +155,23 @@ jobs: DEV_ALLOWED_TAGS_PATTERN='main' if [[ \ - ${{ inputs.environment }} =~ \.*($PROD_ENV_PATTERN)\.* && \ + $_ENVIRONMENT =~ \.*($PROD_ENV_PATTERN)\.* && \ ! "$BRANCH_OR_TAG_LOWER" =~ ^($PROD_ALLOWED_TAGS_PATTERN).* \ ]] || [[ \ - ${{ inputs.environment }} =~ \.*($QA_ENV_PATTERN)\.* && \ + $_ENVIRONMENT =~ \.*($QA_ENV_PATTERN)\.* && \ ! "$BRANCH_OR_TAG_LOWER" =~ ^($QA_ALLOWED_TAGS_PATTERN).* \ ]] || [[ \ - ${{ inputs.environment }} =~ \.*($DEV_ENV_PATTERN)\.* && \ - $BRANCH_OR_TAG_LOWER != $DEV_ALLOWED_TAGS_PATTERN \ + $_ENVIRONMENT =~ \.*($DEV_ENV_PATTERN)\.* && \ + $BRANCH_OR_TAG_LOWER != "$DEV_ALLOWED_TAGS_PATTERN" \ ]]; then echo "!Deployment blocked!" - echo "Attempting to deploy a tag that is not allowed in ${{ inputs.environment }} environment" + echo "Attempting to deploy a tag that is not allowed in $_ENVIRONMENT environment" echo - echo "Environment: ${{ inputs.environment }}" + echo "Environment: $_ENVIRONMENT" echo "Tag: $BRANCH_OR_TAG_LOWER" exit 1 else - echo "The input Branch/Tag: '$BRANCH_OR_TAG_LOWER' is allowed to deploy on ${{ inputs.environment }} environment" + echo "The input Branch/Tag: '$BRANCH_OR_TAG_LOWER' is allowed to deploy on $_ENVIRONMENT environment" fi approval: @@ -251,19 +255,24 @@ jobs: id: set-artifact-commit env: GH_TOKEN: ${{ github.token }} + _BUILD_WEB_RUN_ID: ${{ inputs.build-web-run-id }} + _ARTIFACT_BUILD_COMMIT: ${{ steps.download-latest-artifacts-run-id.outputs.artifact-build-commit }} + _DOWNLOAD_LATEST_ARTIFACTS_OUTCOME: ${{ steps.download-latest-artifacts.outcome }} + _WORKFLOW_ID: ${{ steps.trigger-build-web.outputs.workflow_id}} + _ARTIFACT_COMMIT: ${{ steps.download-latest-artifacts.outputs.artifact-build-commit }} run: | # If run-id was used, get the commit from the download-latest-artifacts-run-id step - if [ "${{ inputs.build-web-run-id }}" ]; then - echo "commit=${{ steps.download-latest-artifacts-run-id.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + if [ "$_BUILD_WEB_RUN_ID" ]; then + echo "commit=$_ARTIFACT_BUILD_COMMIT" >> "$GITHUB_OUTPUT" - elif [ "${{ steps.download-latest-artifacts.outcome }}" == "failure" ]; then + elif [ "$_DOWNLOAD_LATEST_ARTIFACTS_OUTCOME" == "failure" ]; then # If the download-latest-artifacts step failed, query the GH API to get the commit SHA of the artifact that was just built with trigger-build-web. - commit=$(gh api /repos/bitwarden/clients/actions/runs/${{ steps.trigger-build-web.outputs.workflow_id }}/artifacts --jq '.artifacts[0].workflow_run.head_sha') - echo "commit=$commit" >> $GITHUB_OUTPUT + commit=$(gh api "/repos/bitwarden/clients/actions/runs/$_WORKFLOW_ID/artifacts" --jq '.artifacts[0].workflow_run.head_sha') + echo "commit=$commit" >> "$GITHUB_OUTPUT" else # Set the commit to the output of step download-latest-artifacts. - echo "commit=${{ steps.download-latest-artifacts.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + echo "commit=$_ARTIFACT_COMMIT" >> "$GITHUB_OUTPUT" fi notify-start: @@ -299,12 +308,14 @@ jobs: name: Display commit needs: artifact-check runs-on: ubuntu-22.04 + env: + _ARTIFACT_BUILD_COMMIT_SHA: ${{ needs.artifact-check.outputs.artifact_build_commit }} steps: - name: Display commit SHA run: | REPO_URL="https://github.com/bitwarden/clients/commit" - COMMIT_SHA="${{ needs.artifact-check.outputs.artifact_build_commit }}" - echo ":steam_locomotive: View [commit]($REPO_URL/$COMMIT_SHA)" >> $GITHUB_STEP_SUMMARY + COMMIT_SHA="$_ARTIFACT_BUILD_COMMIT_SHA" + echo ":steam_locomotive: View [commit]($REPO_URL/$COMMIT_SHA)" >> "$GITHUB_STEP_SUMMARY" azure-deploy: name: Deploy Web Vault to ${{ inputs.environment }} Storage Account @@ -358,7 +369,7 @@ jobs: - name: Unzip build asset working-directory: apps/web - run: unzip ${{ env._ENVIRONMENT_ARTIFACT }} + run: unzip "$_ENVIRONMENT_ARTIFACT" - name: Login to Azure uses: bitwarden/gh-actions/azure-login@main @@ -379,9 +390,10 @@ jobs: env: AZCOPY_AUTO_LOGIN_TYPE: AZCLI AZCOPY_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + _VAULT_NAME: ${{ steps.retrieve-secrets-azcopy.outputs.sa-bitwarden-web-vault-name }}. run: | - azcopy sync ./build 'https://${{ steps.retrieve-secrets-azcopy.outputs.sa-bitwarden-web-vault-name }}.blob.core.windows.net/$web/' \ - --delete-destination=${{ inputs.force-delete-destination }} --compare-hash="MD5" + azcopy sync ./build "https://$_VAULT_NAME.blob.core.windows.net/$web/" \ + --delete-destination="${{ inputs.force-delete-destination }}" --compare-hash="MD5" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main diff --git a/.github/workflows/lint-crowdin-config.yml b/.github/workflows/lint-crowdin-config.yml index 38a3ef59ea7..40f73f7fc5a 100644 --- a/.github/workflows/lint-crowdin-config.yml +++ b/.github/workflows/lint-crowdin-config.yml @@ -25,6 +25,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 1 + persist-credentials: false - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 14b5d51d9ef..0136bd2f70f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,6 +32,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Lint filenames (no capital characters) run: | @@ -58,7 +60,7 @@ jobs: run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -90,6 +92,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Check Rust version run: rustup --version diff --git a/.github/workflows/locales-lint.yml b/.github/workflows/locales-lint.yml index 0c8148d4c28..26c910f955e 100644 --- a/.github/workflows/locales-lint.yml +++ b/.github/workflows/locales-lint.yml @@ -18,17 +18,19 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Checkout base branch repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.base.sha }} path: base + persist-credentials: false - name: Install dependencies run: npm ci - name: Compare run: | - npm run test:locales - if [ $? -eq 0 ]; then + if npm run test:locales; then echo "Lint check successful." else echo "Lint check failed." diff --git a/.github/workflows/nx.yml b/.github/workflows/nx.yml index 526c2b5d864..3e14169a065 100644 --- a/.github/workflows/nx.yml +++ b/.github/workflows/nx.yml @@ -15,6 +15,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false - name: Get Node Version id: retrieve-node-version @@ -22,7 +23,7 @@ jobs: run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 1287970ccf6..9bbd982d32f 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -65,14 +65,16 @@ jobs: - name: Version output id: version-output + env: + _INPUT_VERSION: ${{ inputs.version }} run: | - if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then + if [[ "$_INPUT_VERSION" == "latest" || "$_INPUT_VERSION" == "" ]]; then VERSION=$(curl "https://api.github.com/repos/bitwarden/clients/releases" | jq -c '.[] | select(.tag_name | contains("cli")) | .tag_name' | head -1 | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') echo "Latest Released Version: $VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> "$GITHUB_OUTPUT" else - echo "Release Version: ${{ inputs.version }}" - echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "Release Version: $_INPUT_VERSION" + echo "version=$_INPUT_VERSION" >> "$GITHUB_OUTPUT" fi - name: Create GitHub deployment @@ -100,6 +102,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main @@ -122,14 +126,14 @@ jobs: uses: samuelmeuli/action-snapcraft@fceeb3c308e76f3487e72ef608618de625fb7fe8 # v3.0.1 - name: Download artifacts - run: wget https://github.com/bitwarden/clients/releases/download/cli-v${{ env._PKG_VERSION }}/bw_${{ env._PKG_VERSION }}_amd64.snap + run: wget "https://github.com/bitwarden/clients/releases/download/cli-v$_PKG_VERSION/bw_$_PKG_VERSION_amd64.snap" - name: Publish Snap & logout if: ${{ inputs.publish_type != 'Dry Run' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | - snapcraft upload bw_${{ env._PKG_VERSION }}_amd64.snap --release stable + snapcraft upload "bw_$_PKG_VERSION_amd64.snap" --release stable snapcraft logout choco: @@ -146,6 +150,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main @@ -173,7 +179,7 @@ jobs: run: New-Item -ItemType directory -Path ./dist - name: Download artifacts - run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/cli-v${{ env._PKG_VERSION }}/bitwarden-cli.${{ env._PKG_VERSION }}.nupkg" -OutFile bitwarden-cli.${{ env._PKG_VERSION }}.nupkg + run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/cli-v$_PKG_VERSION/bitwarden-cli.$_PKG_VERSION.nupkg" -OutFile bitwarden-cli.$_PKG_VERSION.nupkg working-directory: apps/cli/dist - name: Push to Chocolatey @@ -196,6 +202,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Get Node version id: retrieve-node-version @@ -203,7 +211,7 @@ jobs: run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -219,8 +227,8 @@ jobs: - name: Download and set up artifact run: | mkdir -p build - wget https://github.com/bitwarden/clients/releases/download/cli-v${{ env._PKG_VERSION }}/bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip - unzip bitwarden-cli-${{ env._PKG_VERSION }}-npm-build.zip -d build + wget "https://github.com/bitwarden/clients/releases/download/cli-v$_PKG_VERSION/bitwarden-cli-$_PKG_VERSION-npm-build.zip" + unzip "bitwarden-cli-$_PKG_VERSION-npm-build.zip" -d build - name: Publish NPM if: ${{ inputs.publish_type != 'Dry Run' }} diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index f0de331431c..a747012467e 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -72,39 +72,47 @@ jobs: - name: Check Publish Version id: version + env: + _INPUT_VERSION: ${{ inputs.version }} run: | - if [[ "${{ inputs.version }}" == "latest" || "${{ inputs.version }}" == "" ]]; then - TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/clients/releases" | jq -c '.[] | select(.tag_name | contains("desktop")) | .tag_name' | head -1 | cut -d '"' -f 2) - VERSION=$(echo $TAG_NAME | sed "s/desktop-v//") + if [[ "$_INPUT_VERSION" == "latest" || "$_INPUT_VERSION" == "" ]]; then + TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/clients/releases" \ + | jq -c '.[] | select(.tag_name | contains("desktop")) | .tag_name' \ + | head -1 | cut -d '"' -f 2) + VERSION="${TAG_NAME#desktop-v}" + echo "Latest Released Version: $VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Tag name: $TAG_NAME" - echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" else - echo "Release Version: ${{ inputs.version }}" - echo "version=${{ inputs.version }}" + VERSION="$_INPUT_VERSION" + TAG_NAME="desktop-v$VERSION" - TAG_NAME="desktop-v${{ inputs.version }}" + echo "Release Version: $VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Tag name: $TAG_NAME" - echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" fi - name: Get Version Channel id: release_channel + env: + _VERSION: ${{ steps.version.outputs.version }} run: | - case "${{ steps.version.outputs.version }}" in + case "${_VERSION}" in *"alpha"*) - echo "channel=alpha" >> $GITHUB_OUTPUT + echo "channel=alpha" >> "$GITHUB_OUTPUT" echo "[!] We do not yet support 'alpha'" exit 1 ;; *"beta"*) - echo "channel=beta" >> $GITHUB_OUTPUT + echo "channel=beta" >> "$GITHUB_OUTPUT" ;; *) - echo "channel=latest" >> $GITHUB_OUTPUT + echo "channel=latest" >> "$GITHUB_OUTPUT" ;; esac @@ -159,16 +167,16 @@ jobs: env: GH_TOKEN: ${{ github.token }} working-directory: apps/desktop/artifacts - run: gh release download ${{ env._RELEASE_TAG }} -R bitwarden/clients + run: gh release download "$_RELEASE_TAG" -R bitwarden/clients - name: Set staged rollout percentage env: RELEASE_CHANNEL: ${{ needs.setup.outputs.release_channel }} ROLLOUT_PCT: ${{ inputs.electron_rollout_percentage }} run: | - echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml - echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml - echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml + echo "stagingPercentage: ${ROLLOUT_PCT}" >> "apps/desktop/artifacts/${RELEASE_CHANNEL}.yml" + echo "stagingPercentage: ${ROLLOUT_PCT}" >> "apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml" + echo "stagingPercentage: ${ROLLOUT_PCT}" >> "apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml" - name: Publish artifacts to S3 if: ${{ inputs.publish_type != 'Dry Run' }} @@ -179,7 +187,7 @@ jobs: AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }} working-directory: apps/desktop/artifacts run: | - aws s3 cp ./ $AWS_S3_BUCKET_NAME/desktop/ \ + aws s3 cp ./ "$AWS_S3_BUCKET_NAME/desktop/" \ --acl "public-read" \ --recursive \ --quiet @@ -214,6 +222,8 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main @@ -241,14 +251,14 @@ jobs: - name: Download artifacts working-directory: apps/desktop/dist - run: wget https://github.com/bitwarden/clients/releases/download/${{ env._RELEASE_TAG }}/bitwarden_${{ env._PKG_VERSION }}_amd64.snap + run: wget "https://github.com/bitwarden/clients/releases/download/$_RELEASE_TAG/bitwarden_$_PKG_VERSION_amd64.snap" - name: Deploy to Snap Store if: ${{ inputs.publish_type != 'Dry Run' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | - snapcraft upload bitwarden_${{ env._PKG_VERSION }}_amd64.snap --release stable + snapcraft upload "bitwarden_$_PKG_VERSION_amd64.snap" --release stable snapcraft logout working-directory: apps/desktop/dist @@ -266,6 +276,8 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Print Environment run: | @@ -300,7 +312,7 @@ jobs: - name: Download artifacts working-directory: apps/desktop/dist - run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/${{ env._RELEASE_TAG }}/bitwarden.${{ env._PKG_VERSION }}.nupkg" -OutFile bitwarden.${{ env._PKG_VERSION }}.nupkg + run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/$_RELEASE_TAG/bitwarden.$_PKG_VERSION.nupkg" -OutFile "bitwarden.$_PKG_VERSION.nupkg" - name: Push to Chocolatey if: ${{ inputs.publish_type != 'Dry Run' }} @@ -321,6 +333,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Validate release notes for MAS if: inputs.mas_publish && (inputs.release_notes == '' || inputs.release_notes == null) @@ -331,7 +345,7 @@ jobs: - name: Download MacOS App Store build number working-directory: apps/desktop - run: wget https://github.com/bitwarden/clients/releases/download/${{ env._RELEASE_TAG }}/macos-build-number.json + run: wget "https://github.com/bitwarden/clients/releases/download/$_RELEASE_TAG/macos-build-number.json" - name: Setup Ruby and Install Fastlane uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0 @@ -365,12 +379,14 @@ jobs: env: APP_STORE_CONNECT_TEAM_ISSUER: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-TEAM-ISSUER }} APP_STORE_CONNECT_AUTH_KEY: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} + _RELEASE_NOTES: ${{ inputs.release_notes }} + _PUBLISH_TYPE: ${{ inputs.publish_type }} working-directory: apps/desktop run: | BUILD_NUMBER=$(jq -r '.buildNumber' macos-build-number.json) - CHANGELOG="${{ inputs.release_notes }}" - IS_DRY_RUN="${{ inputs.publish_type == 'Dry Run' }}" - + CHANGELOG="$_RELEASE_NOTES" + IS_DRY_RUN="$_PUBLISH_TYPE == 'Dry Run'" + if [ "$IS_DRY_RUN" = "true" ]; then echo "🧪 DRY RUN MODE - Testing without actual App Store submission" echo "📦 Would publish build $BUILD_NUMBER to Mac App Store" @@ -388,10 +404,10 @@ jobs: fi fastlane publish --verbose \ - app_version:"${{ env._PKG_VERSION }}" \ - build_number:$BUILD_NUMBER \ + app_version:"$PKG_VERSION" \ + build_number:"$BUILD_NUMBER" \ changelog:"$CHANGELOG" \ - dry_run:$IS_DRY_RUN + dry_run:"$IS_DRY_RUN" update-deployment: name: Update Deployment Status diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index 6446e625156..9f9cbd5c58e 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -29,6 +29,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Branch check if: ${{ inputs.publish_type != 'Dry Run' }} @@ -73,6 +75,8 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false ########## ACR ########## - name: Log in to Azure @@ -100,33 +104,33 @@ jobs: - name: Pull branch image run: | if [[ "${{ inputs.publish_type }}" == "Dry Run" ]]; then - docker pull $_AZ_REGISTRY/web:latest + docker pull "$_AZ_REGISTRY/web:latest" else - docker pull $_AZ_REGISTRY/web:$_BRANCH_NAME + docker pull "$_AZ_REGISTRY/web:$_BRANCH_NAME" fi - name: Tag version run: | if [[ "${{ inputs.publish_type }}" == "Dry Run" ]]; then - docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web:dryrun - docker tag $_AZ_REGISTRY/web:latest $_AZ_REGISTRY/web-sh:dryrun + docker tag "$_AZ_REGISTRY/web:latest" "$_AZ_REGISTRY/web:dryrun" + docker tag "$_AZ_REGISTRY/web:latest" "$_AZ_REGISTRY/web-sh:dryrun" else - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web:$_RELEASE_VERSION - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web-sh:$_RELEASE_VERSION - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web:latest - docker tag $_AZ_REGISTRY/web:$_BRANCH_NAME $_AZ_REGISTRY/web-sh:latest + docker tag "$_AZ_REGISTRY/web:$_BRANCH_NAME" "$_AZ_REGISTRY/web:$_RELEASE_VERSION" + docker tag "$_AZ_REGISTRY/web:$_BRANCH_NAME" "$_AZ_REGISTRY/web-sh:$_RELEASE_VERSION" + docker tag "$_AZ_REGISTRY/web:$_BRANCH_NAME" "$_AZ_REGISTRY/web:latest" + docker tag "$_AZ_REGISTRY/web:$_BRANCH_NAME" "$_AZ_REGISTRY/web-sh:latest" fi - name: Push version run: | if [[ "${{ inputs.publish_type }}" == "Dry Run" ]]; then - docker push $_AZ_REGISTRY/web:dryrun - docker push $_AZ_REGISTRY/web-sh:dryrun + docker push "$_AZ_REGISTRY/web:dryrun" + docker push "$_AZ_REGISTRY/web-sh:dryrun" else - docker push $_AZ_REGISTRY/web:$_RELEASE_VERSION - docker push $_AZ_REGISTRY/web-sh:$_RELEASE_VERSION - docker push $_AZ_REGISTRY/web:latest - docker push $_AZ_REGISTRY/web-sh:latest + docker push "$_AZ_REGISTRY/web:$_RELEASE_VERSION" + docker push "$_AZ_REGISTRY/web-sh:$_RELEASE_VERSION" + docker push "$_AZ_REGISTRY/web:latest" + docker push "$_AZ_REGISTRY/web-sh:latest" fi - name: Log out from Azure diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index ac79287f84d..a2fda230491 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -29,6 +29,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -60,6 +62,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Testing locales - extName length run: | @@ -69,9 +73,11 @@ jobs: echo "============" echo "extName string must be 40 characters or less" echo - for locale in $(ls src/_locales/); do - string_length=$(jq '.extName.message | length' src/_locales/$locale/messages.json) - if [[ $string_length -gt 40 ]]; then + + for locale_path in src/_locales/*/messages.json; do + locale=$(basename "$(dirname "$locale_path")") + string_length=$(jq '.extName.message | length' "$locale_path") + if [ "$string_length" -gt 40 ]; then echo "$locale: $string_length" found_error=true fi @@ -126,11 +132,11 @@ jobs: env: PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} run: | - mv browser-source.zip browser-source-$PACKAGE_VERSION.zip - mv dist-chrome.zip dist-chrome-$PACKAGE_VERSION.zip - mv dist-opera.zip dist-opera-$PACKAGE_VERSION.zip - mv dist-firefox.zip dist-firefox-$PACKAGE_VERSION.zip - mv dist-edge.zip dist-edge-$PACKAGE_VERSION.zip + mv browser-source.zip "browser-source-$PACKAGE_VERSION.zip" + mv dist-chrome.zip "dist-chrome-$PACKAGE_VERSION.zip" + mv dist-opera.zip "dist-opera-$PACKAGE_VERSION.zip" + mv dist-firefox.zip "dist-firefox-$PACKAGE_VERSION.zip" + mv dist-edge.zip "dist-edge-$PACKAGE_VERSION.zip" - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 2d7be2e186e..918f81e2723 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -30,6 +30,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index bfd6115a1a9..a97d72a32b0 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -32,6 +32,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -55,18 +57,20 @@ jobs: - name: Get Version Channel id: release_channel + env: + _VERSION: ${{ steps.version.outputs.version }} run: | - case "${{ steps.version.outputs.version }}" in + case "$_VERSION" in *"alpha"*) - echo "channel=alpha" >> $GITHUB_OUTPUT + echo "channel=alpha" >> "$GITHUB_OUTPUT" echo "[!] We do not yet support 'alpha'" exit 1 ;; *"beta"*) - echo "channel=beta" >> $GITHUB_OUTPUT + echo "channel=beta" >> "$GITHUB_OUTPUT" ;; *) - echo "channel=latest" >> $GITHUB_OUTPUT + echo "channel=latest" >> "$GITHUB_OUTPUT" ;; esac @@ -92,7 +96,7 @@ jobs: env: PKG_VERSION: ${{ steps.version.outputs.version }} working-directory: apps/desktop/artifacts - run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive + run: mv "Bitwarden-$PKG_VERSION-universal.pkg" "Bitwarden-$PKG_VERSION-universal.pkg.archive" - name: Create Release uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 5a3c29d29fc..d616d7adb3f 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -26,6 +26,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -79,9 +81,11 @@ jobs: - name: Rename assets working-directory: apps/web/artifacts + env: + _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} run: | - mv web-*-selfhosted-COMMERCIAL.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip - mv web-*-selfhosted-open-source.zip web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip + mv web-*-selfhosted-COMMERCIAL.zip "web-$_RELEASE_VERSION-selfhosted-COMMERCIAL.zip" + mv web-*-selfhosted-open-source.zip "web-$_RELEASE_VERSION-selfhosted-open-source.zip" - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index ecb8e448a8a..acfda4cdb11 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -57,7 +57,7 @@ jobs: BRANCH="rc" fi - echo "branch=$BRANCH" >> $GITHUB_OUTPUT + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" bump_version: name: Bump Version @@ -108,6 +108,7 @@ jobs: with: ref: main token: ${{ steps.app-token.outputs.token }} + persist-credentials: true - name: Configure Git run: | @@ -124,7 +125,7 @@ jobs: id: current-browser-version run: | CURRENT_VERSION=$(cat package.json | jq -r '.version') - echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" working-directory: apps/browser - name: Browser - Verify input version @@ -140,8 +141,7 @@ jobs: fi # Check if version is newer. - printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V - if [ $? -eq 0 ]; then + if printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V; then echo "Version check successful." else echo "Version check failed." @@ -161,14 +161,14 @@ jobs: id: bump-browser-version-override env: VERSION: ${{ inputs.version_number_override }} - run: npm version --workspace=@bitwarden/browser $VERSION + run: npm version --workspace=@bitwarden/browser "$VERSION" - name: Bump Browser Version - Automatic Calculation if: ${{ inputs.bump_browser == true && inputs.version_number_override == '' }} id: bump-browser-version-automatic env: VERSION: ${{ steps.calculate-next-browser-version.outputs.version }} - run: npm version --workspace=@bitwarden/browser $VERSION + run: npm version --workspace=@bitwarden/browser "$VERSION" - name: Bump Browser Version - Manifest - Version Override if: ${{ inputs.bump_browser == true && inputs.version_number_override != '' }} @@ -211,7 +211,7 @@ jobs: id: current-cli-version run: | CURRENT_VERSION=$(cat package.json | jq -r '.version') - echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" working-directory: apps/cli - name: CLI - Verify input version @@ -227,8 +227,7 @@ jobs: fi # Check if version is newer. - printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V - if [ $? -eq 0 ]; then + if printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V; then echo "Version check successful." else echo "Version check failed." @@ -248,14 +247,14 @@ jobs: id: bump-cli-version-override env: VERSION: ${{ inputs.version_number_override }} - run: npm version --workspace=@bitwarden/cli $VERSION + run: npm version --workspace=@bitwarden/cli "$VERSION" - name: Bump CLI Version - Automatic Calculation if: ${{ inputs.bump_cli == true && inputs.version_number_override == '' }} id: bump-cli-version-automatic env: VERSION: ${{ steps.calculate-next-cli-version.outputs.version }} - run: npm version --workspace=@bitwarden/cli $VERSION + run: npm version --workspace=@bitwarden/cli "$VERSION" ### Desktop - name: Get current Desktop version @@ -263,7 +262,7 @@ jobs: id: current-desktop-version run: | CURRENT_VERSION=$(cat package.json | jq -r '.version') - echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" working-directory: apps/desktop - name: Desktop - Verify input version @@ -279,8 +278,7 @@ jobs: fi # Check if version is newer. - printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V - if [ $? -eq 0 ]; then + if printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V; then echo "Version check successful." else echo "Version check failed." @@ -300,27 +298,27 @@ jobs: id: bump-desktop-version-override env: VERSION: ${{ inputs.version_number_override }} - run: npm version --workspace=@bitwarden/desktop $VERSION + run: npm version --workspace=@bitwarden/desktop "$VERSION" - name: Bump Desktop Version - Root - Automatic Calculation if: ${{ inputs.bump_desktop == true && inputs.version_number_override == '' }} id: bump-desktop-version-automatic env: VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }} - run: npm version --workspace=@bitwarden/desktop $VERSION + run: npm version --workspace=@bitwarden/desktop "$VERSION" - name: Bump Desktop Version - App - Version Override if: ${{ inputs.bump_desktop == true && inputs.version_number_override != '' }} env: VERSION: ${{ inputs.version_number_override }} - run: npm version $VERSION + run: npm version "$VERSION" working-directory: "apps/desktop/src" - name: Bump Desktop Version - App - Automatic Calculation if: ${{ inputs.bump_desktop == true && inputs.version_number_override == '' }} env: VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }} - run: npm version $VERSION + run: npm version "$VERSION" working-directory: "apps/desktop/src" ### Web @@ -329,7 +327,7 @@ jobs: id: current-web-version run: | CURRENT_VERSION=$(cat package.json | jq -r '.version') - echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" working-directory: apps/web - name: Web - Verify input version @@ -345,8 +343,7 @@ jobs: fi # Check if version is newer. - printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V - if [ $? -eq 0 ]; then + if printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V; then echo "Version check successful." else echo "Version check failed." @@ -366,14 +363,14 @@ jobs: id: bump-web-version-override env: VERSION: ${{ inputs.version_number_override }} - run: npm version --workspace=@bitwarden/web-vault $VERSION + run: npm version --workspace=@bitwarden/web-vault "$VERSION" - name: Bump Web Version - Automatic Calculation if: ${{ inputs.bump_web == true && inputs.version_number_override == '' }} id: bump-web-version-automatic env: VERSION: ${{ steps.calculate-next-web-version.outputs.version }} - run: npm version --workspace=@bitwarden/web-vault $VERSION + run: npm version --workspace=@bitwarden/web-vault "$VERSION" ######################## @@ -381,38 +378,50 @@ jobs: id: set-final-version-output env: VERSION: ${{ inputs.version_number_override }} + _BUMP_BROWSER_VERSION_OVERRIDE_OUTCOME: ${{ steps.bump-browser-version-override.outcome }} + _BUMP_BROWSER_VERSION_AUTOMATIC_OUTCOME: ${{ steps.bump-browser-version-automatic.outcome }} + _CALCULATE_NEXT_BROWSER_VERSION: ${{ steps.calculate-next-browser-version.outputs.version }} + _BUMP_CLI_VERSION_OVERRIDE_OUTCOME: ${{ steps.bump-cli-version-override.outcome }} + _BUMP_CLI_VERSION_AUTOMATIC_OUTCOME: ${{ steps.bump-cli-version-automatic.outcome }} + _CALCULATE_NEXT_CLI_VERSION: ${{ steps.calculate-next-cli-version.outputs.version }} + _BUMP_DESKTOP_VERSION_OVERRIDE_OUTCOME: ${{ steps.bump-desktop-version-override.outcome }} + _BUMP_DESKTOP_VERSION_AUTOMATIC_OUTCOME: ${{ steps.bump-desktop-version-automatic.outcome }} + _CALCULATE_NEXT_DESKTOP_VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }} + _BUMP_WEB_VERSION_OVERRIDE_OUTCOME: ${{ steps.bump-web-version-override.outcome }} + _BUMP_WEB_VERSION_AUTOMATIC_OUTCOME: ${{ steps.bump-web-version-automatic.outcome }} + _CALCULATE_NEXT_WEB_VERSION: ${{ steps.calculate-next-web-version.outputs.version }} run: | - if [[ "${{ steps.bump-browser-version-override.outcome }}" = "success" ]]; then - echo "version_browser=$VERSION" >> $GITHUB_OUTPUT - elif [[ "${{ steps.bump-browser-version-automatic.outcome }}" = "success" ]]; then - echo "version_browser=${{ steps.calculate-next-browser-version.outputs.version }}" >> $GITHUB_OUTPUT + if [[ "$_BUMP_BROWSER_VERSION_OVERRIDE_OUTCOME" = "success" ]]; then + echo "version_browser=$VERSION" >> "$GITHUB_OUTPUT" + elif [[ "$_BUMP_BROWSER_VERSION_AUTOMATIC_OUTCOME" = "success" ]]; then + echo "version_browser=$_CALCULATE_NEXT_BROWSER_VERSION" >> "$GITHUB_OUTPUT" fi - if [[ "${{ steps.bump-cli-version-override.outcome }}" = "success" ]]; then - echo "version_cli=$VERSION" >> $GITHUB_OUTPUT - elif [[ "${{ steps.bump-cli-version-automatic.outcome }}" = "success" ]]; then - echo "version_cli=${{ steps.calculate-next-cli-version.outputs.version }}" >> $GITHUB_OUTPUT + if [[ "$_BUMP_CLI_VERSION_OVERRIDE_OUTCOME" = "success" ]]; then + echo "version_cli=$VERSION" >> "$GITHUB_OUTPUT" + elif [[ "$_BUMP_CLI_VERSION_AUTOMATIC_OUTCOME" = "success" ]]; then + echo "version_cli=$_CALCULATE_NEXT_CLI_VERSION" >> "$GITHUB_OUTPUT" fi - if [[ "${{ steps.bump-desktop-version-override.outcome }}" = "success" ]]; then - echo "version_desktop=$VERSION" >> $GITHUB_OUTPUT - elif [[ "${{ steps.bump-desktop-version-automatic.outcome }}" = "success" ]]; then - echo "version_desktop=${{ steps.calculate-next-desktop-version.outputs.version }}" >> $GITHUB_OUTPUT + if [[ "$_BUMP_DESKTOP_VERSION_OVERRIDE_OUTCOME" = "success" ]]; then + echo "version_desktop=$VERSION" >> "$GITHUB_OUTPUT" + elif [[ "$_BUMP_DESKTOP_VERSION_AUTOMATIC_OUTCOME" = "success" ]]; then + echo "version_desktop=$_CALCULATE_NEXT_DESKTOP_VERSION" >> "$GITHUB_OUTPUT" fi - if [[ "${{ steps.bump-web-version-override.outcome }}" = "success" ]]; then - echo "version_web=$VERSION" >> $GITHUB_OUTPUT - elif [[ "${{ steps.bump-web-version-automatic.outcome }}" = "success" ]]; then - echo "version_web=${{ steps.calculate-next-web-version.outputs.version }}" >> $GITHUB_OUTPUT + if [[ "$_BUMP_WEB_VERSION_OVERRIDE_OUTCOME" = "success" ]]; then + echo "version_web=$VERSION" >> "$GITHUB_OUTPUT" + elif [[ "$_BUMP_WEB_VERSION_AUTOMATIC_OUTCOME" = "success" ]]; then + echo "version_web=$_CALCULATE_NEXT_WEB_VERSION" >> "$GITHUB_OUTPUT" fi - name: Check if version changed id: version-changed run: | if [ -n "$(git status --porcelain)" ]; then - echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT + echo "changes_to_commit=TRUE" >> "$GITHUB_OUTPUT" else - echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT + echo "changes_to_commit=FALSE" >> "$GITHUB_OUTPUT" echo "No changes to commit!"; fi @@ -464,13 +473,14 @@ jobs: with: ref: ${{ inputs.target_ref }} token: ${{ steps.app-token.outputs.token }} + persist-credentials: true - name: Check if ${{ needs.setup.outputs.branch }} branch exists env: BRANCH_NAME: ${{ needs.setup.outputs.branch }} run: | - if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then - echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY + if [[ $(git ls-remote --heads origin "$BRANCH_NAME") ]]; then + echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> "$GITHUB_STEP_SUMMARY" exit 1 fi @@ -478,5 +488,5 @@ jobs: env: BRANCH_NAME: ${{ needs.setup.outputs.branch }} run: | - git switch --quiet --create $BRANCH_NAME - git push --quiet --set-upstream origin $BRANCH_NAME + git switch --quiet --create "$BRANCH_NAME" + git push --quiet --set-upstream origin "$BRANCH_NAME" diff --git a/.github/workflows/retrieve-current-desktop-rollout.yml b/.github/workflows/retrieve-current-desktop-rollout.yml index c45453ed9d0..30aef41e649 100644 --- a/.github/workflows/retrieve-current-desktop-rollout.yml +++ b/.github/workflows/retrieve-current-desktop-rollout.yml @@ -39,10 +39,10 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }} AWS_DEFAULT_REGION: 'us-west-2' AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }} - run: aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . --quiet + run: aws s3 cp "$AWS_S3_BUCKET_NAME/desktop/latest.yml" . --quiet - name: Get current rollout percentage run: | CURRENT_PCT=$(sed -r -n "s/stagingPercentage:\s([0-9]+)/\1/p" latest.yml) CURRENT_VERSION=$(sed -r -n "s/version:\s(.*)/\1/p" latest.yml) - echo "Desktop ${CURRENT_VERSION} rollout percentage is ${CURRENT_PCT}%" >> $GITHUB_STEP_SUMMARY + echo "Desktop ${CURRENT_VERSION} rollout percentage is ${CURRENT_PCT}%" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/staged-rollout-desktop.yml b/.github/workflows/staged-rollout-desktop.yml index 4adf81100bd..3d4f0376b39 100644 --- a/.github/workflows/staged-rollout-desktop.yml +++ b/.github/workflows/staged-rollout-desktop.yml @@ -47,11 +47,11 @@ jobs: AWS_DEFAULT_REGION: 'us-west-2' AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }} run: | - aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest.yml . \ + aws s3 cp "$AWS_S3_BUCKET_NAME/desktop/latest.yml" . \ --quiet - aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-linux.yml . \ + aws s3 cp "$AWS_S3_BUCKET_NAME/desktop/latest-linux.yml" . \ --quiet - aws s3 cp $AWS_S3_BUCKET_NAME/desktop/latest-mac.yml . \ + aws s3 cp "$AWS_S3_BUCKET_NAME/desktop/latest-mac.yml" . \ --quiet - name: Check new rollout percentage @@ -86,11 +86,11 @@ jobs: AWS_DEFAULT_REGION: 'us-west-2' AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-electron-bucket-name }} run: | - aws s3 cp latest.yml $AWS_S3_BUCKET_NAME/desktop/ \ + aws s3 cp latest.yml "$AWS_S3_BUCKET_NAME/desktop/" \ --acl "public-read" - aws s3 cp latest-linux.yml $AWS_S3_BUCKET_NAME/desktop/ \ + aws s3 cp latest-linux.yml "$AWS_S3_BUCKET_NAME/desktop/" \ --acl "public-read" - aws s3 cp latest-mac.yml $AWS_S3_BUCKET_NAME/desktop/ \ + aws s3 cp latest-mac.yml "$AWS_S3_BUCKET_NAME/desktop/" \ --acl "public-read" diff --git a/.github/workflows/test-browser-interactions.yml b/.github/workflows/test-browser-interactions.yml index 3af1a1a8e9d..a05f506d63f 100644 --- a/.github/workflows/test-browser-interactions.yml +++ b/.github/workflows/test-browser-interactions.yml @@ -21,6 +21,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 + persist-credentials: false - name: Check for job requirements if: ${{ !github.event.workflow_run.pull_requests || !github.event.workflow_run.head_branch }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 680bfb87cfe..cf62df3180f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,13 +25,15 @@ jobs: steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Get Node Version id: retrieve-node-version run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} - echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Set up Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 @@ -102,6 +104,8 @@ jobs: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Build working-directory: ./apps/desktop/desktop_native @@ -134,6 +138,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Install rust uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable @@ -168,6 +174,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Download jest coverage uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index 3cb5646886a..0f7f2c9f46d 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -42,6 +42,7 @@ jobs: with: ref: main token: ${{ steps.app-token.outputs.token }} + persist-credentials: true - name: Configure Git run: | @@ -52,7 +53,7 @@ jobs: id: current-desktop-version run: | CURRENT_VERSION=$(cat package.json | jq -r '.version') - echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" working-directory: apps/desktop - name: Calculate next Desktop release version @@ -65,12 +66,12 @@ jobs: id: bump-desktop-version-automatic env: VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }} - run: npm version --workspace=@bitwarden/desktop $VERSION + run: npm version --workspace=@bitwarden/desktop "$VERSION" - name: Bump Desktop Version - App - Automatic Calculation env: VERSION: ${{ steps.calculate-next-desktop-version.outputs.version }} - run: npm version $VERSION + run: npm version "$VERSION" working-directory: "apps/desktop/src" - name: Commit files