From e6d1e1e5e46bd602113f6282798d1916d1bf9b35 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Wed, 16 Oct 2024 12:33:32 +0200 Subject: [PATCH] v5.1.0 release into main (#21870) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhancement: ui issues and french translations (#21633) * chore: ui issues and french translations * chore: added missing translation * chore: made it prettier * fix: test CI/CD building repo 2 times (#21740) * fix: do not display fill from locales if i18n no enabled (#21737) * fix: do not display fill from locales if i18n no enabled * chore: disable only if there is not i18n enabled * fix: send ids when connecting relations to update u&p user role (#21726) * fix:update role * fix: send entry id when connecting/disconnecting relations * fix: changed empty components wording (#21630) * fix: changed empty components wording * fix: updated selectors * fix: made it prettier * enhancement: add preview panel button (#21776) * chore: add test workflow filters for each test type (#21770) * chore: upgrade mysql2 package to 3.9.8 (#21795) * feat: add mapping property support for documentation plugin's open api generated docs (#21752) * feat: add mapping property support for documentation plugin's open API generated spec Dynamic zones are reflected in the OpenAPI docs generated as an anyOf. We can use a discriminator and mapping to improve the docs. * fix: missing one value enum for __component property * enhancement: extract logic to convert component names to OpenAPI names * chore: run prettier write on clean schema attributes --------- Co-authored-by: Jamie Howard <48524071+jhoward1994@users.noreply.github.com> * chore: fix mysql workflow (#21801) * chore: upgrade koa/router to 12.0.2 (#21802) * Update "upgrade major" rules (#21745) * fix: broken list view pagination (#21800) * fix: broken list view pagination * chore: add list view pagination e2e test * enhancement: add copy preview url button (#21780) * enhancement: add copy preview button * chore: remove useClipboard from upload plugin * Add Upgrade Command (#21754) * Make locale/localizations private for non-localized cts (#21495) * chore: upgrade sendgrid-mail to 8.1.3 (#21797) * v5.1.0 --------- Co-authored-by: Lucas Boilly <45385696+lucasboilly@users.noreply.github.com> Co-authored-by: Boegie19 <34578426+Boegie19@users.noreply.github.com> Co-authored-by: Marc Roig Co-authored-by: Rémi de Juvigny <8087692+remidej@users.noreply.github.com> Co-authored-by: Pedro Cerejo Co-authored-by: Jamie Howard <48524071+jhoward1994@users.noreply.github.com> Co-authored-by: Jean-Sébastien Herbaux Co-authored-by: Rémi de Juvigny --- .github/actions/check-pr-status/package.json | 2 +- .github/filters.yaml | 7 +- .github/workflows/skipped_tests.yml | 115 +++++++++++++----- .github/workflows/tests.yml | 53 ++++---- examples/getstarted/package.json | 2 +- examples/kitchensink/package.json | 2 +- lerna.json | 2 +- packages/admin-test-utils/package.json | 2 +- packages/cli/cloud/package.json | 2 +- packages/cli/create-strapi-app/package.json | 2 +- .../create-strapi-app/src/utils/database.ts | 2 +- packages/cli/create-strapi/package.json | 2 +- packages/core/admin/admin/src/index.ts | 1 + packages/core/admin/package.json | 2 +- .../src/history/components/VersionsList.tsx | 22 +--- .../FormInputs/Component/Initializer.tsx | 2 +- .../components/FormInputs/Relations.tsx | 3 + .../preview/components/PreviewSidePanel.tsx | 71 +++++++++++ .../admin/src/preview/index.ts | 11 +- .../admin/src/preview/services/preview.ts | 22 ++++ .../admin/src/translations/en.json | 6 +- .../admin/src/translations/es.json | 2 +- .../admin/src/translations/fr.json | 14 ++- .../admin/src/translations/ja.json | 2 +- packages/core/content-manager/package.json | 2 +- packages/core/content-releases/package.json | 2 +- .../core/content-type-builder/package.json | 2 +- packages/core/core/package.json | 4 +- packages/core/data-transfer/package.json | 2 +- packages/core/database/package.json | 2 +- packages/core/email/package.json | 2 +- packages/core/permissions/package.json | 2 +- packages/core/review-workflows/package.json | 2 +- packages/core/strapi/package.json | 2 +- packages/core/types/package.json | 4 +- .../src/components/CopyLinkButton/index.jsx | 3 +- .../src/hooks/tests/useClipboard.test.ts | 24 ---- .../upload/admin/src/hooks/useClipboard.ts | 37 ------ packages/core/upload/package.json | 2 +- packages/core/utils/package.json | 2 +- packages/generators/generators/package.json | 2 +- packages/plugins/cloud/package.json | 2 +- packages/plugins/color-picker/package.json | 2 +- packages/plugins/documentation/package.json | 2 +- .../helpers/utils/clean-schema-attributes.ts | 40 +++++- packages/plugins/graphql/package.json | 2 +- .../admin/src/components/CMHeaderActions.tsx | 5 + packages/plugins/i18n/package.json | 2 +- packages/plugins/i18n/server/src/register.ts | 9 +- .../plugins/i18n/server/src/services/index.ts | 2 + .../server/src/services/sanitize/index.ts | 42 +++++++ .../plugins/i18n/server/src/utils/index.ts | 2 + packages/plugins/sentry/package.json | 2 +- .../plugins/users-permissions/package.json | 2 +- .../server/controllers/role.js | 19 ++- .../providers/email-amazon-ses/package.json | 2 +- packages/providers/email-mailgun/package.json | 2 +- .../providers/email-nodemailer/package.json | 2 +- .../providers/email-sendgrid/package.json | 4 +- .../providers/email-sendmail/package.json | 2 +- packages/providers/upload-aws-s3/package.json | 2 +- .../providers/upload-cloudinary/package.json | 2 +- packages/providers/upload-local/package.json | 2 +- packages/utils/api-tests/package.json | 2 +- .../utils/eslint-config-custom/package.json | 2 +- packages/utils/logger/package.json | 2 +- packages/utils/tsconfig/package.json | 2 +- packages/utils/typescript/package.json | 2 +- packages/utils/upgrade/package.json | 2 +- .../utils/upgrade/src/cli/commands/upgrade.ts | 7 ++ packages/utils/upgrade/src/cli/errors.ts | 7 ++ .../utils/upgrade/src/modules/error/utils.ts | 21 ++++ .../upgrade/src/modules/format/formats.ts | 2 +- .../upgrade/src/modules/upgrader/types.ts | 8 ++ .../upgrade/src/modules/upgrader/upgrader.ts | 57 +++++++-- .../modules/version/__tests__/range.test.ts | 28 ++++- .../modules/version/__tests__/semver.test.ts | 1 + .../upgrade/src/modules/version/range.ts | 55 ++++++--- .../upgrade/src/modules/version/types.ts | 3 + .../src/tasks/__tests__/codemods.test.ts | 2 +- .../utils/upgrade/src/tasks/codemods/utils.ts | 2 + .../src/tasks/upgrade/prompts/index.ts | 1 + .../src/tasks/upgrade/prompts/latest.ts | 62 ++++++++++ .../src/tasks/upgrade/requirements/major.ts | 16 ++- .../upgrade/src/tasks/upgrade/upgrade.ts | 61 +++++++--- scripts/front/package.json | 2 +- templates/website/yarn.lock | 16 +-- .../core/content-manager/search.test.api.js | 7 +- .../api/core/strapi/api/sort/sort.test.api.ts | 9 -- .../api/validate/validate-query.test.api.js | 1 - .../tests/content-manager/uniqueness.spec.ts | 13 +- yarn.lock | 76 ++++++------ 92 files changed, 738 insertions(+), 329 deletions(-) create mode 100644 packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx create mode 100644 packages/core/content-manager/admin/src/preview/services/preview.ts delete mode 100644 packages/core/upload/admin/src/hooks/tests/useClipboard.test.ts delete mode 100644 packages/core/upload/admin/src/hooks/useClipboard.ts create mode 100644 packages/plugins/i18n/server/src/services/sanitize/index.ts create mode 100644 packages/utils/upgrade/src/tasks/upgrade/prompts/index.ts create mode 100644 packages/utils/upgrade/src/tasks/upgrade/prompts/latest.ts diff --git a/.github/actions/check-pr-status/package.json b/.github/actions/check-pr-status/package.json index 48fc0633f20..4a303325f42 100644 --- a/.github/actions/check-pr-status/package.json +++ b/.github/actions/check-pr-status/package.json @@ -1,6 +1,6 @@ { "name": "check-pr-status", - "version": "5.0.6", + "version": "5.1.0", "private": true, "license": "MIT", "main": "dist/index.js", diff --git a/.github/filters.yaml b/.github/filters.yaml index 321b1f86b84..b9c85700865 100644 --- a/.github/filters.yaml +++ b/.github/filters.yaml @@ -6,7 +6,6 @@ backend: - 'packages/**/strapi-server.js' - 'packages/{utils,generators,cli,providers}/**' - 'packages/core/*/{lib,bin,ee,src}/**' - - 'tests/api/**' - 'packages/core/database/**' frontend: - '.github/actions/yarn-nm-install/*.yml' @@ -16,3 +15,9 @@ frontend: - 'packages/**/admin/ee/admin/**' - 'packages/**/strapi-admin.js' - 'packages/admin-test-utils/**' +api: + - 'tests/api/**' +e2e: + - 'tests/e2e/**' +cli: + - 'tests/cli/**' diff --git a/.github/workflows/skipped_tests.yml b/.github/workflows/skipped_tests.yml index 62eef267bed..a8292e9abec 100644 --- a/.github/workflows/skipped_tests.yml +++ b/.github/workflows/skipped_tests.yml @@ -16,7 +16,8 @@ jobs: permissions: pull-requests: read outputs: - nonDoc: ${{ steps.filter.outputs.nonDoc }} + backend: ${{ steps.filter.outputs.backend }} + frontend: ${{ steps.filter.outputs.frontend }} steps: - uses: actions/checkout@v4 with: @@ -28,43 +29,57 @@ jobs: filters: .github/filters.yaml pretty: - name: 'pretty (node: 20)' + name: 'pretty (node: ${{ matrix.node }})' runs-on: ubuntu-latest strategy: matrix: - node: [20] + node: [20, 22] steps: - run: echo "Skipped" lint: name: 'lint (node: ${{ matrix.node }})' + needs: [build] runs-on: ubuntu-latest strategy: matrix: - node: [20] + node: [20, 22] + steps: + - run: echo "Skipped" + + build: + name: 'build (node: ${{ matrix.node }})' + needs: [changes] + runs-on: ubuntu-latest + strategy: + matrix: + node: [20, 22] steps: - run: echo "Skipped" typescript: + name: 'typescript (node: ${{ matrix.node }})' + needs: [changes, build] runs-on: ubuntu-latest - needs: [build] - name: 'typescript' + strategy: + matrix: + node: [20, 22] steps: - run: echo "Skipped" unit_back: name: 'unit_back (node: ${{ matrix.node }})' - needs: [lint] + needs: [changes, build] runs-on: ubuntu-latest strategy: matrix: - node: [18, 20] + node: [20, 22] steps: - run: echo "Skipped" unit_front: name: 'unit_front (node: ${{ matrix.node }})' - needs: [lint] + needs: [changes, build] runs-on: ubuntu-latest strategy: matrix: @@ -72,66 +87,100 @@ jobs: steps: - run: echo "Skipped" - build: - name: 'build (node: ${{ matrix.node }})' - needs: [changes, lint, unit_front] + e2e_ce: + name: '[CE] e2e (browser: ${{ matrix.project }}) (shard: ${{ matrix.shard }})' + needs: [changes, build] runs-on: ubuntu-latest strategy: matrix: - node: [18, 20] + project: ['chromium', 'webkit', 'firefox'] + shard: [1/2, 2/2] + steps: + - run: echo "Skipped" + + e2e_ee: + name: '[EE] e2e (browser: ${{ matrix.project }}) (shard: ${{ matrix.shard }})' + needs: [changes, build] + runs-on: ubuntu-latest + strategy: + matrix: + project: ['chromium', 'webkit', 'firefox'] + shard: [1/2, 2/2] steps: - run: echo "Skipped" cli: - name: 'CLI Tests' + name: 'CLI Tests (node: ${{ matrix.node }})' needs: [changes, build] runs-on: ubuntu-latest + strategy: + matrix: + node: [20, 22] steps: - run: echo "Skipped" - e2e_ce: + api_ce_pg: + name: '[CE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' + needs: [changes, build] runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] - name: '[CE] e2e (browser: ${{ matrix.project }})' strategy: matrix: - project: ['chromium', 'webkit', 'firefox'] + node: [20, 22] + shard: [1/5, 2/5, 3/5, 4/5, 5/5] steps: - run: echo "Skipped" - e2e_ee: + api_ce_mysql: + name: '[CE] API Integration (mysql:latest, package: mysql2, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' + needs: [changes, build] runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] - name: '[EE] e2e (browser: ${{ matrix.project }})' strategy: matrix: - project: ['chromium', 'webkit', 'firefox'] + node: [20, 22] + shard: [1/5, 2/5, 3/5, 4/5, 5/5] steps: - run: echo "Skipped" - api_ce_pg: + api_ce_sqlite: + name: '[CE] API Integration (sqlite, package: better-sqlite3, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' + needs: [changes, build] runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] - name: '[CE] API Integration (postgres, node: ${{ matrix.node }})' strategy: matrix: - node: [18, 20] + node: [20, 22] + shard: [1/5, 2/5, 3/5, 4/5, 5/5] steps: - run: echo "Skipped" - api_ce_mysql: + api_ee_pg: + name: '[EE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' + needs: [changes, build] runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] - name: '[CE] API Integration (mysql, node: ${{ matrix.node }})' strategy: matrix: - node: [18, 20] + node: [20, 22] + shard: [1/5, 2/5, 3/5, 4/5, 5/5] steps: - run: echo "Skipped" - api_ce_sqlite: + api_ee_mysql: + name: '[EE] API Integration (mysql:latest, package: mysql2, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' + needs: [changes, build] + runs-on: ubuntu-latest + strategy: + matrix: + node: [20, 22] + shard: [1/5, 2/5, 3/5, 4/5, 5/5] + steps: + - run: echo "Skipped" + + api_ee_sqlite: + name: '[EE] API Integration (sqlite, client: better-sqlite3, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' + needs: [changes, build] runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] - name: '[CE] API Integration (sqlite: ${{ matrix.sqlite_pkg }}, node: ${{ matrix.node }})' + strategy: + matrix: + node: [20, 22] + shard: [1/5, 2/5, 3/5, 4/5, 5/5] steps: - run: echo "Skipped" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d4ab88976ca..bf15d1e144b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,6 +27,9 @@ jobs: outputs: backend: ${{ steps.filter.outputs.backend }} frontend: ${{ steps.filter.outputs.frontend }} + api: ${{ steps.filter.outputs.api }} + e2e: ${{ steps.filter.outputs.e2e }} + cli: ${{ steps.filter.outputs.cli }} steps: - uses: actions/checkout@v4 with: @@ -57,6 +60,7 @@ jobs: lint: name: 'lint (node: ${{ matrix.node }})' + needs: [build] runs-on: ubuntu-latest strategy: matrix: @@ -94,7 +98,7 @@ jobs: typescript: name: 'typescript (node: ${{ matrix.node }})' - needs: [changes, lint, pretty, build] + needs: [changes, build] runs-on: ubuntu-latest strategy: matrix: @@ -121,7 +125,7 @@ jobs: unit_back: if: needs.changes.outputs.backend == 'true' name: 'unit_back (node: ${{ matrix.node }})' - needs: [changes, lint, pretty, build] + needs: [changes, build] runs-on: ubuntu-latest env: YARN_ENABLE_IMMUTABLE_INSTALLS: false @@ -144,8 +148,9 @@ jobs: run: yarn nx affected --target=test:unit --nx-ignore-cycles unit_front: + if: needs.changes.outputs.frontend == 'true' name: 'unit_front (node: ${{ matrix.node }})' - needs: [changes, lint, pretty, build] + needs: [changes, build] runs-on: ubuntu-latest strategy: fail-fast: false # remove once tests aren't flaky @@ -167,10 +172,10 @@ jobs: run: yarn nx affected --target=test:front --nx-ignore-cycles -- --runInBand e2e_ce: - if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' + if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' || needs.changes.outputs.e2e == 'true' timeout-minutes: 60 - needs: [changes, lint, pretty, build] - name: '[CE] e2e (browser: ${{ matrix.project }})' + needs: [changes, build] + name: '[CE] e2e (browser: ${{ matrix.project }}) (shard: ${{ matrix.shard }})' runs-on: ubuntu-latest strategy: fail-fast: false # remove once tests aren't flaky @@ -209,10 +214,10 @@ jobs: retention-days: 1 e2e_ee: - if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' + if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' || needs.changes.outputs.e2e == 'true' timeout-minutes: 60 - needs: [changes, lint, pretty, build] - name: '[EE] e2e (browser: ${{ matrix.project }})' + needs: [changes, build] + name: '[EE] e2e (browser: ${{ matrix.project }}) (shard: ${{ matrix.shard }})' runs-on: ubuntu-latest env: STRAPI_LICENSE: ${{ secrets.strapiLicense }} @@ -254,9 +259,9 @@ jobs: retention-days: 1 cli: - if: needs.changes.outputs.backend == 'true' + if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.cli == 'true' timeout-minutes: 60 - needs: [changes, lint, pretty, build] + needs: [changes, build] name: 'CLI Tests (node: ${{ matrix.node }})' runs-on: ubuntu-latest strategy: @@ -281,9 +286,9 @@ jobs: run: yarn test:cli api_ce_pg: - if: needs.changes.outputs.backend == 'true' + if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.api == 'true' runs-on: ubuntu-latest - needs: [changes, lint, pretty, build] + needs: [changes, build] name: '[CE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' strategy: matrix: @@ -321,9 +326,9 @@ jobs: jestOptions: '--shard=${{ matrix.shard }}' api_ce_mysql: - if: needs.changes.outputs.backend == 'true' + if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.api == 'true' runs-on: ubuntu-latest - needs: [changes, lint, pretty, build] + needs: [changes, build] name: '[CE] API Integration (mysql:latest, package: mysql2}, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' strategy: matrix: @@ -356,13 +361,13 @@ jobs: uses: ./.github/actions/run-build - uses: ./.github/actions/run-api-tests with: - dbOptions: '--dbclient=${{ matrix.db_client }} --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi' + dbOptions: '--dbclient=mysql --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi' jestOptions: '--shard=${{ matrix.shard }}' api_ce_sqlite: - if: needs.changes.outputs.backend == 'true' + if: needs.changes.outputs.backend == 'true' || needs.changes.outputs.api == 'true' runs-on: ubuntu-latest - needs: [changes, lint, pretty, build] + needs: [changes, build] name: '[CE] API Integration (sqlite, package: better-sqlite3, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' strategy: matrix: @@ -385,9 +390,9 @@ jobs: # EE api_ee_pg: runs-on: ubuntu-latest - needs: [changes, lint, pretty, build] + needs: [changes, build] name: '[EE] API Integration (postgres, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' - if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') + if: (needs.changes.outputs.backend == 'true' || needs.changes.outputs.api == 'true') && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') env: STRAPI_LICENSE: ${{ secrets.strapiLicense }} strategy: @@ -428,9 +433,9 @@ jobs: api_ee_mysql: runs-on: ubuntu-latest - needs: [changes, lint, pretty, build] + needs: [changes, build] name: '[EE] API Integration (mysql:latest, package: mysql2, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' - if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') + if: (needs.changes.outputs.backend == 'true' || needs.changes.outputs.api == 'true') && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') env: STRAPI_LICENSE: ${{ secrets.strapiLicense }} strategy: @@ -470,9 +475,9 @@ jobs: api_ee_sqlite: runs-on: ubuntu-latest - needs: [changes, lint, pretty, build] + needs: [changes, build] name: '[EE] API Integration (sqlite, client: better-sqlite3, node: ${{ matrix.node }}, shard: ${{ matrix.shard }})' - if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') + if: (needs.changes.outputs.backend == 'true' || needs.changes.outputs.api == 'true') && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') env: STRAPI_LICENSE: ${{ secrets.strapiLicense }} strategy: diff --git a/examples/getstarted/package.json b/examples/getstarted/package.json index 12875406f89..0b78d4b5c56 100644 --- a/examples/getstarted/package.json +++ b/examples/getstarted/package.json @@ -25,7 +25,7 @@ "@strapi/strapi": "workspace:*", "better-sqlite3": "11.3.0", "lodash": "4.17.21", - "mysql2": "3.9.4", + "mysql2": "3.9.8", "passport-google-oauth2": "0.2.0", "pg": "8.11.1", "react": "18.3.1", diff --git a/examples/kitchensink/package.json b/examples/kitchensink/package.json index 3fc47725e8e..fdc36be697c 100644 --- a/examples/kitchensink/package.json +++ b/examples/kitchensink/package.json @@ -19,7 +19,7 @@ "@strapi/strapi": "workspace:*", "better-sqlite3": "11.3.0", "lodash": "4.17.21", - "mysql2": "3.9.4", + "mysql2": "3.9.8", "passport-google-oauth2": "0.2.0", "pg": "8.11.1", "react": "18.3.1", diff --git a/lerna.json b/lerna.json index aa5d30e9982..a77abdc8177 100644 --- a/lerna.json +++ b/lerna.json @@ -1,4 +1,4 @@ { - "version": "5.0.6", + "version": "5.1.0", "npmClient": "yarn" } diff --git a/packages/admin-test-utils/package.json b/packages/admin-test-utils/package.json index 768005a96c7..51db34d3a0d 100644 --- a/packages/admin-test-utils/package.json +++ b/packages/admin-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/admin-test-utils", - "version": "5.0.6", + "version": "5.1.0", "private": true, "description": "Test utilities for the Strapi administration panel", "license": "MIT", diff --git a/packages/cli/cloud/package.json b/packages/cli/cloud/package.json index fd35e19dd42..1b8e440205a 100644 --- a/packages/cli/cloud/package.json +++ b/packages/cli/cloud/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/cloud-cli", - "version": "5.0.6", + "version": "5.1.0", "description": "Commands to interact with the Strapi Cloud", "keywords": [ "strapi", diff --git a/packages/cli/create-strapi-app/package.json b/packages/cli/create-strapi-app/package.json index ceefd3cec8c..7f822dda8e0 100644 --- a/packages/cli/create-strapi-app/package.json +++ b/packages/cli/create-strapi-app/package.json @@ -1,6 +1,6 @@ { "name": "create-strapi-app", - "version": "5.0.6", + "version": "5.1.0", "description": "Generate a new Strapi application.", "keywords": [ "create-strapi-app", diff --git a/packages/cli/create-strapi-app/src/utils/database.ts b/packages/cli/create-strapi-app/src/utils/database.ts index a6e1ab8b8d7..98ff5a31735 100644 --- a/packages/cli/create-strapi-app/src/utils/database.ts +++ b/packages/cli/create-strapi-app/src/utils/database.ts @@ -105,7 +105,7 @@ export async function getDatabaseInfos(options: Options): Promise { } const sqlClientModule = { - mysql: { mysql2: '3.9.4' }, + mysql: { mysql2: '3.9.8' }, postgres: { pg: '8.8.0' }, sqlite: { 'better-sqlite3': '11.3.0' }, }; diff --git a/packages/cli/create-strapi/package.json b/packages/cli/create-strapi/package.json index 943b348f8b4..9abc5aefd2f 100644 --- a/packages/cli/create-strapi/package.json +++ b/packages/cli/create-strapi/package.json @@ -1,6 +1,6 @@ { "name": "create-strapi", - "version": "5.0.6", + "version": "5.1.0", "description": "Generate a new Strapi application.", "keywords": [ "create-strapi", diff --git a/packages/core/admin/admin/src/index.ts b/packages/core/admin/admin/src/index.ts index 2bed2fd48fc..118d29bc708 100644 --- a/packages/core/admin/admin/src/index.ts +++ b/packages/core/admin/admin/src/index.ts @@ -47,6 +47,7 @@ export { useQueryParams } from './hooks/useQueryParams'; export { useFetchClient } from './hooks/useFetchClient'; export { useFocusInputField } from './hooks/useFocusInputField'; export { useRBAC } from './hooks/useRBAC'; +export { useClipboard } from './hooks/useClipboard'; export { useAdminUsers } from './services/users'; /** diff --git a/packages/core/admin/package.json b/packages/core/admin/package.json index 545d31a3e45..c85b3eebcf4 100644 --- a/packages/core/admin/package.json +++ b/packages/core/admin/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/admin", - "version": "5.0.6", + "version": "5.1.0", "description": "Strapi Admin", "repository": { "type": "git", diff --git a/packages/core/content-manager/admin/src/history/components/VersionsList.tsx b/packages/core/content-manager/admin/src/history/components/VersionsList.tsx index 90d9b798879..1e4688da020 100644 --- a/packages/core/content-manager/admin/src/history/components/VersionsList.tsx +++ b/packages/core/content-manager/admin/src/history/components/VersionsList.tsx @@ -17,7 +17,9 @@ import type { HistoryVersions } from '../../../../shared/contracts'; * -----------------------------------------------------------------------------------------------*/ const BlueText = (children: React.ReactNode) => ( - {children} + + {children} + ); /* ------------------------------------------------------------------------------------------------- @@ -89,10 +91,7 @@ const VersionCard = ({ version, isCurrent }: VersionCardProps) => { borderStyle="solid" borderColor={isActive ? 'primary600' : 'neutral200'} color="neutral800" - paddingTop={4} - paddingBottom={4} - paddingLeft={5} - paddingRight={5} + padding={5} tag={Link} to={`?${stringify({ ...query, id: version.id })}`} style={{ textDecoration: 'none' }} @@ -226,16 +225,7 @@ const VersionsList = () => { )} - + {versions.data.map((version, index) => (
  • { ))} {versions.meta.pagination.page < versions.meta.pagination.pageCount && ( - + {formatMessage({ id: 'content-manager.history.sidebar.show-older', diff --git a/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Component/Initializer.tsx b/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Component/Initializer.tsx index 007ec7068e1..04aa4d53503 100644 --- a/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Component/Initializer.tsx +++ b/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Component/Initializer.tsx @@ -40,7 +40,7 @@ const Initializer = ({ disabled, name, onClick }: InitializerProps) => { {formatMessage({ id: getTranslation('components.empty-repeatable'), - defaultMessage: 'No entry yet. Click on the button below to add one.', + defaultMessage: 'No entry yet. Click to add one.', })} diff --git a/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx b/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx index b0a4e789074..d91450ce06e 100644 --- a/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx +++ b/packages/core/content-manager/admin/src/pages/EditView/components/FormInputs/Relations.tsx @@ -87,6 +87,7 @@ function useHandleDisconnect(fieldName: string, consumerName: string) { addFieldRow(`${fieldName}.disconnect`, { id: relation.id, apiData: { + id: relation.id, documentId: relation.documentId, locale: relation.locale, }, @@ -276,6 +277,7 @@ const RelationsField = React.forwardRef( const item = { id: relation.id, apiData: { + id: relation.id, documentId: relation.documentId, locale: relation.locale, }, @@ -743,6 +745,7 @@ const RelationsList = ({ ...relation, ...{ apiData: { + id: relation.id, documentId: relation.documentId, locale: relation.locale, position, diff --git a/packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx b/packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx new file mode 100644 index 00000000000..7897f8dcda6 --- /dev/null +++ b/packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; + +import { useClipboard, useNotification } from '@strapi/admin/strapi-admin'; +import { Button, Flex, IconButton } from '@strapi/design-system'; +import { Link as LinkIcon } from '@strapi/icons'; +import { UID } from '@strapi/types'; +import { useIntl } from 'react-intl'; +import { Link } from 'react-router-dom'; + +import { useGetPreviewUrlQuery } from '../services/preview'; + +import type { PanelComponent } from '@strapi/content-manager/strapi-admin'; + +const PreviewSidePanel: PanelComponent = ({ model, documentId, document }) => { + const { formatMessage } = useIntl(); + const { toggleNotification } = useNotification(); + const { copy } = useClipboard(); + const { data, error } = useGetPreviewUrlQuery({ + params: { + contentType: model as UID.ContentType, + }, + query: { + documentId, + locale: document?.locale, + status: document?.status, + }, + }); + + if (!data?.data?.url || error) { + return null; + } + + const { url } = data.data; + + const handleCopyLink = () => { + copy(url); + toggleNotification({ + message: formatMessage({ + id: 'content-manager.preview.copy.success', + defaultMessage: 'Copied preview link', + }), + type: 'success', + }); + }; + + return { + title: formatMessage({ id: 'content-manager.preview.panel.title', defaultMessage: 'Preview' }), + content: ( + + + + + + + ), + }; +}; + +export { PreviewSidePanel }; diff --git a/packages/core/content-manager/admin/src/preview/index.ts b/packages/core/content-manager/admin/src/preview/index.ts index a8de7baa76d..5562d7ee9da 100644 --- a/packages/core/content-manager/admin/src/preview/index.ts +++ b/packages/core/content-manager/admin/src/preview/index.ts @@ -1,17 +1,22 @@ /* eslint-disable check-file/no-index */ +import { PreviewSidePanel } from './components/PreviewSidePanel'; import { FEATURE_ID } from './constants'; +import type { ContentManagerPlugin } from '../content-manager'; import type { PluginDefinition } from '@strapi/admin/strapi-admin'; const previewAdmin = { bootstrap(app) { // TODO: Add license registry check when it's available if (!window.strapi.future.isEnabled(FEATURE_ID)) { - return {}; + return; } - // eslint-disable-next-line no-console -- TODO remove when we have real functionality - console.log('Bootstrapping preview admin'); + + const contentManagerPluginApis = app.getPlugin('content-manager') + .apis as ContentManagerPlugin['config']['apis']; + + contentManagerPluginApis.addEditViewSidePanel([PreviewSidePanel]); }, } satisfies Partial; diff --git a/packages/core/content-manager/admin/src/preview/services/preview.ts b/packages/core/content-manager/admin/src/preview/services/preview.ts new file mode 100644 index 00000000000..3b91fc135ec --- /dev/null +++ b/packages/core/content-manager/admin/src/preview/services/preview.ts @@ -0,0 +1,22 @@ +import { GetPreviewUrl } from '../../../../shared/contracts/preview'; +import { contentManagerApi } from '../../services/api'; + +const previewApi = contentManagerApi.injectEndpoints({ + endpoints: (builder) => ({ + getPreviewUrl: builder.query({ + query({ query, params }) { + return { + url: `/content-manager/preview/url/${params.contentType}`, + method: 'GET', + config: { + params: query, + }, + }; + }, + }), + }), +}); + +const { useGetPreviewUrlQuery } = previewApi; + +export { useGetPreviewUrlQuery }; diff --git a/packages/core/content-manager/admin/src/translations/en.json b/packages/core/content-manager/admin/src/translations/en.json index e73186f8e04..b3e31340494 100644 --- a/packages/core/content-manager/admin/src/translations/en.json +++ b/packages/core/content-manager/admin/src/translations/en.json @@ -71,7 +71,7 @@ "components.TableEmpty.withFilters": "There are no {contentType} with the applied filters...", "components.TableEmpty.withSearch": "There are no {contentType} corresponding to the search ({search})...", "components.TableEmpty.withoutFilter": "There are no {contentType}...", - "components.empty-repeatable": "No entry yet. Click on the button below to add one.", + "components.empty-repeatable": "No entry yet. Click to add one.", "components.notification.info.maximum-requirement": "You have already reached the maximum number of fields", "components.notification.info.minimum-requirement": "A field has been added to match the minimum requirement", "components.repeatable.reorder.error": "An error occurred while reordering your component's field, please try again", @@ -231,6 +231,10 @@ "popUpWarning.warning.unpublish-question": "Are you sure you don't want to publish it?", "popUpWarning.warning.updateAllSettings": "This will modify all your settings", "popover.display-relations.label": "Display relations", + "preview.panel.title": "Preview", + "preview.panel.button": "Open preview", + "preview.copy.label": "Copy preview link", + "preview.copy.success": "Copied preview link", "relation.add": "Add relation", "relation.disconnect": "Remove", "relation.error-adding-relation": "An error occurred while trying to add the relation.", diff --git a/packages/core/content-manager/admin/src/translations/es.json b/packages/core/content-manager/admin/src/translations/es.json index d6a0cfdc8bd..8ffc598603d 100644 --- a/packages/core/content-manager/admin/src/translations/es.json +++ b/packages/core/content-manager/admin/src/translations/es.json @@ -49,7 +49,7 @@ "components.TableEmpty.withFilters": "No hay {contentType} con los filtros aplicados...", "components.TableEmpty.withSearch": "No hay {contentType} que coincida con la búsqueda ({search})...", "components.TableEmpty.withoutFilter": "No hay {contentType}...", - "components.empty-repeatable": "Aún no hay entrada. Haga clic en el botón de abajo para agregar uno.", + "components.empty-repeatable": "Aún no hay entrada. Haga clic para agregar una.", "components.notification.info.maximum-requirement": "Ya has alcanzado el número máximo de campos", "components.notification.info.minimum-requirement": "Se ha agregado un campo para cumplir con el requisito mínimo", "components.repeatable.reorder.error": "Se produjo un error al reordenar el campo de su componente. Vuelva a intentarlo.", diff --git a/packages/core/content-manager/admin/src/translations/fr.json b/packages/core/content-manager/admin/src/translations/fr.json index e03d02afe76..ae38ca3fa1a 100644 --- a/packages/core/content-manager/admin/src/translations/fr.json +++ b/packages/core/content-manager/admin/src/translations/fr.json @@ -49,7 +49,7 @@ "components.TableEmpty.withFilters": "Aucun {contentType} n'a été trouvé avec ces filtres...", "components.TableEmpty.withSearch": "Aucun {contentType} n'a été trouvé avec cette recherche ({search})...", "components.TableEmpty.withoutFilter": "Aucun {contentType} n'a été trouvé...", - "components.empty-repeatable": "Il n'a pas encore d'entrée. Cliquez sur le bouton pour en ajouter une.", + "components.empty-repeatable": "Il n'a pas encore d'entrée. Cliquez pour en ajouter une.", "components.notification.info.maximum-requirement": "Le nombre maximal de champs est atteint", "components.notification.info.minimum-requirement": "Un champ a été rajouté pour remplir les conditions minimales", "components.repeatable.reorder.error": "Une erreur s'est produite lors de la réorganisation du champ de votre composant, veuillez réessayer", @@ -192,5 +192,15 @@ "apiError.This attribute must be unique": "Le champ {field} doit être unique", "popUpWarning.warning.publish-question": "Êtes-vous sûr de vouloir le publier ?", "popUpwarning.warning.has-draft-relations.button-confirm": "Oui, publier", - "popUpwarning.warning.has-draft-relations.message": "{count, plural, =0 { des relations de votre contenu n'est} one { des relations de votre contenu n'est} other { des relations de votre contenu ne sont}} pas publié actuellement.

    Cela peut engendrer des liens cassés ou des erreurs dans votre projet." + "popUpwarning.warning.has-draft-relations.message": "{count, plural, =0 { des relations de votre contenu n'est} one { des relations de votre contenu n'est} other { des relations de votre contenu ne sont}} pas publié actuellement.

    Cela peut engendrer des liens cassés ou des erreurs dans votre projet.", + "history.sidebar.show-newer": "Voir les versions récentes", + "history.sidebar.show-older": "Voir les anciennes versions", + "history.content.new-field.message": "Ce champ n'existait pas lorsque cette version a été sauvegardée. Si vous restaurez cette version, il sera vide.", + "history.content.unknown-fields.message": "Ces champs ont été supprimés ou renommés dans le Content-Type Builder. Ces champs ne seront pas restaurés.", + "history.content.no-relations": "Aucune relation.", + "history.restore.confirm.button": "Restaurer", + "history.restore.confirm.title": "Êtes-vous sûr de vouloir restaurer cette version ?", + "history.restore.confirm.message": "{isDraft, select, true {Le contenu restauré écrasera votre brouillon.} other {Le contenu restauré ne sera pas publié, il écrasera le brouillon et sera sauvegardé en tant que changement en attente de publication. Vous pourrez publier les changements à tout moment.}}", + "history.restore.success.title": "Version restaurée.", + "history.restore.success.message": "Le contenu de la version restaurée n'a pas encore été publié." } diff --git a/packages/core/content-manager/admin/src/translations/ja.json b/packages/core/content-manager/admin/src/translations/ja.json index 629d9f87c5b..c9e0c748c4b 100644 --- a/packages/core/content-manager/admin/src/translations/ja.json +++ b/packages/core/content-manager/admin/src/translations/ja.json @@ -49,7 +49,7 @@ "components.TableEmpty.withFilters": "適用されたフィルタには{contentType}はありません...", "components.TableEmpty.withSearch": "検索に対応する{contentType}はありません({search})...", "components.TableEmpty.withoutFilter": "{contentType}はありません...", - "components.empty-repeatable": "No entry yet. Click on the button below to add one.", + "components.empty-repeatable": "No entry yet. Click to add one.", "components.notification.info.maximum-requirement": "You have already reached the maximum number of fields", "components.notification.info.minimum-requirement": "A field has been added to match the minimum requirement", "components.repeatable.reorder.error": "An error occurred while reordering your component's field, please try again", diff --git a/packages/core/content-manager/package.json b/packages/core/content-manager/package.json index dc5511713a1..9e8775eb04b 100644 --- a/packages/core/content-manager/package.json +++ b/packages/core/content-manager/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/content-manager", - "version": "5.0.6", + "version": "5.1.0", "description": "A powerful UI to easily manage your data.", "repository": { "type": "git", diff --git a/packages/core/content-releases/package.json b/packages/core/content-releases/package.json index 9b2d62843e1..047b86ee7c2 100644 --- a/packages/core/content-releases/package.json +++ b/packages/core/content-releases/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/content-releases", - "version": "5.0.6", + "version": "5.1.0", "description": "Strapi plugin for organizing and releasing content", "repository": { "type": "git", diff --git a/packages/core/content-type-builder/package.json b/packages/core/content-type-builder/package.json index 35c51215ed3..dd5b87839d2 100644 --- a/packages/core/content-type-builder/package.json +++ b/packages/core/content-type-builder/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/content-type-builder", - "version": "5.0.6", + "version": "5.1.0", "description": "Create and manage content types", "repository": { "type": "git", diff --git a/packages/core/core/package.json b/packages/core/core/package.json index ed81d57bba9..34cdcbdbdc9 100644 --- a/packages/core/core/package.json +++ b/packages/core/core/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/core", - "version": "5.0.6", + "version": "5.1.0", "description": "Core of Strapi", "homepage": "https://strapi.io", "bugs": { @@ -53,7 +53,7 @@ }, "dependencies": { "@koa/cors": "5.0.0", - "@koa/router": "12.0.1", + "@koa/router": "12.0.2", "@paralleldrive/cuid2": "2.2.2", "@strapi/admin": "workspace:*", "@strapi/database": "workspace:*", diff --git a/packages/core/data-transfer/package.json b/packages/core/data-transfer/package.json index 926eba2102d..cf8bdf81f76 100644 --- a/packages/core/data-transfer/package.json +++ b/packages/core/data-transfer/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/data-transfer", - "version": "5.0.6", + "version": "5.1.0", "description": "Data transfer capabilities for Strapi", "keywords": [ "strapi", diff --git a/packages/core/database/package.json b/packages/core/database/package.json index 6b9756c7c96..5581a613012 100644 --- a/packages/core/database/package.json +++ b/packages/core/database/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/database", - "version": "5.0.6", + "version": "5.1.0", "description": "Strapi's database layer", "homepage": "https://strapi.io", "bugs": { diff --git a/packages/core/email/package.json b/packages/core/email/package.json index 9541e8c4855..9a14ea68c53 100644 --- a/packages/core/email/package.json +++ b/packages/core/email/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/email", - "version": "5.0.6", + "version": "5.1.0", "description": "Easily configure your Strapi application to send emails.", "repository": { "type": "git", diff --git a/packages/core/permissions/package.json b/packages/core/permissions/package.json index 83f2f3a2450..fd99782796c 100644 --- a/packages/core/permissions/package.json +++ b/packages/core/permissions/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/permissions", - "version": "5.0.6", + "version": "5.1.0", "description": "Strapi's permission layer.", "repository": { "type": "git", diff --git a/packages/core/review-workflows/package.json b/packages/core/review-workflows/package.json index fdb94e61bd5..3d5b38bc285 100644 --- a/packages/core/review-workflows/package.json +++ b/packages/core/review-workflows/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/review-workflows", - "version": "5.0.6", + "version": "5.1.0", "description": "Review workflows for your content", "repository": { "type": "git", diff --git a/packages/core/strapi/package.json b/packages/core/strapi/package.json index 97da86254b6..03f59454800 100644 --- a/packages/core/strapi/package.json +++ b/packages/core/strapi/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/strapi", - "version": "5.0.6", + "version": "5.1.0", "description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite", "keywords": [ "strapi", diff --git a/packages/core/types/package.json b/packages/core/types/package.json index bd7bf780260..10add3be7dd 100644 --- a/packages/core/types/package.json +++ b/packages/core/types/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/types", - "version": "5.0.6", + "version": "5.1.0", "description": "Shared typescript types for Strapi internal use", "keywords": [ "strapi" @@ -45,7 +45,7 @@ "dependencies": { "@casl/ability": "6.5.0", "@koa/cors": "5.0.0", - "@koa/router": "12.0.1", + "@koa/router": "12.0.2", "@strapi/database": "workspace:*", "@strapi/logger": "workspace:*", "@strapi/permissions": "workspace:*", diff --git a/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx b/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx index bbeefb820d2..cb886f2e70a 100644 --- a/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx +++ b/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx @@ -1,12 +1,11 @@ import React from 'react'; -import { useNotification } from '@strapi/admin/strapi-admin'; +import { useNotification, useClipboard } from '@strapi/admin/strapi-admin'; import { IconButton } from '@strapi/design-system'; import { Link as LinkIcon } from '@strapi/icons'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; -import { useClipboard } from '../../hooks/useClipboard'; import { getTrad } from '../../utils'; export const CopyLinkButton = ({ url }) => { diff --git a/packages/core/upload/admin/src/hooks/tests/useClipboard.test.ts b/packages/core/upload/admin/src/hooks/tests/useClipboard.test.ts deleted file mode 100644 index 526b7e1f4b3..00000000000 --- a/packages/core/upload/admin/src/hooks/tests/useClipboard.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { renderHook } from '@tests/utils'; - -import { useClipboard } from '../useClipboard'; - -describe('useClipboard', () => { - it('should return false if the value passed to the function is not a string or number', async () => { - const { result } = renderHook(() => useClipboard()); - - // @ts-expect-error testing invalid, empty input to the hook - expect(await result.current.copy({})).toBe(false); - }); - - it('should return false if the value passed to copy is an empty string', async () => { - const { result } = renderHook(() => useClipboard()); - - expect(await result.current.copy('')).toBe(false); - }); - - it('should return true if the copy was successful', async () => { - const { result } = renderHook(() => useClipboard()); - - expect(await result.current.copy('test')).toBe(true); - }); -}); diff --git a/packages/core/upload/admin/src/hooks/useClipboard.ts b/packages/core/upload/admin/src/hooks/useClipboard.ts deleted file mode 100644 index 58f7533a8e1..00000000000 --- a/packages/core/upload/admin/src/hooks/useClipboard.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useCallback } from 'react'; - -const useClipboard = () => { - const copy = useCallback(async (value: string | number) => { - try { - // only strings and numbers casted to strings can be copied to clipboard - if (typeof value !== 'string' && typeof value !== 'number') { - throw new Error( - `Cannot copy typeof ${typeof value} to clipboard, must be a string or number` - ); - } - // empty strings are also considered invalid - else if (value === '') { - throw new Error(`Cannot copy empty string to clipboard.`); - } - - const stringifiedValue = value.toString(); - - await navigator.clipboard.writeText(stringifiedValue); - - return true; - } catch (error) { - /** - * Realistically this isn't useful in production as there's nothing the user can do. - */ - if (process.env.NODE_ENV === 'development') { - console.warn('Copy failed', error); - } - - return false; - } - }, []); - - return { copy }; -}; - -export { useClipboard }; diff --git a/packages/core/upload/package.json b/packages/core/upload/package.json index fa501b5b25d..1f193a48037 100644 --- a/packages/core/upload/package.json +++ b/packages/core/upload/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/upload", - "version": "5.0.6", + "version": "5.1.0", "description": "Makes it easy to upload images and files to your Strapi Application.", "license": "SEE LICENSE IN LICENSE", "author": { diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index 7f132ac22f2..30bd541adb4 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/utils", - "version": "5.0.6", + "version": "5.1.0", "description": "Shared utilities for the Strapi packages", "keywords": [ "strapi", diff --git a/packages/generators/generators/package.json b/packages/generators/generators/package.json index d76cf7ad0de..4a5420a59f9 100644 --- a/packages/generators/generators/package.json +++ b/packages/generators/generators/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/generators", - "version": "5.0.6", + "version": "5.1.0", "description": "Interactive API generator.", "keywords": [ "strapi", diff --git a/packages/plugins/cloud/package.json b/packages/plugins/cloud/package.json index da11fc041c2..b99b0609981 100644 --- a/packages/plugins/cloud/package.json +++ b/packages/plugins/cloud/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/plugin-cloud", - "version": "5.0.6", + "version": "5.1.0", "description": "Instructions to deploy your local project to Strapi Cloud", "license": "MIT", "author": { diff --git a/packages/plugins/color-picker/package.json b/packages/plugins/color-picker/package.json index bef11cbad6d..f4c7b1f63e5 100644 --- a/packages/plugins/color-picker/package.json +++ b/packages/plugins/color-picker/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/plugin-color-picker", - "version": "5.0.6", + "version": "5.1.0", "description": "Strapi maintained Custom Fields", "repository": { "type": "git", diff --git a/packages/plugins/documentation/package.json b/packages/plugins/documentation/package.json index 271cb63c403..a4c5bdef0ca 100644 --- a/packages/plugins/documentation/package.json +++ b/packages/plugins/documentation/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/plugin-documentation", - "version": "5.0.6", + "version": "5.1.0", "description": "Create an OpenAPI Document and visualize your API with SWAGGER UI.", "repository": { "type": "git", diff --git a/packages/plugins/documentation/server/src/services/helpers/utils/clean-schema-attributes.ts b/packages/plugins/documentation/server/src/services/helpers/utils/clean-schema-attributes.ts index ad2f30f8011..190295a9543 100644 --- a/packages/plugins/documentation/server/src/services/helpers/utils/clean-schema-attributes.ts +++ b/packages/plugins/documentation/server/src/services/helpers/utils/clean-schema-attributes.ts @@ -10,6 +10,20 @@ interface Options { didAddStrapiComponentsToSchemas: (name: string, schema: object) => boolean; } +/** + * @description - Convert attribute component names to OpenAPI component names + * + * @returns OpenAPI component name + */ +const convertComponentName = (component: string, isRef = false): string => { + const cleanComponentName = `${pascalCase(component)}Component`; + + if (isRef) { + return `#/components/schemas/${cleanComponentName}`; + } + return cleanComponentName; +}; + /** * @description - Converts types found on attributes to OpenAPI acceptable data types * @@ -104,11 +118,11 @@ const cleanSchemaAttributes = ( }; const refComponentSchema: OpenAPIV3.ReferenceObject = { - $ref: `#/components/schemas/${pascalCase(attribute.component)}Component`, + $ref: convertComponentName(attribute.component, true), }; const componentExists = didAddStrapiComponentsToSchemas( - `${pascalCase(attribute.component)}Component`, + convertComponentName(attribute.component), rawComponentSchema ); @@ -130,7 +144,7 @@ const cleanSchemaAttributes = ( type: 'object', properties: { ...(isRequest ? {} : { id: { type: 'number' } }), - __component: { type: 'string' }, + __component: { type: 'string', enum: [component] }, ...cleanSchemaAttributes(componentAttributes, { typeMap, isRequest, @@ -140,22 +154,38 @@ const cleanSchemaAttributes = ( }; const refComponentSchema: OpenAPIV3.ReferenceObject = { - $ref: `#/components/schemas/${pascalCase(component)}Component`, + $ref: convertComponentName(component, true), }; const componentExists = didAddStrapiComponentsToSchemas( - `${pascalCase(component)}Component`, + convertComponentName(component), rawComponentSchema ); const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema; return finalComponentSchema; }); + let discriminator: OpenAPIV3.DiscriminatorObject | undefined; + if (components.every((component) => Object.hasOwn(component, '$ref'))) { + discriminator = { + propertyName: '__component', + mapping: attribute.components.reduce( + (acc, component) => { + acc[component] = convertComponentName(component, true); + return acc; + }, + {} as { + [value: string]: string; + } + ), + }; + } schemaAttributes[prop] = { type: 'array', items: { anyOf: components, }, + discriminator, }; break; } diff --git a/packages/plugins/graphql/package.json b/packages/plugins/graphql/package.json index 8a2c61377b7..4ba289208fe 100644 --- a/packages/plugins/graphql/package.json +++ b/packages/plugins/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/plugin-graphql", - "version": "5.0.6", + "version": "5.1.0", "description": "Adds GraphQL endpoint with default API methods.", "repository": { "type": "git", diff --git a/packages/plugins/i18n/admin/src/components/CMHeaderActions.tsx b/packages/plugins/i18n/admin/src/components/CMHeaderActions.tsx index 5d8ef60b32d..dcd00bc7d0c 100644 --- a/packages/plugins/i18n/admin/src/components/CMHeaderActions.tsx +++ b/packages/plugins/i18n/admin/src/components/CMHeaderActions.tsx @@ -241,6 +241,7 @@ const FillFromAnotherLocaleAction = ({ }: HeaderActionProps) => { const { formatMessage } = useIntl(); const [{ query }] = useQueryParams(); + const { hasI18n } = useI18n(); const currentDesiredLocale = query.plugins?.i18n?.locale; const [localeSelected, setLocaleSelected] = React.useState(null); const setValues = useForm('FillFromAnotherLocale', (state) => state.setValues); @@ -278,6 +279,10 @@ const FillFromAnotherLocaleAction = ({ onClose(); }; + if (!hasI18n) { + return null; + } + return { type: 'icon', icon: , diff --git a/packages/plugins/i18n/package.json b/packages/plugins/i18n/package.json index dd12cf6083d..8311f552092 100644 --- a/packages/plugins/i18n/package.json +++ b/packages/plugins/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/i18n", - "version": "5.0.6", + "version": "5.1.0", "description": "Create read and update content in different languages, both from the Admin Panel and from the API", "repository": { "type": "git", diff --git a/packages/plugins/i18n/server/src/register.ts b/packages/plugins/i18n/server/src/register.ts index 8127d014b1b..9307af29a87 100644 --- a/packages/plugins/i18n/server/src/register.ts +++ b/packages/plugins/i18n/server/src/register.ts @@ -3,6 +3,7 @@ import type { Core } from '@strapi/types'; import validateLocaleCreation from './controllers/validate-locale-creation'; import graphqlProvider from './graphql'; +import { getService } from './utils'; export default ({ strapi }: { strapi: Core.Strapi }) => { extendContentTypes(strapi); @@ -38,12 +39,16 @@ const addContentManagerLocaleMiddleware = (strapi: Core.Strapi) => { * @param {Strapi} strapi */ const extendContentTypes = (strapi: Core.Strapi) => { + const { isLocalizedContentType } = getService('content-types'); + Object.values(strapi.contentTypes).forEach((contentType) => { const { attributes } = contentType; + const isLocalized = isLocalizedContentType(contentType); + _.set(attributes, 'locale', { writable: true, - private: false, + private: !isLocalized, configurable: false, visible: false, type: 'string', @@ -54,7 +59,7 @@ const extendContentTypes = (strapi: Core.Strapi) => { relation: 'oneToMany', target: contentType.uid, writable: false, - private: false, + private: !isLocalized, configurable: false, visible: false, unstable_virtual: true, diff --git a/packages/plugins/i18n/server/src/services/index.ts b/packages/plugins/i18n/server/src/services/index.ts index cf788f9db0f..137e97ade07 100644 --- a/packages/plugins/i18n/server/src/services/index.ts +++ b/packages/plugins/i18n/server/src/services/index.ts @@ -4,12 +4,14 @@ import localizations from './localizations'; import locales from './locales'; import isoLocales from './iso-locales'; import contentTypes from './content-types'; +import sanitize from './sanitize'; export default { permissions, metrics, localizations, locales, + sanitize, 'iso-locales': isoLocales, 'content-types': contentTypes, }; diff --git a/packages/plugins/i18n/server/src/services/sanitize/index.ts b/packages/plugins/i18n/server/src/services/sanitize/index.ts new file mode 100644 index 00000000000..b1dde1e6bfd --- /dev/null +++ b/packages/plugins/i18n/server/src/services/sanitize/index.ts @@ -0,0 +1,42 @@ +import type { Core, Schema, Data } from '@strapi/types'; + +import { traverseEntity } from '@strapi/utils'; +import { curry } from 'lodash/fp'; + +import { getService } from '../../utils'; + +const LOCALIZATION_FIELDS = ['locale', 'localizations']; + +const sanitize = ({ strapi }: { strapi: Core.Strapi }) => { + const { isLocalizedContentType } = getService('content-types'); + + /** + * Sanitizes localization fields of a given entity based on its schema. + * + * Remove localization-related fields that are unnecessary, that is + * for schemas that aren't localized. + */ + const sanitizeLocalizationFields = curry((schema: Schema.Schema, entity: Data.Entity) => + traverseEntity( + ({ key, schema }, { remove }) => { + const isLocalized = isLocalizedContentType(schema); + const isLocalizationField = LOCALIZATION_FIELDS.includes(key); + + if (!isLocalized && isLocalizationField) { + remove(key); + } + }, + { schema, getModel: strapi.getModel.bind(strapi) }, + entity + ) + ); + + return { + sanitizeLocalizationFields, + }; +}; + +type SanitizeService = typeof sanitize; + +export default sanitize; +export type { SanitizeService }; diff --git a/packages/plugins/i18n/server/src/utils/index.ts b/packages/plugins/i18n/server/src/utils/index.ts index 8453304edeb..b8585e263e2 100644 --- a/packages/plugins/i18n/server/src/utils/index.ts +++ b/packages/plugins/i18n/server/src/utils/index.ts @@ -4,6 +4,7 @@ import type { ContentTypesService } from '../services/content-types'; import type { MetricsService } from '../services/metrics'; import type { ISOLocalesService } from '../services/iso-locales'; import type { LocalizationsService } from '../services/localizations'; +import type { SanitizeService } from '../services/sanitize'; type S = { permissions: PermissionsService; @@ -12,6 +13,7 @@ type S = { localizations: LocalizationsService; ['iso-locales']: ISOLocalesService; ['content-types']: ContentTypesService; + sanitize: SanitizeService; }; const getCoreStore = () => { diff --git a/packages/plugins/sentry/package.json b/packages/plugins/sentry/package.json index 476ef44a317..3b7b369e9e4 100644 --- a/packages/plugins/sentry/package.json +++ b/packages/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/plugin-sentry", - "version": "5.0.6", + "version": "5.1.0", "description": "Send Strapi error events to Sentry", "repository": { "type": "git", diff --git a/packages/plugins/users-permissions/package.json b/packages/plugins/users-permissions/package.json index ca3b697254c..4ec69bc4aa8 100644 --- a/packages/plugins/users-permissions/package.json +++ b/packages/plugins/users-permissions/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/plugin-users-permissions", - "version": "5.0.6", + "version": "5.1.0", "description": "Protect your API with a full-authentication process based on JWT", "repository": { "type": "git", diff --git a/packages/plugins/users-permissions/server/controllers/role.js b/packages/plugins/users-permissions/server/controllers/role.js index e6cad7c3786..4e08da139ab 100644 --- a/packages/plugins/users-permissions/server/controllers/role.js +++ b/packages/plugins/users-permissions/server/controllers/role.js @@ -1,10 +1,19 @@ 'use strict'; const _ = require('lodash'); -const { ApplicationError, ValidationError } = require('@strapi/utils').errors; +const { async, errors } = require('@strapi/utils'); const { getService } = require('../utils'); const { validateDeleteRoleBody } = require('./validation/user'); +const { ApplicationError, ValidationError } = errors; + +const sanitizeOutput = async (role) => { + const { sanitizeLocalizationFields } = strapi.plugin('i18n').service('sanitize'); + const schema = strapi.getModel('plugin::users-permissions.role'); + + return async.pipe(sanitizeLocalizationFields(schema))(role); +}; + module.exports = { /** * Default action. @@ -30,13 +39,17 @@ module.exports = { return ctx.notFound(); } - ctx.send({ role }); + const safeRole = await sanitizeOutput(role); + + ctx.send({ role: safeRole }); }, async find(ctx) { const roles = await getService('role').find(); - ctx.send({ roles }); + const safeRoles = await Promise.all(roles.map(sanitizeOutput)); + + ctx.send({ roles: safeRoles }); }, async updateRole(ctx) { diff --git a/packages/providers/email-amazon-ses/package.json b/packages/providers/email-amazon-ses/package.json index 7f30d9c2f17..85d81273b42 100644 --- a/packages/providers/email-amazon-ses/package.json +++ b/packages/providers/email-amazon-ses/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-email-amazon-ses", - "version": "5.0.6", + "version": "5.1.0", "description": "Amazon SES provider for strapi email", "keywords": [ "email", diff --git a/packages/providers/email-mailgun/package.json b/packages/providers/email-mailgun/package.json index f3147ef5923..4594f519f40 100644 --- a/packages/providers/email-mailgun/package.json +++ b/packages/providers/email-mailgun/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-email-mailgun", - "version": "5.0.6", + "version": "5.1.0", "description": "Mailgun provider for strapi email plugin", "keywords": [ "email", diff --git a/packages/providers/email-nodemailer/package.json b/packages/providers/email-nodemailer/package.json index e8a9335342e..ce43f49db21 100644 --- a/packages/providers/email-nodemailer/package.json +++ b/packages/providers/email-nodemailer/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-email-nodemailer", - "version": "5.0.6", + "version": "5.1.0", "description": "Nodemailer provider for Strapi 3", "keywords": [ "strapi", diff --git a/packages/providers/email-sendgrid/package.json b/packages/providers/email-sendgrid/package.json index 3b28da056e8..0833657bc6f 100644 --- a/packages/providers/email-sendgrid/package.json +++ b/packages/providers/email-sendgrid/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-email-sendgrid", - "version": "5.0.6", + "version": "5.1.0", "description": "Sendgrid provider for strapi email", "keywords": [ "email", @@ -42,7 +42,7 @@ "watch": "pack-up watch" }, "dependencies": { - "@sendgrid/mail": "7.7.0", + "@sendgrid/mail": "8.1.3", "@strapi/utils": "workspace:*" }, "devDependencies": { diff --git a/packages/providers/email-sendmail/package.json b/packages/providers/email-sendmail/package.json index 07382fde53a..c6ffbeb2ba1 100644 --- a/packages/providers/email-sendmail/package.json +++ b/packages/providers/email-sendmail/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-email-sendmail", - "version": "5.0.6", + "version": "5.1.0", "description": "Sendmail provider for strapi email", "keywords": [ "email", diff --git a/packages/providers/upload-aws-s3/package.json b/packages/providers/upload-aws-s3/package.json index 8211196011a..7537e8d1d96 100644 --- a/packages/providers/upload-aws-s3/package.json +++ b/packages/providers/upload-aws-s3/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-upload-aws-s3", - "version": "5.0.6", + "version": "5.1.0", "description": "AWS S3 provider for strapi upload", "keywords": [ "upload", diff --git a/packages/providers/upload-cloudinary/package.json b/packages/providers/upload-cloudinary/package.json index 0747e983c67..e8b347f3a3f 100644 --- a/packages/providers/upload-cloudinary/package.json +++ b/packages/providers/upload-cloudinary/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-upload-cloudinary", - "version": "5.0.6", + "version": "5.1.0", "description": "Cloudinary provider for strapi upload", "keywords": [ "upload", diff --git a/packages/providers/upload-local/package.json b/packages/providers/upload-local/package.json index 84e9a170809..05a25b42a1e 100644 --- a/packages/providers/upload-local/package.json +++ b/packages/providers/upload-local/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/provider-upload-local", - "version": "5.0.6", + "version": "5.1.0", "description": "Local provider for strapi upload", "keywords": [ "upload", diff --git a/packages/utils/api-tests/package.json b/packages/utils/api-tests/package.json index 19e1a3d115b..9ac4d826085 100644 --- a/packages/utils/api-tests/package.json +++ b/packages/utils/api-tests/package.json @@ -1,6 +1,6 @@ { "name": "api-tests", - "version": "5.0.6", + "version": "5.1.0", "private": true, "dependencies": { "dotenv": "16.4.5", diff --git a/packages/utils/eslint-config-custom/package.json b/packages/utils/eslint-config-custom/package.json index 63a32debcb1..b579032eead 100644 --- a/packages/utils/eslint-config-custom/package.json +++ b/packages/utils/eslint-config-custom/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-custom", - "version": "5.0.6", + "version": "5.1.0", "private": true, "main": "index.js" } diff --git a/packages/utils/logger/package.json b/packages/utils/logger/package.json index 4808d7af15b..37101a86bd5 100644 --- a/packages/utils/logger/package.json +++ b/packages/utils/logger/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/logger", - "version": "5.0.6", + "version": "5.1.0", "description": "Strapi's logger", "homepage": "https://strapi.io", "bugs": { diff --git a/packages/utils/tsconfig/package.json b/packages/utils/tsconfig/package.json index ef03a1cfc00..914ca68d42a 100644 --- a/packages/utils/tsconfig/package.json +++ b/packages/utils/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "tsconfig", - "version": "5.0.6", + "version": "5.1.0", "private": true, "devDependencies": { "@tsconfig/node18": "18.2.2" diff --git a/packages/utils/typescript/package.json b/packages/utils/typescript/package.json index 7bad2df1f8c..148b7196804 100644 --- a/packages/utils/typescript/package.json +++ b/packages/utils/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/typescript-utils", - "version": "5.0.6", + "version": "5.1.0", "description": "Typescript support for Strapi", "keywords": [ "strapi", diff --git a/packages/utils/upgrade/package.json b/packages/utils/upgrade/package.json index 3dd3dcb0656..29911e7d0a3 100644 --- a/packages/utils/upgrade/package.json +++ b/packages/utils/upgrade/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/upgrade", - "version": "5.0.6", + "version": "5.1.0", "description": "CLI to upgrade Strapi applications effortless", "keywords": [ "strapi", diff --git a/packages/utils/upgrade/src/cli/commands/upgrade.ts b/packages/utils/upgrade/src/cli/commands/upgrade.ts index cd419651fc9..c54c4ae4fbb 100644 --- a/packages/utils/upgrade/src/cli/commands/upgrade.ts +++ b/packages/utils/upgrade/src/cli/commands/upgrade.ts @@ -2,6 +2,7 @@ import prompts from 'prompts'; import { InvalidArgumentError, Option } from 'commander'; import type { Command } from 'commander'; + import { loggerFactory } from '../../modules/logger'; import { Version, isLiteralSemVer, isValidSemVer, semVerFactory } from '../../modules/version'; import { handleError } from '../errors'; @@ -71,6 +72,12 @@ export const register = (program: Command) => { }); }; + // upgrade latest + addReleaseUpgradeCommand( + Version.ReleaseType.Latest, + 'Upgrade to the latest available version of Strapi' + ); + // upgrade major addReleaseUpgradeCommand( Version.ReleaseType.Major, diff --git a/packages/utils/upgrade/src/cli/errors.ts b/packages/utils/upgrade/src/cli/errors.ts index ab503cc892c..4ca6d3fcd5e 100644 --- a/packages/utils/upgrade/src/cli/errors.ts +++ b/packages/utils/upgrade/src/cli/errors.ts @@ -1,6 +1,13 @@ import chalk from 'chalk'; +import { AbortedError } from '../modules/error'; + export const handleError = (err: unknown, isSilent: boolean) => { + // If the upgrade process has been aborted, exit silently + if (err instanceof AbortedError) { + process.exit(0); + } + if (!isSilent) { console.error( chalk.red(`[ERROR]\t[${new Date().toISOString()}]`), diff --git a/packages/utils/upgrade/src/modules/error/utils.ts b/packages/utils/upgrade/src/modules/error/utils.ts index d60dd60a134..ae448a599c7 100644 --- a/packages/utils/upgrade/src/modules/error/utils.ts +++ b/packages/utils/upgrade/src/modules/error/utils.ts @@ -1,9 +1,30 @@ +import type { Version } from '../version'; + export class UnexpectedError extends Error { constructor() { super('Unexpected Error'); } } +export class NPMCandidateNotFoundError extends Error { + target: Version.SemVer | Version.Range | Version.ReleaseType; + + constructor( + target: Version.SemVer | Version.Range | Version.ReleaseType, + message: string = `Couldn't find a valid NPM candidate for "${target}"` + ) { + super(message); + + this.target = target; + } +} + +export class AbortedError extends Error { + constructor(message: string = 'Upgrade aborted') { + super(message); + } +} + export const unknownToError = (e: unknown): Error => { if (e instanceof Error) { return e; diff --git a/packages/utils/upgrade/src/modules/format/formats.ts b/packages/utils/upgrade/src/modules/format/formats.ts index 7f00d2a1d5f..6f4af6657aa 100644 --- a/packages/utils/upgrade/src/modules/format/formats.ts +++ b/packages/utils/upgrade/src/modules/format/formats.ts @@ -78,7 +78,7 @@ export const codemodList = (codemods: Codemod.List) => { const fName = chalk.blue(codemod.format()); const fUID = codemodUID(codemod.uid); - return [fIndex, fVersion, fKind, fName, fUID]; + return [fIndex, fVersion, fKind, fName, fUID] satisfies Row; }); const table = new CliTable3({ diff --git a/packages/utils/upgrade/src/modules/upgrader/types.ts b/packages/utils/upgrade/src/modules/upgrader/types.ts index 3e4854964a2..d5be46ec3c4 100644 --- a/packages/utils/upgrade/src/modules/upgrader/types.ts +++ b/packages/utils/upgrade/src/modules/upgrader/types.ts @@ -1,9 +1,15 @@ +import type { NPM } from '../npm'; +import type { AppProject } from '../project'; import type { Version } from '../version'; import type { Requirement } from '../requirement'; import type { Logger } from '../logger'; import type { ConfirmationCallback } from '../common/types'; export interface Upgrader { + getNPMPackage(): NPM.Package; + getProject(): AppProject; + getTarget(): Version.SemVer; + setTarget(target: Version.SemVer): this; setRequirements(requirements: Requirement.Requirement[]): this; setLogger(logger: Logger): this; @@ -14,6 +20,8 @@ export interface Upgrader { dry(enabled?: boolean): this; onConfirm(callback: ConfirmationCallback | null): this; + confirm(message: string): Promise; + addRequirement(requirement: Requirement.Requirement): this; upgrade(): Promise; diff --git a/packages/utils/upgrade/src/modules/upgrader/upgrader.ts b/packages/utils/upgrade/src/modules/upgrader/upgrader.ts index 27f4219a991..b6440900255 100644 --- a/packages/utils/upgrade/src/modules/upgrader/upgrader.ts +++ b/packages/utils/upgrade/src/modules/upgrader/upgrader.ts @@ -11,7 +11,7 @@ import { rangeFromVersions, semVerFactory, } from '../version'; -import { unknownToError } from '../error'; +import { NPMCandidateNotFoundError, unknownToError } from '../error'; import * as f from '../format'; import { codemodRunnerFactory } from '../codemod-runner'; @@ -57,6 +57,18 @@ export class Upgrader implements UpgraderInterface { this.confirmationCallback = null; } + getNPMPackage(): NPM.Package { + return this.npmPackage; + } + + getProject(): AppProject { + return this.project; + } + + getTarget(): Version.SemVer { + return semVerFactory(this.target.raw); + } + setRequirements(requirements: Requirement.Requirement[]) { this.requirements = requirements; return this; @@ -172,6 +184,14 @@ export class Upgrader implements UpgraderInterface { return successReport(); } + async confirm(message: string): Promise { + if (typeof this.confirmationCallback !== 'function') { + return true; + } + + return this.confirmationCallback(message); + } + private async checkRequirements( requirements: Requirement.Requirement[], context: Requirement.TestContext @@ -280,7 +300,7 @@ export class Upgrader implements UpgraderInterface { this.logger?.debug?.(`Using ${f.highlight(packageManagerName)} as package manager`); if (this.isDry) { - this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic('dry mode')}`); + this.logger?.debug?.(`Skipping dependencies installation (${chalk.italic('dry mode')})`); return; } @@ -292,10 +312,13 @@ export class Upgrader implements UpgraderInterface { private async runCodemods(range: Version.Range): Promise { const codemodRunner = codemodRunnerFactory(this.project, range); + codemodRunner.dry(this.isDry); + if (this.logger) { codemodRunner.setLogger(this.logger); } + await codemodRunner.run(); } } @@ -309,10 +332,16 @@ const resolveNPMTarget = ( project: AppProject, target: Version.ReleaseType | Version.SemVer, npmPackage: NPM.Package -): NPM.NPMPackageVersion | undefined => { +): NPM.NPMPackageVersion => { // Semver if (isSemverInstance(target)) { - return npmPackage.findVersion(target); + const version = npmPackage.findVersion(target); + + if (!version) { + throw new NPMCandidateNotFoundError(target); + } + + return version; } // Release Types @@ -321,10 +350,16 @@ const resolveNPMTarget = ( const npmVersionsMatches = npmPackage.findVersionsInRange(range); // The targeted version is the latest one that matches the given range - return npmVersionsMatches.at(-1); + const version = npmVersionsMatches.at(-1); + + if (!version) { + throw new NPMCandidateNotFoundError(range, `The project is already up-to-date (${target})`); + } + + return version; } - return undefined; + throw new NPMCandidateNotFoundError(target); }; export const upgraderFactory = ( @@ -332,15 +367,11 @@ export const upgraderFactory = ( target: Version.ReleaseType | Version.SemVer, npmPackage: NPM.Package ) => { - const targetedNPMVersion = resolveNPMTarget(project, target, npmPackage); - if (!targetedNPMVersion) { - throw new Error(`Couldn't find a matching version in the NPM registry for "${target}"`); - } - - const semverTarget = semVerFactory(targetedNPMVersion.version); + const npmTarget = resolveNPMTarget(project, target, npmPackage); + const semverTarget = semVerFactory(npmTarget.version); if (semver.eq(semverTarget, project.strapiVersion)) { - throw new Error(`The project is already on ${f.version(semverTarget)}`); + throw new Error(`The project is already using v${semverTarget}`); } return new Upgrader(project, semverTarget, npmPackage); diff --git a/packages/utils/upgrade/src/modules/version/__tests__/range.test.ts b/packages/utils/upgrade/src/modules/version/__tests__/range.test.ts index 25c242312c5..0acdedc0d95 100644 --- a/packages/utils/upgrade/src/modules/version/__tests__/range.test.ts +++ b/packages/utils/upgrade/src/modules/version/__tests__/range.test.ts @@ -19,7 +19,31 @@ describe('Version Utilities', () => { const range = rangeFromReleaseType(currentVersion, Version.ReleaseType.Major); expect(range).toBeInstanceOf(semver.Range); - expect(range.raw).toBe('>1.0.0 <=2.0.0'); + expect(range.raw).toBe('>1.0.0 <=2'); + }); + + it('should create a range for Minor release type', () => { + const currentVersion = new semver.SemVer('1.0.0'); + + const range = rangeFromReleaseType(currentVersion, Version.ReleaseType.Minor); + expect(range).toBeInstanceOf(semver.Range); + expect(range.raw).toBe('>1.0.0 <2.0.0'); + }); + + it('should create a range for Patch release type', () => { + const currentVersion = new semver.SemVer('1.0.0'); + + const range = rangeFromReleaseType(currentVersion, Version.ReleaseType.Patch); + expect(range).toBeInstanceOf(semver.Range); + expect(range.raw).toBe('>1.0.0 <1.1.0'); + }); + + it('should create a range for Latest release type', () => { + const currentVersion = new semver.SemVer('1.0.0'); + + const range = rangeFromReleaseType(currentVersion, Version.ReleaseType.Latest); + expect(range).toBeInstanceOf(semver.Range); + expect(range.raw).toBe('>1.0.0'); }); it('should throw for unsupported release types', () => { @@ -41,7 +65,7 @@ describe('Version Utilities', () => { expect(range.raw).toBe(`>${currentVersion.raw} <=${targetVersion.raw}`); }); - it.each([[Version.ReleaseType.Major, '>1.0.0 <=2.0.0']])( + it.each([[Version.ReleaseType.Major, '>1.0.0 <=2']])( 'should create a range when target is %s', (releaseType, expectedRange) => { const currentVersion = new semver.SemVer('1.0.0'); diff --git a/packages/utils/upgrade/src/modules/version/__tests__/semver.test.ts b/packages/utils/upgrade/src/modules/version/__tests__/semver.test.ts index 9f43cdb97c0..485e2694a1f 100644 --- a/packages/utils/upgrade/src/modules/version/__tests__/semver.test.ts +++ b/packages/utils/upgrade/src/modules/version/__tests__/semver.test.ts @@ -43,6 +43,7 @@ describe('Version Utilities', () => { expect(isSemVerReleaseType(Version.ReleaseType.Major)).toBe(true); expect(isSemVerReleaseType(Version.ReleaseType.Minor)).toBe(true); expect(isSemVerReleaseType(Version.ReleaseType.Patch)).toBe(true); + expect(isSemVerReleaseType(Version.ReleaseType.Latest)).toBe(true); }); it('should return false for invalid release types', () => { diff --git a/packages/utils/upgrade/src/modules/version/range.ts b/packages/utils/upgrade/src/modules/version/range.ts index f56a80ed0e5..889b8236a7a 100644 --- a/packages/utils/upgrade/src/modules/version/range.ts +++ b/packages/utils/upgrade/src/modules/version/range.ts @@ -1,7 +1,7 @@ import semver from 'semver'; import * as Version from './types'; -import { isSemverInstance, isSemVerReleaseType } from './semver'; +import { isSemverInstance, isSemVerReleaseType, semVerFactory } from './semver'; export const rangeFactory = (range: string): Version.Range => { return new semver.Range(range); @@ -9,23 +9,48 @@ export const rangeFactory = (range: string): Version.Range => { export const rangeFromReleaseType = (current: Version.SemVer, identifier: Version.ReleaseType) => { switch (identifier) { - case Version.ReleaseType.Major: { - // semver.inc(_, 'major') will target .0.0 which is what we want - // e.g. for 4.15.4, it'll return 5.0.0 - const nextMajor = semver.inc(current, 'major') as Version.LiteralSemVer; - return rangeFactory(`>${current.raw} <=${nextMajor}`); + case Version.ReleaseType.Latest: { + // Match anything greater than the current version + return rangeFactory(`>${current.raw}`); } - case Version.ReleaseType.Patch: { - // This will return the minor for the next minor - // e.g. for 4.15.4, it'll return 4.16.0 - const minor = semver.inc(current, 'minor') as Version.LiteralSemVer; - return rangeFactory(`>${current.raw} <${minor}`); + case Version.ReleaseType.Major: { + // For example, 4.15.4 returns 5.0.0 + const nextMajor = semVerFactory(current.raw).inc('major'); + + // Using only the major version as the upper limit allows any minor, + // patch, or build version to be taken in the range. + // + // For example, if the current version is "4.15.4", incrementing the + // major version would result in "5.0.0". + // The generated rule is ">4.15.4 <=5", allowing any version + // greater than "4.15.4" but less than "6.0.0-0". + return rangeFactory(`>${current.raw} <=${nextMajor.major}`); } case Version.ReleaseType.Minor: { - // This will return the major for the next major - // e.g. for 4.15.4, it'll return 5.0.0 - const major = semver.inc(current, 'major') as Version.LiteralSemVer; - return rangeFactory(`>${current.raw} <${major}`); + // For example, 4.15.4 returns 5.0.0 + const nextMajor = semVerFactory(current.raw).inc('major'); + + // Using the .. version as the upper limit allows any minor, + // patch, or build versions to be taken in the range. + // + // For example, if the current version is "4.15.4", incrementing the + // major version would result in "5.0.0". + // The generated rule is ">4.15.4 <5.0.0", allowing any version + // greater than "4.15.4" but less than "5.0.0". + return rangeFactory(`>${current.raw} <${nextMajor.raw}`); + } + case Version.ReleaseType.Patch: { + // For example, 4.15.4 returns 4.16.0 + const nextMinor = semVerFactory(current.raw).inc('minor'); + + // Using only the minor version as the upper limit allows any patch + // or build versions to be taken in the range. + // + // For example, if the current version is "4.15.4", incrementing the + // minor version would result in "4.16.0". + // The generated rule is ">4.15.4 <4.16.0", allowing any version + // greater than "4.15.4" but less than "4.16.0". + return rangeFactory(`>${current.raw} <${nextMinor.raw}`); } default: { throw new Error('Not implemented'); diff --git a/packages/utils/upgrade/src/modules/version/types.ts b/packages/utils/upgrade/src/modules/version/types.ts index 82fb9e48f84..d1c27b9d99e 100644 --- a/packages/utils/upgrade/src/modules/version/types.ts +++ b/packages/utils/upgrade/src/modules/version/types.ts @@ -10,7 +10,10 @@ export type LiteralSemVer = `${Version}.${Version}.${Version}`; export type { SemVer, Range } from 'semver'; export enum ReleaseType { + // Classic Major = 'major', Minor = 'minor', Patch = 'patch', + // Other + Latest = 'latest', } diff --git a/packages/utils/upgrade/src/tasks/__tests__/codemods.test.ts b/packages/utils/upgrade/src/tasks/__tests__/codemods.test.ts index 134e67fff51..80ccac66b34 100644 --- a/packages/utils/upgrade/src/tasks/__tests__/codemods.test.ts +++ b/packages/utils/upgrade/src/tasks/__tests__/codemods.test.ts @@ -30,7 +30,7 @@ describe('codemods task', () => { target: Version.ReleaseType.Major, logger, dry: false, - selectCodemods: (options) => Promise.resolve(options), + selectCodemods: (options: T) => Promise.resolve(options), }; (codemodRunnerFactory as jest.Mock).mockReturnValue({ diff --git a/packages/utils/upgrade/src/tasks/codemods/utils.ts b/packages/utils/upgrade/src/tasks/codemods/utils.ts index ea803cd54bf..319d12f084e 100644 --- a/packages/utils/upgrade/src/tasks/codemods/utils.ts +++ b/packages/utils/upgrade/src/tasks/codemods/utils.ts @@ -18,6 +18,8 @@ export const getRangeFromTarget = ( const { major, minor, patch } = currentVersion; switch (target) { + case Version.ReleaseType.Latest: + throw new Error("Can't use to create a codemods range: not implemented"); case Version.ReleaseType.Major: return rangeFactory(`${major}`); case Version.ReleaseType.Minor: diff --git a/packages/utils/upgrade/src/tasks/upgrade/prompts/index.ts b/packages/utils/upgrade/src/tasks/upgrade/prompts/index.ts new file mode 100644 index 00000000000..d6c0d24fc6d --- /dev/null +++ b/packages/utils/upgrade/src/tasks/upgrade/prompts/index.ts @@ -0,0 +1 @@ +export * from './latest'; diff --git a/packages/utils/upgrade/src/tasks/upgrade/prompts/latest.ts b/packages/utils/upgrade/src/tasks/upgrade/prompts/latest.ts new file mode 100644 index 00000000000..2b20e2d6a3e --- /dev/null +++ b/packages/utils/upgrade/src/tasks/upgrade/prompts/latest.ts @@ -0,0 +1,62 @@ +import { AbortedError } from '../../../modules/error'; +import * as f from '../../../modules/format'; + +import { rangeFactory, semVerFactory, Version } from '../../../modules/version'; + +import type { Upgrader } from '../../../modules/upgrader'; +import type { UpgradeOptions } from '../types'; + +/** + * Handles the upgrade prompts when using the latest tag. + * + * - checks if an upgrade involves a major bump, warning and asking for user confirmation before proceeding + */ +export const latest = async (upgrader: Upgrader, options: UpgradeOptions) => { + // Exit if the upgrade target isn't the latest tag + if (options.target !== Version.ReleaseType.Latest) { + return; + } + + // Retrieve utilities from the upgrader instance + const npmPackage = upgrader.getNPMPackage(); + const target = upgrader.getTarget(); + const project = upgrader.getProject(); + + const { strapiVersion: current } = project; + + // Pre-formatted strings used in logs + const fTargetMajor = f.highlight(`v${target.major}`); + const fCurrentMajor = f.highlight(`v${current.major}`); + + const fTarget = f.version(target); + const fCurrent = f.version(current); + + // Flags + const isMajorUpgrade = target.major > current.major; + + // Handle potential major upgrade, warns, and asks for confirmation to proceed + if (isMajorUpgrade) { + options.logger.warn( + `Detected a major upgrade for the "${f.highlight(Version.ReleaseType.Latest)}" tag: ${fCurrent} > ${fTarget}` + ); + + // Find the latest release in between the current one and the next major + const newerPackageRelease = npmPackage + .findVersionsInRange(rangeFactory(`>${current.raw} <${target.major}`)) + .at(-1); + + // If the project isn't on the latest version for the current major, emit a warning + if (newerPackageRelease) { + const fLatest = f.version(semVerFactory(newerPackageRelease.version)); + options.logger.warn( + `It's recommended to first upgrade to the latest version of ${fCurrentMajor} (${fLatest}) before upgrading to ${fTargetMajor}.` + ); + } + + const proceedAnyway = await upgrader.confirm(`I know what I'm doing. Proceed anyway!`); + + if (!proceedAnyway) { + throw new AbortedError(); + } + } +}; diff --git a/packages/utils/upgrade/src/tasks/upgrade/requirements/major.ts b/packages/utils/upgrade/src/tasks/upgrade/requirements/major.ts index 8b379ae03c3..44703e8a349 100644 --- a/packages/utils/upgrade/src/tasks/upgrade/requirements/major.ts +++ b/packages/utils/upgrade/src/tasks/upgrade/requirements/major.ts @@ -1,4 +1,5 @@ import { requirementFactory } from '../../../modules/requirement'; +import { semVerFactory } from '../../../modules/version'; export const REQUIRE_AVAILABLE_NEXT_MAJOR = requirementFactory( 'REQUIRE_AVAILABLE_NEXT_MAJOR', @@ -18,15 +19,18 @@ export const REQUIRE_LATEST_FOR_CURRENT_MAJOR = requirementFactory( 'REQUIRE_LATEST_FOR_CURRENT_MAJOR', (context) => { const { project, target, npmVersionsMatches } = context; + const { major: currentMajor } = project.strapiVersion; - if (npmVersionsMatches.length !== 1) { - const invalidVersions = npmVersionsMatches.slice(0, -1); - const invalidVersionsAsSemVer = invalidVersions.map((v) => v.version); - const nbInvalidVersions = npmVersionsMatches.length; - const currentMajor = project.strapiVersion.major; + const invalidMatches = npmVersionsMatches.filter( + (match) => semVerFactory(match.version).major === currentMajor + ); + + if (invalidMatches.length > 0) { + const invalidVersions = invalidMatches.map((match) => match.version); + const invalidVersionsCount = invalidVersions.length; throw new Error( - `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${nbInvalidVersions} versions between the current one and ${target}: ${invalidVersionsAsSemVer}` + `Doing a major upgrade requires to be on the latest v${currentMajor} version, but found ${invalidVersionsCount} versions between the current one and ${target}. Please upgrade to ${invalidVersions.at(-1)} and try again.` ); } } diff --git a/packages/utils/upgrade/src/tasks/upgrade/upgrade.ts b/packages/utils/upgrade/src/tasks/upgrade/upgrade.ts index 3ef7dbafeb4..c6366e36e4a 100644 --- a/packages/utils/upgrade/src/tasks/upgrade/upgrade.ts +++ b/packages/utils/upgrade/src/tasks/upgrade/upgrade.ts @@ -1,20 +1,23 @@ import path from 'node:path'; -import * as requirements from './requirements'; -import { timerFactory } from '../../modules/timer'; -import { upgraderFactory, constants as upgraderConstants } from '../../modules/upgrader'; -import { npmPackageFactory } from '../../modules/npm'; -import { projectFactory, isApplicationProject } from '../../modules/project'; import * as f from '../../modules/format'; +import { npmPackageFactory } from '../../modules/npm'; +import { isApplicationProject, projectFactory } from '../../modules/project'; +import { timerFactory } from '../../modules/timer'; +import { constants as upgraderConstants, upgraderFactory } from '../../modules/upgrader'; import { Version } from '../../modules/version'; +import * as requirements from './requirements'; +import * as prompts from './prompts'; + import type { UpgradeOptions } from './types'; +import type { Upgrader } from '../../modules/upgrader'; export const upgrade = async (options: UpgradeOptions) => { const timer = timerFactory(); const { logger, codemodsTarget } = options; - // Make sure we're resolving the correct working directory based on the given input + // Resolves the correct working directory based on the given input const cwd = path.resolve(options.cwd ?? process.cwd()); const project = projectFactory(cwd); @@ -27,11 +30,17 @@ export const upgrade = async (options: UpgradeOptions) => { ); } + logger.debug( + `Application: VERSION=${f.version(project.packageJSON.version as Version.LiteralVersion)}; STRAPI_VERSION=${f.version(project.strapiVersion)}` + ); + const npmPackage = npmPackageFactory(upgraderConstants.STRAPI_PACKAGE_NAME); - // Load all versions from the registry + // Load all available versions from the NPM registry await npmPackage.refresh(); + // Initialize the upgrade instance + // Throws during initialization if the provided target is incompatible with the current version const upgrader = upgraderFactory(project, options.target, npmPackage) .dry(options.dry ?? false) .onConfirm(options.confirm ?? null) @@ -42,20 +51,14 @@ export const upgrade = async (options: UpgradeOptions) => { upgrader.overrideCodemodsTarget(codemodsTarget); } - // We're not adding the same requirements (e.g. "REQUIRE_LATEST_FOR_CURRENT_MAJOR") when manually targeting a - // major upgrade (using a semver) as it's implied that the user knows what they're doing - if (options.target === Version.ReleaseType.Major) { - upgrader - .addRequirement(requirements.major.REQUIRE_AVAILABLE_NEXT_MAJOR) - .addRequirement(requirements.major.REQUIRE_LATEST_FOR_CURRENT_MAJOR); - } + // Prompt user for confirmation details before upgrading + await runUpgradePrompts(upgrader, options); - // Make sure the git repository is in an optional state before running the upgrade - // Mainly used to ease rollbacks in case the upgrade is corrupted - upgrader.addRequirement(requirements.common.REQUIRE_GIT.asOptional()); + // Add specific requirements before upgrading + addUpgradeRequirements(upgrader, options); // Actually run the upgrade process once configured, - // The response contains information about the final status (success/error) + // The response contains information about the final status: success/error const upgradeReport = await upgrader.upgrade(); if (!upgradeReport.success) { @@ -64,5 +67,25 @@ export const upgrade = async (options: UpgradeOptions) => { timer.stop(); - logger.info(`Completed in ${f.durationMs(timer.elapsedMs)}`); + logger.info(`Completed in ${f.durationMs(timer.elapsedMs)}ms`); +}; + +const runUpgradePrompts = async (upgrader: Upgrader, options: UpgradeOptions) => { + if (options.target === Version.ReleaseType.Latest) { + await prompts.latest(upgrader, options); + } +}; + +const addUpgradeRequirements = (upgrader: Upgrader, options: UpgradeOptions): void => { + // Don't add the same requirements when manually targeting a major upgrade + // using a semver as it's implied that the users know what they're doing + if (options.target === Version.ReleaseType.Major) { + upgrader + .addRequirement(requirements.major.REQUIRE_AVAILABLE_NEXT_MAJOR) + .addRequirement(requirements.major.REQUIRE_LATEST_FOR_CURRENT_MAJOR); + } + + // Make sure the git repository is in an optimal state before running the upgrade + // Mainly used to ease rollbacks in case the upgrade is corrupted + upgrader.addRequirement(requirements.common.REQUIRE_GIT.asOptional()); }; diff --git a/scripts/front/package.json b/scripts/front/package.json index 6b5046ca643..c74b9380e2d 100644 --- a/scripts/front/package.json +++ b/scripts/front/package.json @@ -1,6 +1,6 @@ { "name": "scripts-front", - "version": "5.0.6", + "version": "5.1.0", "private": true, "scripts": { "test:front": "jest --config jest.config.front.js" diff --git a/templates/website/yarn.lock b/templates/website/yarn.lock index 5cbca89bf5f..85c575c39cc 100644 --- a/templates/website/yarn.lock +++ b/templates/website/yarn.lock @@ -1606,16 +1606,16 @@ __metadata: languageName: node linkType: hard -"@koa/router@npm:12.0.1": - version: 12.0.1 - resolution: "@koa/router@npm:12.0.1" +"@koa/router@npm:12.0.2": + version: 12.0.2 + resolution: "@koa/router@npm:12.0.2" dependencies: debug: "npm:^4.3.4" http-errors: "npm:^2.0.0" koa-compose: "npm:^4.1.0" methods: "npm:^1.1.2" - path-to-regexp: "npm:^6.2.1" - checksum: 10c0/978a668a88dad8cba38afe0df537b95f6d49bdaa60af5f479240fde3a87a7046cf8c068a58c355442335794db5cb583b88ca07a4b65f217b44925a7ce99ccc1d + path-to-regexp: "npm:^6.3.0" + checksum: 10c0/9d33af8b5cb7e80cf2a17e156fe1821ad31ad672ff8e9df62a3af2d2e4a6f49abbbb7038edaea45ef078cabdd8a1ce595ad7da810e96b17c5b954ee46f7e554d languageName: node linkType: hard @@ -3347,7 +3347,7 @@ __metadata: resolution: "@strapi/core@npm:5.0.0" dependencies: "@koa/cors": "npm:5.0.0" - "@koa/router": "npm:12.0.1" + "@koa/router": "npm:12.0.2" "@paralleldrive/cuid2": "npm:2.2.2" "@strapi/admin": "npm:5.0.0" "@strapi/database": "npm:5.0.0" @@ -3823,7 +3823,7 @@ __metadata: dependencies: "@casl/ability": "npm:6.5.0" "@koa/cors": "npm:5.0.0" - "@koa/router": "npm:12.0.1" + "@koa/router": "npm:12.0.2" "@strapi/database": "npm:5.0.0" "@strapi/logger": "npm:5.0.0" "@strapi/permissions": "npm:5.0.0" @@ -12331,7 +12331,7 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^6.2.0, path-to-regexp@npm:^6.2.1": +"path-to-regexp@npm:^6.2.0, path-to-regexp@npm:^6.3.0": version: 6.3.0 resolution: "path-to-regexp@npm:6.3.0" checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6 diff --git a/tests/api/core/content-manager/search.test.api.js b/tests/api/core/content-manager/search.test.api.js index ffc47fcda56..b00285161fc 100644 --- a/tests/api/core/content-manager/search.test.api.js +++ b/tests/api/core/content-manager/search.test.api.js @@ -191,10 +191,9 @@ describe('Search query', () => { expect(Array.isArray(res.body.results)).toBe(true); expect(res.body.results.length).toBe(data.beds.length); - // TODO V5: Filter out i18n fields if content type is not localized - expect(res.body.results.map(omit([...CREATOR_FIELDS, 'localizations', 'status']))).toEqual( - expect.arrayContaining(data.beds) - ); + expect( + res.body.results.map(omit([...CREATOR_FIELDS, 'locale', 'localizations', 'status'])) + ).toEqual(expect.arrayContaining(data.beds)); }); test('search with special characters', async () => { diff --git a/tests/api/core/strapi/api/sort/sort.test.api.ts b/tests/api/core/strapi/api/sort/sort.test.api.ts index b5ceb6f32e7..c1a4c0c9687 100644 --- a/tests/api/core/strapi/api/sort/sort.test.api.ts +++ b/tests/api/core/strapi/api/sort/sort.test.api.ts @@ -26,7 +26,6 @@ const expectArticle = (letter: string) => { return { id, title, - locale: 'en', documentId: expect.any(String), publishedAt: expect.anything(), updatedAt: expect.anything(), @@ -108,24 +107,20 @@ const fixtures = { category: (fixtures) => [ { name: 'Category A', - locale: 'en', tags: fixtures.tag.filter((tag) => tag.name.endsWith('B')).map((cat) => cat.id), }, { name: 'Category C', - locale: 'en', tags: fixtures.tag .filter((tag) => tag.name.endsWith('D') || tag.name.endsWith('A')) .map((cat) => cat.id), }, { name: 'Category B', - locale: 'en', tags: fixtures.tag.filter((tag) => tag.name.endsWith('C')).map((cat) => cat.id), }, { name: 'Category D', - locale: 'en', tags: fixtures.tag.filter((tag) => tag.name.endsWith('A')).map((cat) => cat.id), }, ], @@ -134,24 +129,20 @@ const fixtures = { return [ { title: 'Article A', - locale: 'en', categories: fixtures.category.filter((cat) => cat.name.endsWith('B')).map((cat) => cat.id), }, { title: 'Article C', - locale: 'en', categories: fixtures.category.filter((cat) => cat.name.endsWith('D')).map((cat) => cat.id), }, { title: 'Article D', - locale: 'en', categories: fixtures.category .filter((cat) => cat.name.endsWith('A') || cat.name.endsWith('D')) .map((cat) => cat.id), }, { title: 'Article B', - locale: 'en', categories: fixtures.category .filter((cat) => cat.name.endsWith('C') || cat.name.endsWith('D')) .map((cat) => cat.id), diff --git a/tests/api/core/strapi/api/validate/validate-query.test.api.js b/tests/api/core/strapi/api/validate/validate-query.test.api.js index edf40dca0d1..589a3b944bd 100644 --- a/tests/api/core/strapi/api/validate/validate-query.test.api.js +++ b/tests/api/core/strapi/api/validate/validate-query.test.api.js @@ -776,7 +776,6 @@ describe('Core API - Validate', () => { // TODO: Sanitize id field 'id', 'documentId', - 'locale', 'name', 'name_non_searchable', 'name_hidden', diff --git a/tests/e2e/tests/content-manager/uniqueness.spec.ts b/tests/e2e/tests/content-manager/uniqueness.spec.ts index 283b4509123..8568d77f3b7 100644 --- a/tests/e2e/tests/content-manager/uniqueness.spec.ts +++ b/tests/e2e/tests/content-manager/uniqueness.spec.ts @@ -114,13 +114,13 @@ test.describe('Uniqueness', () => { // testing against if (isSingle) { - await page.getByRole('button', { name: 'No entry yet. Click on the' }).first().click(); - await page.getByRole('button', { name: 'No entry yet. Click on the' }).first().click(); + await page.getByRole('button', { name: 'No entry yet. Click' }).first().click(); + await page.getByRole('button', { name: 'No entry yet. Click' }).first().click(); } else { - await page.getByRole('button', { name: 'No entry yet. Click on the' }).nth(1).click(); + await page.getByRole('button', { name: 'No entry yet. Click' }).nth(1).click(); await page .getByLabel('', { exact: true }) - .getByRole('button', { name: 'No entry yet. Click on the' }) + .getByRole('button', { name: 'No entry yet. Click' }) .click(); } } @@ -188,10 +188,7 @@ test.describe('Uniqueness', () => { // If the field is a repeatable component field, we add an entry and fill // it with the same value to test uniqueness within the same entity. await page.getByRole('button', { name: 'Add an entry' }).click(); - await page - .getByRole('region') - .getByRole('button', { name: 'No entry yet. Click on the' }) - .click(); + await page.getByRole('region').getByRole('button', { name: 'No entry yet. Click' }).click(); await page.getByRole(fieldRole, { name: field.name }).fill(field.value); await clickSave(page); diff --git a/yarn.lock b/yarn.lock index fe0e254c78e..0f67ba7e6e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3714,16 +3714,16 @@ __metadata: languageName: node linkType: hard -"@koa/router@npm:12.0.1": - version: 12.0.1 - resolution: "@koa/router@npm:12.0.1" +"@koa/router@npm:12.0.2": + version: 12.0.2 + resolution: "@koa/router@npm:12.0.2" dependencies: debug: "npm:^4.3.4" http-errors: "npm:^2.0.0" koa-compose: "npm:^4.1.0" methods: "npm:^1.1.2" - path-to-regexp: "npm:^6.2.1" - checksum: 10c0/978a668a88dad8cba38afe0df537b95f6d49bdaa60af5f479240fde3a87a7046cf8c068a58c355442335794db5cb583b88ca07a4b65f217b44925a7ce99ccc1d + path-to-regexp: "npm:^6.3.0" + checksum: 10c0/9d33af8b5cb7e80cf2a17e156fe1821ad31ad672ff8e9df62a3af2d2e4a6f49abbbb7038edaea45ef078cabdd8a1ce595ad7da810e96b17c5b954ee46f7e554d languageName: node linkType: hard @@ -5964,32 +5964,32 @@ __metadata: languageName: node linkType: hard -"@sendgrid/client@npm:^7.7.0": - version: 7.7.0 - resolution: "@sendgrid/client@npm:7.7.0" +"@sendgrid/client@npm:^8.1.3": + version: 8.1.3 + resolution: "@sendgrid/client@npm:8.1.3" dependencies: - "@sendgrid/helpers": "npm:^7.7.0" - axios: "npm:^0.26.0" - checksum: 10c0/acf1db2dcc5181f6f9befba811ba6acb31e50051ab6bded952cb073f117c9237d86013602cdf523b91582c40545e1085a413f1e9e490a46b1d664a5c956c1f3f + "@sendgrid/helpers": "npm:^8.0.0" + axios: "npm:^1.6.8" + checksum: 10c0/1977e9e541d1277a0d8a29eff7f63d4580d2fc2c5e7d9d2adbaee46a471350b1967d6f03dac6adab63522f1775ee5a9e8eddb5502039d3ac8ae98acfedf190cf languageName: node linkType: hard -"@sendgrid/helpers@npm:^7.7.0": - version: 7.7.0 - resolution: "@sendgrid/helpers@npm:7.7.0" +"@sendgrid/helpers@npm:^8.0.0": + version: 8.0.0 + resolution: "@sendgrid/helpers@npm:8.0.0" dependencies: deepmerge: "npm:^4.2.2" - checksum: 10c0/14dfe9af191dd9ad18f0b2744d6d12dbc80f830507b6112d8c4c1c4741ff282393a06e4b4559c0f404d33971639ecadb1017e0cc3b7187e56ec64ab9ee5ff21c + checksum: 10c0/e7f341099c63915eb095102f8c7ec220a8f2fb66a0cd836ab91e7424005e7c5fd27b65e3a5ed43654f5b2b3608fa7d14ebba924ff86e5d0bdfeaf0248f836a50 languageName: node linkType: hard -"@sendgrid/mail@npm:7.7.0": - version: 7.7.0 - resolution: "@sendgrid/mail@npm:7.7.0" +"@sendgrid/mail@npm:8.1.3": + version: 8.1.3 + resolution: "@sendgrid/mail@npm:8.1.3" dependencies: - "@sendgrid/client": "npm:^7.7.0" - "@sendgrid/helpers": "npm:^7.7.0" - checksum: 10c0/5a1d617f1e3f8d47d4fe188ff08f18fca63e3006545ad1f954bf30887806c810f6c0dea7d8850c24de03b3aca75f70f4324369ed2497d957870b8a65f1451127 + "@sendgrid/client": "npm:^8.1.3" + "@sendgrid/helpers": "npm:^8.0.0" + checksum: 10c0/180d4609710dd22e60ecfcfcc7aaf37d8936872c3b7f9413b86d32e55e9d20f21842c4d81fff35d0906ca3750262a6df95b6c96eae5c48fe6c260d8214bd54b8 languageName: node linkType: hard @@ -7348,7 +7348,7 @@ __metadata: resolution: "@strapi/core@workspace:packages/core/core" dependencies: "@koa/cors": "npm:5.0.0" - "@koa/router": "npm:12.0.1" + "@koa/router": "npm:12.0.2" "@paralleldrive/cuid2": "npm:2.2.2" "@strapi/admin": "workspace:*" "@strapi/database": "workspace:*" @@ -8028,7 +8028,7 @@ __metadata: version: 0.0.0-use.local resolution: "@strapi/provider-email-sendgrid@workspace:packages/providers/email-sendgrid" dependencies: - "@sendgrid/mail": "npm:7.7.0" + "@sendgrid/mail": "npm:8.1.3" "@strapi/pack-up": "npm:5.0.0" "@strapi/utils": "workspace:*" eslint-config-custom: "workspace:*" @@ -8263,7 +8263,7 @@ __metadata: dependencies: "@casl/ability": "npm:6.5.0" "@koa/cors": "npm:5.0.0" - "@koa/router": "npm:12.0.1" + "@koa/router": "npm:12.0.2" "@strapi/database": "workspace:*" "@strapi/logger": "workspace:*" "@strapi/pack-up": "npm:5.0.0" @@ -11580,12 +11580,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^0.26.0": - version: 0.26.1 - resolution: "axios@npm:0.26.1" +"axios@npm:^1.6.8": + version: 1.7.7 + resolution: "axios@npm:1.7.7" dependencies: - follow-redirects: "npm:^1.14.8" - checksum: 10c0/77ad7f1e6ca04fcd3fa8af1795b09d8b7c005b71a31f28d99ba40cda0bdcc12a4627801d7fac5efa62b9f667a8402bd54c669039694373bc8d44f6be611f785c + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/4499efc89e86b0b49ffddc018798de05fab26e3bf57913818266be73279a6418c3ce8f9e934c7d2d707ab8c095e837fc6c90608fb7715b94d357720b5f568af7 languageName: node linkType: hard @@ -16856,7 +16858,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.14.8, follow-redirects@npm:^1.15.2, follow-redirects@npm:^1.15.6": +"follow-redirects@npm:^1.15.2, follow-redirects@npm:^1.15.6": version: 1.15.6 resolution: "follow-redirects@npm:1.15.6" peerDependenciesMeta: @@ -17437,7 +17439,7 @@ __metadata: "@strapi/strapi": "workspace:*" better-sqlite3: "npm:11.3.0" lodash: "npm:4.17.21" - mysql2: "npm:3.9.4" + mysql2: "npm:3.9.8" passport-google-oauth2: "npm:0.2.0" pg: "npm:8.11.1" react: "npm:18.3.1" @@ -20815,7 +20817,7 @@ __metadata: "@strapi/strapi": "workspace:*" better-sqlite3: "npm:11.3.0" lodash: "npm:4.17.21" - mysql2: "npm:3.9.4" + mysql2: "npm:3.9.8" passport-google-oauth2: "npm:0.2.0" pg: "npm:8.11.1" react: "npm:18.3.1" @@ -22685,9 +22687,9 @@ __metadata: languageName: node linkType: hard -"mysql2@npm:3.9.4": - version: 3.9.4 - resolution: "mysql2@npm:3.9.4" +"mysql2@npm:3.9.8": + version: 3.9.8 + resolution: "mysql2@npm:3.9.8" dependencies: denque: "npm:^2.1.0" generate-function: "npm:^2.3.1" @@ -22697,7 +22699,7 @@ __metadata: named-placeholders: "npm:^1.1.3" seq-queue: "npm:^0.0.5" sqlstring: "npm:^2.3.2" - checksum: 10c0/bdb3c3da705b7e7a20fbdfe982a38acff133dda571d05b35bcc8a2b39e0535f91e448d6afd4cda24aa87fda2dbe5ff1b6165186c4b6c2f1037b73ca1b0af09fb + checksum: 10c0/c97043a122ce34a0a73ac7f6ce4c44312e4dd51fd347a763e3a28aab51a45dad151404a1c5f97bf0fa40e4437915e8c5b20cbf602cd99931ac1b4ef9b4ffb0b8 languageName: node linkType: hard @@ -24498,7 +24500,7 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:6.3.0, path-to-regexp@npm:^6.2.0, path-to-regexp@npm:^6.2.1": +"path-to-regexp@npm:6.3.0, path-to-regexp@npm:^6.2.0, path-to-regexp@npm:^6.3.0": version: 6.3.0 resolution: "path-to-regexp@npm:6.3.0" checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6