diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 842fa6b671b..70ce08f0ac2 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -191,6 +191,108 @@ "dependencyDashboardApproval": true }, + // Keep `@types/*` aligned with the runtime major they describe. Type defs + // for a different major than what actually runs are silently wrong at best + // (e.g. @types/express 5 vs Express 4) and build-breaking at worst + // (@types/react 19 fails the @tryghost/shade build under React 18) — this + // is what stalled the grouped "Update Types packages (major)" PR (#28307). + // Two mechanisms, with the major-dashboard-approval rule above as the human + // backstop for any types-ahead major that still slips through: + // + // 1. @types/node has no `node` npm package to pair with — it tracks the + // Node.js runtime declared in `engines`, not a dependency — so a hard + // version cap is the only lever. Raise it when we bump the Node engine. + { + "description": "Cap @types/node at the installed Node major (engines: ^22.13.1)", + "matchPackageNames": [ + "@types/node" + ], + "allowedVersions": "<23" + }, + + // 2. Every other @types/* is grouped WITH the runtime package it describes + // (rules below) so a runtime major and its type-definition major travel + // in the same PR and land together, instead of the types racing ahead. + // This is self-maintaining — no per-package caps to hand-raise on each + // upgrade (a stale cap silently flips from guard to blocker once the + // runtime moves, as happened with supertest 6 -> 7). + // + // Any @types/* with NO managed runtime peer is left as its own individual + // PR rather than lumped into one mega "Types packages (major)" PR (so one + // breaking type can't block the rest). Ordering matters: this ungroup + // default comes BEFORE the pairings so the pairings (later) win for paired + // packages, and the pairings come BEFORE the react17/eslint9/tailwind3 + // catalog rules below so those version-lane groups still win for their own + // depTypes (e.g. react@17 in the react17 catalog stays in its own PR). + { + "description": "Unpaired @types/* majors get their own PR, not one mega Types group", + "matchPackageNames": [ + "@types/**" + ], + "matchUpdateTypes": [ + "major" + ], + "groupName": null + }, + { + "description": "React runtime + its type defs bump together", + "groupName": "React", + "matchPackageNames": [ + "react", + "react-dom", + "@types/react", + "@types/react-dom" + ] + }, + { + "description": "Express runtime + its type defs bump together", + "groupName": "Express", + "matchPackageNames": [ + "express", + "@types/express" + ] + }, + { + "description": "jest runtime + its type defs bump together", + "groupName": "jest", + "matchPackageNames": [ + "jest", + "@types/jest" + ] + }, + { + "description": "supertest runtime + its type defs bump together", + "groupName": "supertest", + "matchPackageNames": [ + "supertest", + "@types/supertest" + ] + }, + { + "description": "dockerode runtime + its type defs bump together", + "groupName": "dockerode", + "matchPackageNames": [ + "dockerode", + "@types/dockerode" + ] + }, + { + "description": "nodemailer runtime + its type defs bump together", + "groupName": "nodemailer", + "matchPackageNames": [ + "nodemailer", + "@types/nodemailer" + ] + }, + { + "description": "sinon runtime + its type defs bump together", + "groupName": "sinon", + "matchPackageNames": [ + "sinon", + "@types/sinon" + ] + }, + // Group NQL packages separately from other TryGhost packages { "groupName": "NQL packages", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 985b05e32d8..2db8005a084 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: pull-requests: read steps: - name: Checkout current commit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ref: ${{ env.HEAD_COMMIT }} fetch-depth: 0 @@ -241,7 +241,7 @@ jobs: if: github.event_name == 'pull_request' steps: - name: Checkout PR head commit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 @@ -262,7 +262,7 @@ jobs: if: github.event_name == 'pull_request' steps: - name: Checkout PR head commit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 @@ -282,7 +282,7 @@ jobs: if: needs.job_setup.outputs.is_tag == 'true' || needs.job_setup.outputs.affected_projects_str != '' name: Lint steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 1000 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 @@ -322,7 +322,7 @@ jobs: needs.job_setup.outputs.is_tag == 'true' || needs.job_setup.outputs.changed_i18n_apps == 'true' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: @@ -348,7 +348,7 @@ jobs: CI: true COVERAGE: true steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: @@ -387,7 +387,7 @@ jobs: node: ${{ fromJSON(needs.job_setup.outputs.node_test_matrix) }} name: Unit tests (Node ${{ matrix.node }}) steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 1000 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 @@ -493,7 +493,7 @@ jobs: NODE_ENV: ${{ matrix.env.NODE_ENV }} name: Acceptance tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }}) steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 env: @@ -612,7 +612,7 @@ jobs: NODE_ENV: ${{ matrix.env.NODE_ENV }} name: Legacy tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }}) steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: submodules: true - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 @@ -676,7 +676,7 @@ jobs: env: CI: true steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 env: @@ -731,7 +731,7 @@ jobs: ports: - 7181:7181 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install Tinybird CLI run: curl -fsSL https://tinybird.co/install.sh | sh - name: Build project @@ -827,7 +827,7 @@ jobs: packages: write steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: submodules: true @@ -1093,7 +1093,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 @@ -1140,7 +1140,7 @@ jobs: packages: write steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Download public app artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 @@ -1312,7 +1312,7 @@ jobs: shardTotal: 2 steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Setup Docker Registry Mirrors uses: ./.github/actions/setup-docker-registry-mirrors @@ -1409,7 +1409,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 @@ -1493,7 +1493,7 @@ jobs: ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Restore Admin coverage if: contains(needs.job_admin-tests.result, 'success') @@ -1596,7 +1596,7 @@ jobs: package_path: 'apps/admin-toolbar' steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Set up Node.js @@ -1661,7 +1661,7 @@ jobs: cdn_paths: 'https://cdn.jsdelivr.net/ghost/admin-toolbar@~CURRENT_MINOR/umd/admin-toolbar.min.js' steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Set up Node.js @@ -1872,7 +1872,7 @@ jobs: env: GH_TOKEN: ${{ secrets.CANARY_DOCKER_BUILD }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 84d424ba9ea..b67799bd486 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install gh-aw extension uses: github/gh-aw/actions/setup-cli@ce1794953e0ec42adc41b6fca05e02ab49ee21c3 # v0.68.3 with: diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml index 69a1e1ed8e6..f1bc90b6797 100644 --- a/.github/workflows/create-release-branch.yml +++ b/.github/workflows/create-release-branch.yml @@ -21,14 +21,14 @@ jobs: if: github.repository == 'TryGhost/Ghost' runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 if: inputs.base-ref == 'latest' with: ref: main fetch-depth: 0 submodules: true - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 if: inputs.base-ref != 'latest' with: ref: ${{ inputs.base-ref }} diff --git a/.github/workflows/devcontainer-build.yml b/.github/workflows/devcontainer-build.yml index f73849e814e..5f8a5cedf7c 100644 --- a/.github/workflows/devcontainer-build.yml +++ b/.github/workflows/devcontainer-build.yml @@ -41,7 +41,7 @@ jobs: cancel-in-progress: true steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up QEMU uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 diff --git a/.github/workflows/publish-tb-cli.yml b/.github/workflows/publish-tb-cli.yml index 16fa4dc598e..f2971fd990e 100644 --- a/.github/workflows/publish-tb-cli.yml +++ b/.github/workflows/publish-tb-cli.yml @@ -21,7 +21,7 @@ jobs: cancel-in-progress: true steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe041c71dec..df9db888848 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,7 +42,7 @@ jobs: with: ssh-private-key: ${{ secrets.DEPLOY_KEY }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: # Deploy key (via ssh-agent) is used for git push — it bypasses # branch protection and triggers downstream workflows (unlike GITHUB_TOKEN) diff --git a/.github/workflows/translation-review.yml b/.github/workflows/translation-review.yml index 99cf7d0dab7..e83ec9c0a3a 100644 --- a/.github/workflows/translation-review.yml +++ b/.github/workflows/translation-review.yml @@ -53,7 +53,7 @@ jobs: ) steps: - name: Checkout main (trusted ref — never the PR head) - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false ref: main diff --git a/apps/admin-x-design-system/package.json b/apps/admin-x-design-system/package.json index 59c43e18378..f446809cac0 100644 --- a/apps/admin-x-design-system/package.json +++ b/apps/admin-x-design-system/package.json @@ -40,7 +40,7 @@ "@types/validator": "catalog:", "@typescript-eslint/parser": "catalog:", "@vitejs/plugin-react": "catalog:", - "autoprefixer": "10.4.21", + "autoprefixer": "10.5.0", "c8": "catalog:", "chai": "catalog:", "eslint": "catalog:", diff --git a/apps/comments-ui/package.json b/apps/comments-ui/package.json index 6526a6b6d5a..88f929e7143 100644 --- a/apps/comments-ui/package.json +++ b/apps/comments-ui/package.json @@ -68,7 +68,7 @@ "@tryghost/nql": "catalog:", "@vitejs/plugin-react": "catalog:", "@vitest/coverage-v8": "catalog:", - "autoprefixer": "10.4.21", + "autoprefixer": "10.5.0", "bson-objectid": "catalog:", "concurrently": "catalog:", "eslint": "catalog:", diff --git a/apps/portal/package.json b/apps/portal/package.json index 3824692b22c..c08339200d6 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -1,6 +1,6 @@ { "name": "@tryghost/portal", - "version": "2.68.58", + "version": "2.68.59", "license": "MIT", "repository": "https://github.com/TryGhost/Ghost", "author": "Ghost Foundation", diff --git a/apps/portal/src/components/pages/account-plan-page.js b/apps/portal/src/components/pages/account-plan-page.js index 607832aa683..00f2050fc03 100644 --- a/apps/portal/src/components/pages/account-plan-page.js +++ b/apps/portal/src/components/pages/account-plan-page.js @@ -438,6 +438,23 @@ const UpgradePlanSection = ({ ); }; +// Shown when there are no paid plans to display (e.g. a member reaches the +// plans page via a theme button or deep link while the site has no paid tiers). +const NoPlansAvailableMessage = () => { + return ( + + + + {t('Sorry, no paid plans are available.')} + + + + ); +}; + const PlansContainer = ({ plans, selectedPlan, confirmationPlan, confirmationType, showConfirmation = false, pendingOffer, onPlanSelect, onPlanCheckout, onConfirm, onCancelSubscription, @@ -446,6 +463,14 @@ const PlansContainer = ({ const {member} = useContext(AppContext); // Plan upgrade flow for free, complimentary, or gift members. if (!isPaidMember({member}) || isComplimentaryMember({member}) || isGiftMember({member})) { + // No paid plans to choose from. This covers the deep-link / theme-button + // entry point (#/portal/account/plans), which cannot be gated in-app, + // where the body would otherwise render blank under the page header. + if (plans.length === 0) { + return ( + + ); + } return ( { expect(continueBtn).toHaveLength(1); }); + test('shows an informative message when no paid plans are available', () => { + // Reproduces the deep-link / theme-button entry point (#/portal/account/plans) + // on a site with members enabled but no paid plan configured. Without the + // empty state the body renders blank under the "Choose a plan" header. + const siteData = getSiteData({ + paidMembersEnabled: false, + products: getProductsData({numOfProducts: 0}) + }); + const {getByTestId, queryByTestId} = customSetup({site: siteData}); + + const message = getByTestId('no-plans-available-notification-text'); + expect(message).toBeInTheDocument(); + expect(message).toHaveTextContent('Sorry, no paid plans are available.'); + + // The plan selectors should not render in the empty state. + expect(queryByTestId('monthly-switch')).not.toBeInTheDocument(); + expect(queryByTestId('yearly-switch')).not.toBeInTheDocument(); + }); + test('can choose plan and continue', async () => { const siteData = getSiteData({ products: getProductsData({numOfProducts: 1}) diff --git a/apps/signup-form/package.json b/apps/signup-form/package.json index 66e5a3af2f4..f5f836e6e43 100644 --- a/apps/signup-form/package.json +++ b/apps/signup-form/package.json @@ -44,7 +44,7 @@ "@types/react": "catalog:", "@types/react-dom": "catalog:", "@vitejs/plugin-react": "catalog:", - "autoprefixer": "10.4.21", + "autoprefixer": "10.5.0", "concurrently": "catalog:", "eslint": "catalog:", "eslint-plugin-tailwindcss": "catalog:tailwind3", diff --git a/e2e/helpers/environment/service-managers/ghost-manager.ts b/e2e/helpers/environment/service-managers/ghost-manager.ts index c360b9f6096..d51dfe91ffd 100644 --- a/e2e/helpers/environment/service-managers/ghost-manager.ts +++ b/e2e/helpers/environment/service-managers/ghost-manager.ts @@ -21,6 +21,8 @@ import type {GhostConfig} from '@/helpers/playwright/fixture'; const debug = baseDebug('e2e:GhostManager'); type GhostEnvOverrides = GhostConfig | Record; +const READINESS_POLL_INTERVAL_MS = 250; + interface TinybirdConfigFile { workspaceId?: string; adminToken?: string; @@ -194,14 +196,13 @@ export class GhostManager { } /** - * Wait for Ghost container to become healthy. - * Uses Docker's built-in health check mechanism. + * Wait for Ghost to become reachable through the same gateway path used by tests. */ async waitForReady(timeoutMs: number = 120000): Promise { if (!this.ghostContainer) { throw new Error('Ghost container not initialized'); } - await this.waitForHealthy(this.ghostContainer, timeoutMs); + await this.waitForHostReadiness(this.ghostContainer, timeoutMs); } private async buildEnvWithSchedulerUrl( @@ -414,10 +415,7 @@ export class GhostManager { } } - /** - * Wait for a container to become healthy according to Docker's health check. - */ - private async waitForHealthy(container: Container, timeoutMs: number): Promise { + private async waitForHostReadiness(container: Container, timeoutMs: number): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { @@ -425,8 +423,8 @@ export class GhostManager { const health = info.State.Health; const status = health?.Status; - if (status === 'healthy') { - debug('Container is healthy'); + if (info.State.Running && await this.probeHostReadiness()) { + debug('Host readiness probe passed'); return; } @@ -444,13 +442,34 @@ export class GhostManager { // Still starting - wait and check again await new Promise((r) => { - setTimeout(r, 1000); + setTimeout(r, READINESS_POLL_INTERVAL_MS); }); } // Timeout const logs = await container.logs({stdout: true, stderr: true, tail: 100}); logging.error(`Timeout waiting for container. Last logs:\n${logs.toString()}`); - throw new Error('Timeout waiting for Ghost to become healthy'); + throw new Error('Timeout waiting for Ghost to become ready'); + } + + private async probeHostReadiness(): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 500); + + try { + const response = await fetch(`http://localhost:${this.getGatewayPort()}/ghost/api/admin/authentication/setup`, { + method: 'GET', + headers: {Accept: 'application/json'}, + signal: controller.signal + }); + const body = await response.json().catch(() => null) as {setup?: Array<{status?: unknown}>} | null; + + return response.ok && Array.isArray(body?.setup) && typeof body.setup[0]?.status === 'boolean'; + } catch (error) { + debug('Host readiness probe failed:', error); + return false; + } finally { + clearTimeout(timeout); + } } } diff --git a/e2e/helpers/services/members/members-service.ts b/e2e/helpers/services/members/members-service.ts index ec5858c2f21..5afe2272b42 100644 --- a/e2e/helpers/services/members/members-service.ts +++ b/e2e/helpers/services/members/members-service.ts @@ -85,4 +85,11 @@ export class MembersService { } return data.members[0]; } + + async deleteAll(): Promise { + const response = await this.request.delete(`${this.adminEndpoint}/members?all=true`); + if (!response.ok()) { + throw new Error(`Failed to delete members: ${response.status()}`); + } + } } diff --git a/e2e/package.json b/e2e/package.json index 438e207be64..ae809c001cb 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -33,7 +33,7 @@ "@playwright/test": "catalog:", "@tryghost/debug": "catalog:", "@tryghost/logging": "catalog:", - "@types/dockerode": "3.3.47", + "@types/dockerode": "4.0.1", "@types/express": "catalog:", "busboy": "1.6.0", "dockerode": "4.0.12", diff --git a/e2e/tests/admin/billing/force-upgrade.test.ts b/e2e/tests/admin/billing/force-upgrade.test.ts index 3dadad8dd25..e4b3193a589 100644 --- a/e2e/tests/admin/billing/force-upgrade.test.ts +++ b/e2e/tests/admin/billing/force-upgrade.test.ts @@ -1,6 +1,5 @@ import {BillingPage, NAV_ITEMS, SidebarPage} from '@/helpers/pages'; import {expect, test} from '@/helpers/playwright'; -import {usePerTestIsolation} from '@/helpers/playwright/isolation'; const MOCK_BILLING_URL = 'https://billing.mock.test'; @@ -31,8 +30,6 @@ const FORCE_UPGRADE_BMA_HTML = `
+ {t('Sorry, no paid plans are available.')} +