From a3074867b82a7e23a109b0ae81a2d68a6163a346 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Wed, 17 Jan 2024 09:36:40 -0600 Subject: [PATCH 1/6] feat(next): update Next.js settings to add draft mode Issue #502 --- modules/next/composer.json | 2 +- .../src/Plugin/Next/PreviewUrlGenerator/Jwt.php | 4 ++-- modules/next/next.info.yml | 2 +- .../src/Controller/NextSiteEntityController.php | 2 +- .../next/src/Form/NextEntityTypeConfigForm.php | 16 ++++++++-------- modules/next/src/Form/NextSettingsForm.php | 4 ++-- modules/next/src/Form/NextSiteForm.php | 16 ++++++++-------- .../Next/PreviewUrlGenerator/SimpleOauth.php | 4 ++-- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/modules/next/composer.json b/modules/next/composer.json index 05f49ac4..48cea01e 100644 --- a/modules/next/composer.json +++ b/modules/next/composer.json @@ -1,6 +1,6 @@ { "name": "drupal/next", - "description": "Next.js + Drupal for Incremental Static Regeneration and Preview mode.", + "description": "Next.js + Drupal for Incremental Static Regeneration and Draft mode.", "type": "drupal-module", "homepage": "http://drupal.org/project/next", "license": "GPL-2.0-or-later", diff --git a/modules/next/modules/next_jwt/src/Plugin/Next/PreviewUrlGenerator/Jwt.php b/modules/next/modules/next_jwt/src/Plugin/Next/PreviewUrlGenerator/Jwt.php index c5676e21..f09b2f59 100644 --- a/modules/next/modules/next_jwt/src/Plugin/Next/PreviewUrlGenerator/Jwt.php +++ b/modules/next/modules/next_jwt/src/Plugin/Next/PreviewUrlGenerator/Jwt.php @@ -103,8 +103,8 @@ public function defaultConfiguration() { */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form['secret_expiration'] = [ - '#title' => $this->t('Preview secret expiration time'), - '#description' => $this->t('The value, in seconds, to be used as expiration time for the preview secret. It is recommended to use short-lived secrets for increased security.'), + '#title' => $this->t('Secret expiration time'), + '#description' => $this->t('The value, in seconds, to be used as expiration time for the validation secret. It is recommended to use short-lived secrets for increased security.'), '#type' => 'number', '#required' => TRUE, '#default_value' => $this->configuration['secret_expiration'], diff --git a/modules/next/next.info.yml b/modules/next/next.info.yml index de7eaf0a..42872eb2 100644 --- a/modules/next/next.info.yml +++ b/modules/next/next.info.yml @@ -1,5 +1,5 @@ name: Next.js -description: Next.js + Drupal for Incremental Static Regeneration and Preview mode. +description: Next.js + Drupal for Incremental Static Regeneration and Draft mode. type: module core_version_requirement: ^9 || ^10 package: Web services diff --git a/modules/next/src/Controller/NextSiteEntityController.php b/modules/next/src/Controller/NextSiteEntityController.php index 07fab5a7..d6e280e8 100644 --- a/modules/next/src/Controller/NextSiteEntityController.php +++ b/modules/next/src/Controller/NextSiteEntityController.php @@ -64,7 +64,7 @@ public function environmentVariables(NextSiteInterface $next_site) { if ($secret = $next_site->getPreviewSecret()) { $variables += [ - 'preview_variables' => '# Required for Preview Mode', + 'preview_variables' => '# Required for Draft Mode', 'DRUPAL_PREVIEW_SECRET' => $secret, ]; } diff --git a/modules/next/src/Form/NextEntityTypeConfigForm.php b/modules/next/src/Form/NextEntityTypeConfigForm.php index feb56514..37d2e65b 100644 --- a/modules/next/src/Form/NextEntityTypeConfigForm.php +++ b/modules/next/src/Form/NextEntityTypeConfigForm.php @@ -119,16 +119,16 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Settings'), ]; - $form['preview_mode'] = [ - '#title' => $this->t('Preview Mode'), - '#description' => $this->t('Configure preview mode the entity type.'), + $form['draft_mode'] = [ + '#title' => $this->t('Draft Mode'), + '#description' => $this->t('Configure draft mode for this entity type.'), '#type' => 'details', '#group' => 'settings', ]; - $form['preview_mode']['site_resolver'] = [ + $form['draft_mode']['site_resolver'] = [ '#title' => $this->t('Plugin'), - '#description' => $this->t('Select a plugin to use for resolving the preview site for this entity type.'), + '#description' => $this->t('Select a plugin to use when validating the draft url for this entity type.'), '#type' => 'select', '#options' => array_merge(['' => $this->t('None')], array_column($this->siteResolverManager->getDefinitions(), 'label', 'id')), '#default_value' => $entity->getSiteResolver() ? $entity->getSiteResolver()->getId() : NULL, @@ -142,7 +142,7 @@ public function form(array $form, FormStateInterface $form_state) { ], ]; - $form['preview_mode']['site_resolver_settings_container'] = [ + $form['draft_mode']['site_resolver_settings_container'] = [ '#type' => 'container', '#prefix' => '
', '#suffix' => '
', @@ -152,7 +152,7 @@ public function form(array $form, FormStateInterface $form_state) { if ($site_resolver instanceof ConfigurableSiteResolverInterface) { $form['configuration'] = []; $subform_state = SubformState::createForSubform($form['configuration'], $form, $form_state); - $form['preview_mode']['site_resolver_settings_container']['configuration'] = $site_resolver->buildConfigurationForm($form['configuration'], $subform_state); + $form['draft_mode']['site_resolver_settings_container']['configuration'] = $site_resolver->buildConfigurationForm($form['configuration'], $subform_state); } $form['revalidation'] = [ @@ -223,7 +223,7 @@ public function submitSiteResolver(array $form, FormStateInterface $form_state) * Handles switching the site resolver selector. */ public function ajaxReplaceSiteResolverSettingsForm($form, FormStateInterface $form_state) { - return $form['preview_mode']['site_resolver_settings_container']; + return $form['draft_mode']['site_resolver_settings_container']; } /** diff --git a/modules/next/src/Form/NextSettingsForm.php b/modules/next/src/Form/NextSettingsForm.php index ac938209..9d1b4181 100644 --- a/modules/next/src/Form/NextSettingsForm.php +++ b/modules/next/src/Form/NextSettingsForm.php @@ -87,14 +87,14 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['preview_url_generator_container'] = [ - '#title' => $this->t('Preview URL'), + '#title' => $this->t('Draft Mode'), '#type' => 'details', '#group' => 'settings', ]; $form['preview_url_generator_container']['preview_url_generator'] = [ '#title' => $this->t('Plugin'), - '#description' => $this->t('Select a plugin to use for the preview URL generator.'), + '#description' => $this->t('Select a plugin to use for the draft validation generator.'), '#type' => 'select', '#options' => array_column($this->previewUrlGeneratorManager->getDefinitions(), 'label', 'id'), '#default_value' => $config->get('preview_url_generator'), diff --git a/modules/next/src/Form/NextSiteForm.php b/modules/next/src/Form/NextSiteForm.php index 94aa6683..9ccfd0d2 100644 --- a/modules/next/src/Form/NextSiteForm.php +++ b/modules/next/src/Form/NextSiteForm.php @@ -51,9 +51,9 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['preview'] = [ - '#title' => $this->t('Preview Mode'), - '#description' => $this->t('Preview mode allows editors to preview content on the site. You can read more on the Next.js documentation.', [ - ':uri' => 'https://nextjs.org/docs/advanced-features/preview-mode', + '#title' => $this->t('Draft Mode'), + '#description' => $this->t('Draft mode (or the deprecated Preview mode) allows editors to preview content on the site. You can read more on the Next.js documentation.', [ + ':uri' => 'https://nextjs.org/docs/app/building-your-application/configuring/draft-mode', ]), '#type' => 'details', '#group' => 'settings', @@ -61,22 +61,22 @@ public function form(array $form, FormStateInterface $form_state) { $form['preview']['preview_url'] = [ '#type' => 'url', - '#title' => $this->t('Preview URL'), - '#description' => $this->t('Enter the preview URL. Example: https://example.com/api/preview.'), + '#title' => $this->t('Draft URL (or Preview URL)'), + '#description' => $this->t('Enter the draft URL or preview URL. Example: https://example.com/api/draft or https://example.com/api/preview.'), '#default_value' => $entity->getPreviewUrl(), ]; $form['preview']['preview_secret'] = [ '#type' => 'textfield', - '#title' => $this->t('Preview secret'), - '#description' => $this->t('Enter a secret for the site preview. This is the same value used for DRUPAL_PREVIEW_SECRET.'), + '#title' => $this->t('Secret key'), + '#description' => $this->t('Enter a secret for the site draft/preview. This must be unique for each Next.js site'), '#default_value' => $entity->getPreviewSecret(), ]; $form['revalidation'] = [ '#title' => $this->t('On-demand Revalidation'), '#description' => $this->t('On-demand revalidation updates your pages when content is updated on your Drupal site. You can read more on the Next.js documentation.', [ - ':uri' => 'https://nextjs.org/docs/advanced-features/preview-mode', + ':uri' => 'https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data', ]), '#type' => 'details', '#group' => 'settings', diff --git a/modules/next/src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php b/modules/next/src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php index ec17960f..bb15e7bc 100644 --- a/modules/next/src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php +++ b/modules/next/src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php @@ -83,8 +83,8 @@ public function defaultConfiguration() { */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form['secret_expiration'] = [ - '#title' => $this->t('Preview secret expiration time'), - '#description' => $this->t('The value, in seconds, to be used as expiration time for the preview secret. It is recommended to use short-lived secrets for increased security.'), + '#title' => $this->t('Secret expiration time'), + '#description' => $this->t('The value, in seconds, to be used as expiration time for the validation secret. It is recommended to use short-lived secrets for increased security.'), '#type' => 'number', '#required' => TRUE, '#default_value' => $this->configuration['secret_expiration'], From cf8806875a627ea6f905792303c6931779445cae Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Thu, 25 Jan 2024 09:26:00 -0600 Subject: [PATCH 2/6] feat(next-drupal): add draft mode for app router pages Issue #502 --- modules/next/next.post_update.php | 15 ++++++ modules/next/next.routing.yml | 9 ++++ packages/next-drupal/jest.config.cjs | 8 +-- packages/next-drupal/package.json | 12 ++++- packages/next-drupal/src/client.ts | 50 +++++++++++++++---- packages/next-drupal/src/draft.ts | 73 ++++++++++++++++++++++++++++ packages/next-drupal/tsup.config.ts | 2 +- 7 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 modules/next/next.post_update.php create mode 100644 packages/next-drupal/src/draft.ts diff --git a/modules/next/next.post_update.php b/modules/next/next.post_update.php new file mode 100644 index 00000000..b9fe007f --- /dev/null +++ b/modules/next/next.post_update.php @@ -0,0 +1,15 @@ + { + const slug = searchParams.get("slug") + + this.debug(`Fetching draft url validation for ${slug}.`) + + // Fetch the headless CMS to check if the provided `slug` exists + let response: Response + try { + // Validate the draft url. + const validateUrl = this.buildUrl("/next/draft-url").toString() + response = await this.fetch(validateUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(Object.fromEntries(searchParams.entries())), + }) + } catch (error) { + response = new Response(JSON.stringify({ message: error.message }), { + status: 401, + }) + } + + this.debug( + response.status !== 200 + ? `Could not validate slug, ${slug}` + : `Validated slug, ${slug}` + ) + + return response + } + async preview( - request?: NextApiRequest, - response?: NextApiResponse, + request: NextApiRequest, + response: NextApiResponse, options?: PreviewOptions ) { const { slug, resourceVersion, plugin } = request.query @@ -1056,14 +1091,9 @@ export class DrupalClient { response.clearPreviewData() // Validate the preview url. - const validateUrl = this.buildUrl("/next/preview-url") - const result = await this.fetch(validateUrl.toString(), { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(request.query), - }) + const result = await this.validateDraftUrl( + new URL(request.url).searchParams + ) if (!result.ok) { response.statusCode = result.status diff --git a/packages/next-drupal/src/draft.ts b/packages/next-drupal/src/draft.ts new file mode 100644 index 00000000..32f42cd0 --- /dev/null +++ b/packages/next-drupal/src/draft.ts @@ -0,0 +1,73 @@ +import { cookies, draftMode } from "next/headers" +import { redirect } from "next/navigation" +import { DRAFT_DATA_COOKIE_NAME, DRAFT_MODE_COOKIE_NAME } from "./client" +import type { NextRequest } from "next/server" +import type { DrupalClient } from "./client" + +export async function enableDraftMode( + request: NextRequest, + drupal: DrupalClient +): Promise { + // Validate the draft request. + const response = await drupal.validateDraftUrl(request.nextUrl.searchParams) + + // If validation fails, don't enable draft mode. + if (!response.ok) { + return response + } + + const searchParams = request.nextUrl.searchParams + const slug = searchParams.get("slug") + + // Enable Draft Mode by setting the cookie + draftMode().enable() + + // Override the default SameSite=lax. + // See https://github.com/vercel/next.js/issues/49927 + const draftModeCookie = cookies().get(DRAFT_MODE_COOKIE_NAME) + if (draftModeCookie) { + cookies().set({ + ...draftModeCookie, + sameSite: "none", + secure: true, + }) + } + + // Send Drupal's data to the draft-mode page. + const { secret, scope, plugin, ...draftData } = Object.fromEntries( + searchParams.entries() + ) + cookies().set({ + ...draftModeCookie, + name: DRAFT_DATA_COOKIE_NAME, + sameSite: "none", + secure: true, + value: JSON.stringify(draftData), + }) + + // Redirect to the path from the fetched post. We can safely redirect to the + // slug since this has been validated on the server. + redirect(slug) +} + +export function disableDraftMode() { + cookies().delete(DRAFT_DATA_COOKIE_NAME) + draftMode().disable() + + return new Response("Draft mode is disabled") +} + +export interface DraftData { + slug?: string + resourceVersion?: string +} + +export function getDraftData() { + let data: DraftData = {} + + if (draftMode().isEnabled && cookies().has(DRAFT_DATA_COOKIE_NAME)) { + data = JSON.parse(cookies().get(DRAFT_DATA_COOKIE_NAME)?.value || "{}") + } + + return data +} diff --git a/packages/next-drupal/tsup.config.ts b/packages/next-drupal/tsup.config.ts index 0690563c..b7692abe 100644 --- a/packages/next-drupal/tsup.config.ts +++ b/packages/next-drupal/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup" export const tsup = defineConfig({ - entry: ["src/index.ts", "src/navigation.ts"], + entry: ["src/index.ts", "src/draft.ts", "src/navigation.ts"], // Enable experimental code splitting support in CommonJS. // splitting: true, // Use Rollup for tree shaking. From 797e0a943a3af9210bc49d5dd9739b303eff4a04 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Tue, 30 Jan 2024 15:17:21 -0600 Subject: [PATCH 3/6] moar: add draft mode for app router pages --- modules/next/next.routing.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/next/next.routing.yml b/modules/next/next.routing.yml index ef7da5a8..7594d18c 100644 --- a/modules/next/next.routing.yml +++ b/modules/next/next.routing.yml @@ -17,6 +17,7 @@ next.validate_draft_url: _access: 'TRUE' _format: 'json' +# TODO: This path is used by next-drupal 1.6.0; remove with next 3.x. next.validate_preview_url: path: '/next/preview-url' defaults: From 4a4945f6c82229e86ef29cabb11a8e983a7ad0b2 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Thu, 25 Jan 2024 12:00:53 -0600 Subject: [PATCH 4/6] feat(next-drupal)!: add draft mode to pages router Issue #502 BREAKING CHANGE: The options to the DrupalClient.preview() method were previously ignored. The options are now passed to the NextApiResponse.setDraftMode() method and their TypeScript definition have now changed to match the options parameter of the setDraftMode method. --- packages/next-drupal/src/client.ts | 74 +++++++++++++++++++++--------- packages/next-drupal/src/types.ts | 7 --- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/packages/next-drupal/src/client.ts b/packages/next-drupal/src/client.ts index 9df7803a..e646d3a7 100644 --- a/packages/next-drupal/src/client.ts +++ b/packages/next-drupal/src/client.ts @@ -34,7 +34,6 @@ import type { Locale, PathAlias, PathPrefix, - PreviewOptions, } from "./types" const DEFAULT_API_PREFIX = "/jsonapi" @@ -1082,9 +1081,11 @@ export class DrupalClient { async preview( request: NextApiRequest, response: NextApiResponse, - options?: PreviewOptions + options?: Parameters[0] ) { - const { slug, resourceVersion, plugin } = request.query + const { slug, resourceVersion, plugin, secret, scope, ...draftData } = + request.query + const useDraftMode = options?.enable try { // Always clear preview data to handle different scopes. @@ -1092,46 +1093,75 @@ export class DrupalClient { // Validate the preview url. const result = await this.validateDraftUrl( - new URL(request.url).searchParams + new URL(request.url, `http://${request.headers.host}`).searchParams ) + const validationPayload = await result.json() + const previewData = { + resourceVersion, + plugin, + ...validationPayload, + } + if (!result.ok) { + this.debug(`Draft url validation error: ${validationPayload.message}`) response.statusCode = result.status - - return response.json(await result.json()) + return response.json(validationPayload) } - const validationPayload = await result.json() + // Optionally turn on draft mode. + if (useDraftMode) { + response.setDraftMode(options) + } - response.setPreviewData({ - resourceVersion, - plugin, - ...validationPayload, - }) + // Turns on preview mode and adds preview data to Next.js' static context. + response.setPreviewData(previewData) // Fix issue with cookie. // See https://github.com/vercel/next.js/discussions/32238. // See https://github.com/vercel/next.js/blob/d895a50abbc8f91726daa2d7ebc22c58f58aabbb/packages/next/server/api-utils/node.ts#L504. - if (this.forceIframeSameSiteCookie) { - const previous = response.getHeader("Set-Cookie") as string[] - previous.forEach((cookie, index) => { - previous[index] = cookie.replace( - "SameSite=Lax", - "SameSite=None;Secure" - ) - }) - response.setHeader(`Set-Cookie`, previous) + const cookies = (response.getHeader("Set-Cookie") as string[]).map( + (cookie) => cookie.replace("SameSite=Lax", "SameSite=None; Secure") + ) + if (useDraftMode) { + // Adds preview data for use in app router pages. + cookies.push( + `${DRAFT_DATA_COOKIE_NAME}=${encodeURIComponent( + JSON.stringify({ slug, resourceVersion, ...draftData }) + )}; Path=/; HttpOnly; SameSite=None; Secure` + ) } + response.setHeader("Set-Cookie", cookies) - // We can safely redirect to the slug since this has been validated on the server. + // We can safely redirect to the slug since this has been validated on the + // server. response.writeHead(307, { Location: slug }) + this.debug(`${useDraftMode ? "Draft" : "Preview"} mode enabled.`) + return response.end() } catch (error) { + this.debug(`Preview failed: ${error.message}`) return response.status(422).end() } } + async previewDisable(request: NextApiRequest, response: NextApiResponse) { + // Disable both preview and draft modes. + response.clearPreviewData() + response.setDraftMode({ enable: false }) + + // Delete the draft data cookie. + const cookies = response.getHeader("Set-Cookie") as string[] + cookies.push( + `${DRAFT_DATA_COOKIE_NAME}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=None; Secure` + ) + response.setHeader("Set-Cookie", cookies) + + response.writeHead(307, { Location: "/" }) + response.end() + } + async getMenu( name: string, options?: JsonApiWithLocaleOptions & diff --git a/packages/next-drupal/src/types.ts b/packages/next-drupal/src/types.ts index ebd5cb62..60c5c783 100644 --- a/packages/next-drupal/src/types.ts +++ b/packages/next-drupal/src/types.ts @@ -410,13 +410,6 @@ export interface JsonApiResourceWithPath extends JsonApiResource { path: PathAlias } -export interface PreviewOptions { - errorMessages?: { - secret?: string - slug?: string - } -} - export type GetResourcePreviewUrlOptions = JsonApiWithLocaleOptions & { isVersionable?: boolean } From 0688eba1e6be4bce5fafb3fab90c5fbc114c59a2 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Thu, 25 Jan 2024 12:15:04 -0600 Subject: [PATCH 5/6] feat(next-drupal)!: drop forceIframeSameSiteCookie option BREAKING CHANGE: The forceIframeSameSiteCookie option to DrupalClient has been removed. The DrupalClient now automatically makes this change when the Next.js website is running in development mode (and the change is not needed for production sites). --- examples/example-graphql/lib/drupal.ts | 1 - packages/next-drupal/src/client.ts | 4 ---- packages/next-drupal/src/types.ts | 10 --------- www/content/docs/configuration.mdx | 28 -------------------------- 4 files changed, 43 deletions(-) diff --git a/examples/example-graphql/lib/drupal.ts b/examples/example-graphql/lib/drupal.ts index fdae367d..f1c78d5b 100644 --- a/examples/example-graphql/lib/drupal.ts +++ b/examples/example-graphql/lib/drupal.ts @@ -8,7 +8,6 @@ export const drupal = new DrupalClient( clientSecret: process.env.DRUPAL_CLIENT_SECRET, }, previewSecret: process.env.DRUPAL_PREVIEW_SECRET, - forceIframeSameSiteCookie: true, } ) diff --git a/packages/next-drupal/src/client.ts b/packages/next-drupal/src/client.ts index e646d3a7..7e22df04 100644 --- a/packages/next-drupal/src/client.ts +++ b/packages/next-drupal/src/client.ts @@ -113,8 +113,6 @@ export class DrupalClient { private previewSecret?: DrupalClientOptions["previewSecret"] - private forceIframeSameSiteCookie?: DrupalClientOptions["forceIframeSameSiteCookie"] - /** * Instantiates a new DrupalClient. * @@ -142,7 +140,6 @@ export class DrupalClient { auth, previewSecret, accessToken, - forceIframeSameSiteCookie = false, throwJsonApiErrors = true, } = options @@ -160,7 +157,6 @@ export class DrupalClient { this.previewSecret = previewSecret this.cache = cache this.accessToken = accessToken - this.forceIframeSameSiteCookie = forceIframeSameSiteCookie this.throwJsonApiErrors = throwJsonApiErrors // Do not throw errors in production. diff --git a/packages/next-drupal/src/types.ts b/packages/next-drupal/src/types.ts index 60c5c783..0e5c9730 100644 --- a/packages/next-drupal/src/types.ts +++ b/packages/next-drupal/src/types.ts @@ -139,16 +139,6 @@ export type DrupalClientOptions = { * The scope used for the current access token. */ accessTokenScope?: string - - /** - * If set to true, the preview cookie will be set with SameSite=None,Secure. - * - * * **Default value**: `false` - * * **Required**: *No* - * - * [Documentation](https://next-drupal.org/docs/client/configuration#forceiframesamesitecookie) - */ - forceIframeSameSiteCookie?: boolean } export type DrupalClientAuth = diff --git a/www/content/docs/configuration.mdx b/www/content/docs/configuration.mdx index 42d9f8ac..baaaec03 100644 --- a/www/content/docs/configuration.mdx +++ b/www/content/docs/configuration.mdx @@ -217,34 +217,6 @@ A long-lived access token you can set directly on the client. --- -### forceIframeSameSiteCookie - - - -Use `forceIframeSameSiteCookie` in development only. - - - -- **Default value**: `false` -- **Required**: No - -If you're running your site in development and the host address is different from the iframe preview, you might run into issues with `SameSite` cookies. - -You can use this option to force the cookie to be set to `SameSite=None,Secure` in **development**. - -```ts -export const drupal = new DrupalClient( - process.env.NEXT_PUBLIC_DRUPAL_BASE_URL, - { - forceIframeSameSiteCookie: process.env.NODE_ENV === "development", - } -) -``` - -For more info see: https://github.com/vercel/next.js/discussions/32238 - ---- - ### debug - **Default value**: `false` From cd91fdd1f310fb62cba1a5a461e521c23d401344 Mon Sep 17 00:00:00 2001 From: JohnAlbin Date: Sun, 28 Jan 2024 15:44:01 -0600 Subject: [PATCH 6/6] feat(next-drupal): add example-router-migration --- .../example-router-migration/.env.example | 12 + .../example-router-migration/.eslintrc.json | 4 + examples/example-router-migration/.gitignore | 40 ++++ examples/example-router-migration/.nvmrc | 1 + .../example-router-migration/.prettierignore | 18 ++ .../example-router-migration/.prettierrc.json | 4 + .../example-router-migration/CHANGELOG.md | 4 + examples/example-router-migration/README.md | 43 ++++ .../app/[...slug]/page.tsx | 115 ++++++++++ .../app/api/disable-draft/route.ts | 6 + .../app/api/draft/route.ts | 7 + .../app/api/revalidate/route.ts | 28 +++ .../example-router-migration/app/layout.tsx | 37 +++ .../example-router-migration/app/page.tsx | 51 +++++ .../components/drupal/Article.tsx | 46 ++++ .../components/drupal/ArticleTeaser.tsx | 54 +++++ .../components/drupal/BasicPage.tsx | 19 ++ .../components/misc/DraftAlert/Client.tsx | 38 ++++ .../components/misc/DraftAlert/index.tsx | 13 ++ .../components/navigation/HeaderNav.tsx | 21 ++ .../components/navigation/Link.tsx | 23 ++ .../components/pages-router/Layout.tsx | 15 ++ .../components/pages-router/PreviewAlert.tsx | 30 +++ .../example-router-migration/lib/drupal.ts | 13 ++ .../example-router-migration/lib/utils.ts | 12 + .../example-router-migration/next.config.js | 16 ++ .../example-router-migration/package.json | 34 +++ .../example-router-migration/pages/_app.tsx | 6 + .../pages/_document.tsx | 13 ++ .../pages/api/exit-preview.ts | 9 + .../pages/api/preview-only.ts | 9 + .../pages/api/preview.ts | 10 + .../pages/pages-router/[...slug].tsx | 92 ++++++++ .../pages/pages-router/index.tsx | 78 +++++++ .../postcss.config.js | 8 + .../public/favicon.ico | Bin 0 -> 15086 bytes .../public/robots.txt | 2 + .../styles/globals.css | 3 + .../tailwind.config.ts | 18 ++ .../example-router-migration/tsconfig.json | 28 +++ yarn.lock | 212 ++++++++++++++++-- 41 files changed, 1175 insertions(+), 17 deletions(-) create mode 100644 examples/example-router-migration/.env.example create mode 100644 examples/example-router-migration/.eslintrc.json create mode 100644 examples/example-router-migration/.gitignore create mode 100644 examples/example-router-migration/.nvmrc create mode 100644 examples/example-router-migration/.prettierignore create mode 100644 examples/example-router-migration/.prettierrc.json create mode 100644 examples/example-router-migration/CHANGELOG.md create mode 100644 examples/example-router-migration/README.md create mode 100644 examples/example-router-migration/app/[...slug]/page.tsx create mode 100644 examples/example-router-migration/app/api/disable-draft/route.ts create mode 100644 examples/example-router-migration/app/api/draft/route.ts create mode 100644 examples/example-router-migration/app/api/revalidate/route.ts create mode 100644 examples/example-router-migration/app/layout.tsx create mode 100644 examples/example-router-migration/app/page.tsx create mode 100644 examples/example-router-migration/components/drupal/Article.tsx create mode 100644 examples/example-router-migration/components/drupal/ArticleTeaser.tsx create mode 100644 examples/example-router-migration/components/drupal/BasicPage.tsx create mode 100644 examples/example-router-migration/components/misc/DraftAlert/Client.tsx create mode 100644 examples/example-router-migration/components/misc/DraftAlert/index.tsx create mode 100644 examples/example-router-migration/components/navigation/HeaderNav.tsx create mode 100644 examples/example-router-migration/components/navigation/Link.tsx create mode 100644 examples/example-router-migration/components/pages-router/Layout.tsx create mode 100644 examples/example-router-migration/components/pages-router/PreviewAlert.tsx create mode 100644 examples/example-router-migration/lib/drupal.ts create mode 100644 examples/example-router-migration/lib/utils.ts create mode 100644 examples/example-router-migration/next.config.js create mode 100644 examples/example-router-migration/package.json create mode 100644 examples/example-router-migration/pages/_app.tsx create mode 100644 examples/example-router-migration/pages/_document.tsx create mode 100644 examples/example-router-migration/pages/api/exit-preview.ts create mode 100644 examples/example-router-migration/pages/api/preview-only.ts create mode 100644 examples/example-router-migration/pages/api/preview.ts create mode 100644 examples/example-router-migration/pages/pages-router/[...slug].tsx create mode 100644 examples/example-router-migration/pages/pages-router/index.tsx create mode 100644 examples/example-router-migration/postcss.config.js create mode 100644 examples/example-router-migration/public/favicon.ico create mode 100644 examples/example-router-migration/public/robots.txt create mode 100644 examples/example-router-migration/styles/globals.css create mode 100644 examples/example-router-migration/tailwind.config.ts create mode 100644 examples/example-router-migration/tsconfig.json diff --git a/examples/example-router-migration/.env.example b/examples/example-router-migration/.env.example new file mode 100644 index 00000000..951ea909 --- /dev/null +++ b/examples/example-router-migration/.env.example @@ -0,0 +1,12 @@ +# See https://next-drupal.org/docs/environment-variables + +# Required +NEXT_PUBLIC_DRUPAL_BASE_URL=https://site.example.com +NEXT_IMAGE_DOMAIN=site.example.com + +# Authentication +DRUPAL_CLIENT_ID=Retrieve this from /admin/config/services/consumer +DRUPAL_CLIENT_SECRET=Retrieve this from /admin/config/services/consumer + +# Required for On-demand Revalidation +DRUPAL_REVALIDATE_SECRET=Retrieve this from /admin/config/services/next diff --git a/examples/example-router-migration/.eslintrc.json b/examples/example-router-migration/.eslintrc.json new file mode 100644 index 00000000..7c1a3add --- /dev/null +++ b/examples/example-router-migration/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": "next/core-web-vitals", + "root": true +} diff --git a/examples/example-router-migration/.gitignore b/examples/example-router-migration/.gitignore new file mode 100644 index 00000000..081b7c17 --- /dev/null +++ b/examples/example-router-migration/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# IDE files +/.idea +/.vscode + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/example-router-migration/.nvmrc b/examples/example-router-migration/.nvmrc new file mode 100644 index 00000000..9a2a0e21 --- /dev/null +++ b/examples/example-router-migration/.nvmrc @@ -0,0 +1 @@ +v20 diff --git a/examples/example-router-migration/.prettierignore b/examples/example-router-migration/.prettierignore new file mode 100644 index 00000000..03c8a68b --- /dev/null +++ b/examples/example-router-migration/.prettierignore @@ -0,0 +1,18 @@ +# Ignore everything. +/* + +# Format most files in the root directory. +!/*.js +!/*.ts +!/*.md +!/*.json +# But ignore some. +/package.json +/package-lock.json +/CHANGELOG.md + +# Don't ignore these nested directories. +!/app +!/components +!/lib +!/pages diff --git a/examples/example-router-migration/.prettierrc.json b/examples/example-router-migration/.prettierrc.json new file mode 100644 index 00000000..3c60a7b5 --- /dev/null +++ b/examples/example-router-migration/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "semi": false, + "trailingComma": "es5" +} diff --git a/examples/example-router-migration/CHANGELOG.md b/examples/example-router-migration/CHANGELOG.md new file mode 100644 index 00000000..e4d87c4d --- /dev/null +++ b/examples/example-router-migration/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/examples/example-router-migration/README.md b/examples/example-router-migration/README.md new file mode 100644 index 00000000..c694ee20 --- /dev/null +++ b/examples/example-router-migration/README.md @@ -0,0 +1,43 @@ +# example-router-migration + +Next.js recommends using their new App Router over the legacy Pages Router. The [full router migration guide](https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration) is available in the Next.js documentation. + +The new App Router is also designed to facilitate sites that need to migrate from the Pages Router in a piecemeal fashion rather than all at once. + +This codebase is an example of a `next-drupal` site that is in the middle of a Next.js Pages to App Router migration. + +## Piecemeal router migration steps + +### Initial migration + +1. Update the `next-drupal` package to the latest 2.x version. +2. Update the `next` module on your Drupal site to the latest 2.x version. + 1. The most recent version is available at https://www.drupal.org/project/next + 2. Run your Drupal site’s /update.php script. +3. Migrate from Preview Mode to Draft mode. Preview mode only works with the legacy Pages Router. Draft mode works with both routers. + 1. Update the `/pages/api/preview.ts` file to match the one in this Git repo. + 2. Update the `/pages/api/exit-preview.ts` file to match the one in this Git repo. + 3. Delete your `/pages/api/revalidate.ts` file. + 4. Create a `/app/api` directory and add all the files from this Git repo’s `/app/api` directory. + +### Piecemeal migration + +Follow [Next.js’ router migration guide](https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration). + +Over time, you will be moving all the files from `/pages` to `/app`. However, these JavaScript files should remain in the `/pages` directory to prevent Preview/Draft Mode from breaking: + +- `/pages/api/exit-preview.ts` +- `/pages/api/preview.ts` + +### Final migration steps + +1. Turn off the legacy Preview Mode. + 1. Go to the Next.js site configuration on your Drupal site at `/admin/config/services/next`. + 2. For each Next.js configuration, change the end of the URL in the “Draft URL (or Preview URL)” setting from `preview` to `draft`, e.g. `https://example.com/api/preview` to `https://example.com/api/draft`. +2. Delete the last files in your `/pages` directory: + - `/pages/api/exit-preview.ts` + - `/pages/api/preview.ts` + +## License + +Licensed under the [MIT license](https://github.com/chapter-three/next-drupal/blob/master/LICENSE). diff --git a/examples/example-router-migration/app/[...slug]/page.tsx b/examples/example-router-migration/app/[...slug]/page.tsx new file mode 100644 index 00000000..2a76ee5e --- /dev/null +++ b/examples/example-router-migration/app/[...slug]/page.tsx @@ -0,0 +1,115 @@ +import { draftMode } from "next/headers" +import { notFound } from "next/navigation" +import { getDraftData } from "next-drupal/draft" +import { Article } from "@/components/drupal/Article" +import { BasicPage } from "@/components/drupal/BasicPage" +import { drupal } from "@/lib/drupal" +import type { Metadata, ResolvingMetadata } from "next" +import type { DrupalNode, JsonApiParams } from "next-drupal" + +async function getNode(slug: string[]) { + const path = slug.join("/") + + const params: JsonApiParams = {} + + const draftData = getDraftData() + + if (draftData.slug === `/${path}`) { + params.resourceVersion = draftData.resourceVersion + } + + // Translating the path also allows us to discover the entity type. + const translatedPath = await drupal.translatePath(path) + + if (!translatedPath) { + throw new Error("Resource not found", { cause: "NotFound" }) + } + + const type = translatedPath.jsonapi?.resourceName! + const uuid = translatedPath.entity.uuid + + if (type === "node--article") { + params.include = "field_image,uid" + } + + const resource = await drupal.getResource(type, uuid, { + params, + }) + + if (!resource) { + throw new Error( + `Failed to fetch resource: ${translatedPath?.jsonapi?.individual}`, + { + cause: "DrupalError", + } + ) + } + + return resource +} + +type NodePageParams = { + slug: string[] +} +type NodePageProps = { + params: NodePageParams + searchParams: { [key: string]: string | string[] | undefined } +} + +export async function generateMetadata( + { params: { slug } }: NodePageProps, + parent: ResolvingMetadata +): Promise { + let node + try { + node = await getNode(slug) + } catch (e) { + // If we fail to fetch the node, don't return any metadata. + return {} + } + + return { + title: node.title, + } +} + +const RESOURCE_TYPES = ["node--page", "node--article"] + +export async function generateStaticParams(): Promise { + // TODO: Replace getStaticPathsFromContext() usage since there is no context. + const paths = await drupal.getStaticPathsFromContext(RESOURCE_TYPES, {}) + // console.log( + // "generateStaticParams", + // paths.map(({ params }) => params) + // ) + return paths.map((path: string | { params: NodePageParams }) => + typeof path === "string" ? { slug: [] } : path?.params + ) +} + +export default async function NodePage({ + params: { slug }, + searchParams, +}: NodePageProps) { + const isDraftMode = draftMode().isEnabled + + let node + try { + node = await getNode(slug) + } catch (error) { + // If getNode throws an error, tell Next.js the path is 404. + notFound() + } + + // If we're not in draft mode and the resource is not published, return a 404. + if (!isDraftMode && node?.status === false) { + notFound() + } + + return ( + <> + {node.type === "node--page" && } + {node.type === "node--article" &&
} + + ) +} diff --git a/examples/example-router-migration/app/api/disable-draft/route.ts b/examples/example-router-migration/app/api/disable-draft/route.ts new file mode 100644 index 00000000..81900948 --- /dev/null +++ b/examples/example-router-migration/app/api/disable-draft/route.ts @@ -0,0 +1,6 @@ +import { disableDraftMode } from "next-drupal/draft" +import type { NextRequest } from "next/server" + +export async function GET(request: NextRequest) { + return disableDraftMode() +} diff --git a/examples/example-router-migration/app/api/draft/route.ts b/examples/example-router-migration/app/api/draft/route.ts new file mode 100644 index 00000000..b8757e2a --- /dev/null +++ b/examples/example-router-migration/app/api/draft/route.ts @@ -0,0 +1,7 @@ +import { drupal } from "@/lib/drupal" +import { enableDraftMode } from "next-drupal/draft" +import type { NextRequest } from "next/server" + +export async function GET(request: NextRequest): Promise { + return enableDraftMode(request, drupal) +} diff --git a/examples/example-router-migration/app/api/revalidate/route.ts b/examples/example-router-migration/app/api/revalidate/route.ts new file mode 100644 index 00000000..d3722f7b --- /dev/null +++ b/examples/example-router-migration/app/api/revalidate/route.ts @@ -0,0 +1,28 @@ +import { revalidatePath } from "next/cache" +import type { NextRequest } from "next/server" + +async function handler(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const slug = searchParams.get("slug") + const secret = searchParams.get("secret") + + // Validate secret. + if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { + return new Response("Invalid secret.", { status: 401 }) + } + + // Validate slug. + if (!slug) { + return new Response("Invalid slug.", { status: 400 }) + } + + try { + revalidatePath(slug) + + return new Response("Revalidated.") + } catch (error) { + return new Response((error as Error).message, { status: 500 }) + } +} + +export { handler as GET, handler as POST } diff --git a/examples/example-router-migration/app/layout.tsx b/examples/example-router-migration/app/layout.tsx new file mode 100644 index 00000000..3b3c9652 --- /dev/null +++ b/examples/example-router-migration/app/layout.tsx @@ -0,0 +1,37 @@ +import { DraftAlert } from "@/components/misc/DraftAlert" +import { HeaderNav } from "@/components/navigation/HeaderNav" +import type { Metadata } from "next" +import type { ReactNode } from "react" + +import "@/styles/globals.css" + +export const metadata: Metadata = { + title: { + default: "Next.js for Drupal", + template: "%s | Next.js for Drupal", + }, + description: "A Next.js site powered by a Drupal backend.", + icons: { + icon: "/favicon.ico", + }, +} + +export default function RootLayout({ + // Layouts must accept a children prop. + // This will be populated with nested layouts or pages + children, +}: { + children: ReactNode +}) { + return ( + + + +
+ +
{children}
+
+ + + ) +} diff --git a/examples/example-router-migration/app/page.tsx b/examples/example-router-migration/app/page.tsx new file mode 100644 index 00000000..a7f1ec4d --- /dev/null +++ b/examples/example-router-migration/app/page.tsx @@ -0,0 +1,51 @@ +import { ArticleTeaser } from "@/components/drupal/ArticleTeaser" +import { Link } from "@/components/navigation/Link" +import { drupal } from "@/lib/drupal" +import type { Metadata } from "next" +import type { DrupalNode } from "next-drupal" + +export const metadata: Metadata = { + description: "A Next.js site powered by a Drupal backend.", +} + +export default async function Home() { + const nodes = await drupal.getResourceCollection( + "node--article", + { + params: { + "filter[status]": 1, + "fields[node--article]": "title,path,field_image,uid,created", + include: "field_image,uid", + sort: "-created", + }, + } + ) + + return ( + <> +

+ Latest Articles. +
+ + Using the App Router + +

+

+ Switch to{" "} + + Pages Router + +

+ {nodes?.length ? ( + nodes.map((node) => ( +
+ +
+
+ )) + ) : ( +

No nodes found

+ )} + + ) +} diff --git a/examples/example-router-migration/components/drupal/Article.tsx b/examples/example-router-migration/components/drupal/Article.tsx new file mode 100644 index 00000000..b4d3d234 --- /dev/null +++ b/examples/example-router-migration/components/drupal/Article.tsx @@ -0,0 +1,46 @@ +import Image from "next/image" +import { absoluteUrl, formatDate } from "@/lib/utils" +import type { DrupalNode } from "next-drupal" + +interface ArticleProps { + node: DrupalNode +} + +export function Article({ node, ...props }: ArticleProps) { + return ( +
+

{node.title}

+
+ {node.uid?.display_name ? ( + + Posted by{" "} + {node.uid?.display_name} + + ) : null} + - {formatDate(node.created)} +
+ {node.field_image && ( +
+ {node.field_image.resourceIdObjMeta.alt + {node.field_image.resourceIdObjMeta.title && ( +
+ {node.field_image.resourceIdObjMeta.title} +
+ )} +
+ )} + {node.body?.processed && ( +
+ )} +
+ ) +} diff --git a/examples/example-router-migration/components/drupal/ArticleTeaser.tsx b/examples/example-router-migration/components/drupal/ArticleTeaser.tsx new file mode 100644 index 00000000..8efeac62 --- /dev/null +++ b/examples/example-router-migration/components/drupal/ArticleTeaser.tsx @@ -0,0 +1,54 @@ +import Image from "next/image" +import { Link } from "@/components/navigation/Link" +import { absoluteUrl, formatDate } from "@/lib/utils" +import type { DrupalNode } from "next-drupal" + +interface ArticleTeaserProps { + node: DrupalNode +} + +export function ArticleTeaser({ node, ...props }: ArticleTeaserProps) { + return ( +
+ +

{node.title}

+ +
+ {node.uid?.display_name ? ( + + Posted by{" "} + {node.uid?.display_name} + + ) : null} + - {formatDate(node.created)} +
+ {node.field_image && ( +
+ {node.field_image.resourceIdObjMeta.alt} +
+ )} + + Read article + + + + +
+ ) +} diff --git a/examples/example-router-migration/components/drupal/BasicPage.tsx b/examples/example-router-migration/components/drupal/BasicPage.tsx new file mode 100644 index 00000000..88d7f00b --- /dev/null +++ b/examples/example-router-migration/components/drupal/BasicPage.tsx @@ -0,0 +1,19 @@ +import type { DrupalNode } from "next-drupal" + +interface BasicPageProps { + node: DrupalNode +} + +export function BasicPage({ node, ...props }: BasicPageProps) { + return ( +
+

{node.title}

+ {node.body?.processed && ( +
+ )} +
+ ) +} diff --git a/examples/example-router-migration/components/misc/DraftAlert/Client.tsx b/examples/example-router-migration/components/misc/DraftAlert/Client.tsx new file mode 100644 index 00000000..00932ae4 --- /dev/null +++ b/examples/example-router-migration/components/misc/DraftAlert/Client.tsx @@ -0,0 +1,38 @@ +"use client" + +import { useEffect, useState } from "react" + +export function DraftAlertClient({ + isDraftEnabled, +}: { + isDraftEnabled: boolean +}) { + const [showDraftAlert, setShowDraftAlert] = useState(false) + + useEffect(() => { + setShowDraftAlert(isDraftEnabled && window.top === window.self) + }, [isDraftEnabled]) + + if (!showDraftAlert) { + return null + } + + function buttonHandler() { + void fetch("/api/disable-draft") + setShowDraftAlert(false) + } + + return ( +
+

+ This page is a draft. + +

+
+ ) +} diff --git a/examples/example-router-migration/components/misc/DraftAlert/index.tsx b/examples/example-router-migration/components/misc/DraftAlert/index.tsx new file mode 100644 index 00000000..a07f0d67 --- /dev/null +++ b/examples/example-router-migration/components/misc/DraftAlert/index.tsx @@ -0,0 +1,13 @@ +import { Suspense } from "react" +import { draftMode } from "next/headers" +import { DraftAlertClient } from "./Client" + +export function DraftAlert() { + const isDraftEnabled = draftMode().isEnabled + + return ( + + + + ) +} diff --git a/examples/example-router-migration/components/navigation/HeaderNav.tsx b/examples/example-router-migration/components/navigation/HeaderNav.tsx new file mode 100644 index 00000000..bccb4e4c --- /dev/null +++ b/examples/example-router-migration/components/navigation/HeaderNav.tsx @@ -0,0 +1,21 @@ +import { Link } from "@/components/navigation/Link" + +export function HeaderNav() { + return ( +
+
+ + Next.js for Drupal + + + Read the docs + +
+
+ ) +} diff --git a/examples/example-router-migration/components/navigation/Link.tsx b/examples/example-router-migration/components/navigation/Link.tsx new file mode 100644 index 00000000..23dbc4c0 --- /dev/null +++ b/examples/example-router-migration/components/navigation/Link.tsx @@ -0,0 +1,23 @@ +import { forwardRef } from "react" +import NextLink from "next/link" +import type { AnchorHTMLAttributes, ReactNode } from "react" +import type { LinkProps as NextLinkProps } from "next/link" + +type LinkProps = NextLinkProps & + Omit, keyof NextLinkProps> & { + children?: ReactNode + } + +export const Link = forwardRef( + function LinkWithRef( + { + // Turn next/link prefetching off by default. + // @see https://github.com/vercel/next.js/discussions/24009 + prefetch = false, + ...rest + }, + ref + ) { + return + } +) diff --git a/examples/example-router-migration/components/pages-router/Layout.tsx b/examples/example-router-migration/components/pages-router/Layout.tsx new file mode 100644 index 00000000..16e08744 --- /dev/null +++ b/examples/example-router-migration/components/pages-router/Layout.tsx @@ -0,0 +1,15 @@ +import { HeaderNav } from "@/components/navigation/HeaderNav" +import { PreviewAlert } from "@/components/pages-router/PreviewAlert" +import type { ReactNode } from "react" + +export function Layout({ children }: { children: ReactNode }) { + return ( + <> + +
+ +
{children}
+
+ + ) +} diff --git a/examples/example-router-migration/components/pages-router/PreviewAlert.tsx b/examples/example-router-migration/components/pages-router/PreviewAlert.tsx new file mode 100644 index 00000000..abca6a68 --- /dev/null +++ b/examples/example-router-migration/components/pages-router/PreviewAlert.tsx @@ -0,0 +1,30 @@ +import { useEffect, useState } from "react" +import { useRouter } from "next/router" + +export function PreviewAlert() { + const router = useRouter() + const isPreview = router.isPreview + const [showPreviewAlert, setShowPreviewAlert] = useState(false) + + useEffect(() => { + setShowPreviewAlert(isPreview && window.top === window.self) + }, [isPreview]) + + if (!showPreviewAlert) { + return null + } + + return ( +
+

+ This page is a preview.{" "} + +

+
+ ) +} diff --git a/examples/example-router-migration/lib/drupal.ts b/examples/example-router-migration/lib/drupal.ts new file mode 100644 index 00000000..e390f347 --- /dev/null +++ b/examples/example-router-migration/lib/drupal.ts @@ -0,0 +1,13 @@ +import { DrupalClient } from "next-drupal" + +const baseUrl: string = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL || "" +const clientId = process.env.DRUPAL_CLIENT_ID || "" +const clientSecret = process.env.DRUPAL_CLIENT_SECRET || "" + +export const drupal = new DrupalClient(baseUrl, { + auth: { + clientId, + clientSecret, + }, + debug: true, +}) diff --git a/examples/example-router-migration/lib/utils.ts b/examples/example-router-migration/lib/utils.ts new file mode 100644 index 00000000..d83a0d73 --- /dev/null +++ b/examples/example-router-migration/lib/utils.ts @@ -0,0 +1,12 @@ +export function formatDate(input: string): string { + const date = new Date(input) + return date.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }) +} + +export function absoluteUrl(input: string) { + return `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}${input}` +} diff --git a/examples/example-router-migration/next.config.js b/examples/example-router-migration/next.config.js new file mode 100644 index 00000000..0a7fabac --- /dev/null +++ b/examples/example-router-migration/next.config.js @@ -0,0 +1,16 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + images: { + remotePatterns: [ + { + // protocol: 'https', + hostname: process.env.NEXT_IMAGE_DOMAIN, + // port: '', + // pathname: '/sites/default/files/**', + }, + ], + }, +} + +module.exports = nextConfig diff --git a/examples/example-router-migration/package.json b/examples/example-router-migration/package.json new file mode 100644 index 00000000..2783e4c6 --- /dev/null +++ b/examples/example-router-migration/package.json @@ -0,0 +1,34 @@ +{ + "name": "example-router-migration", + "version": "1.8.0", + "private": true, + "license": "MIT", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "preview": "next build && next start", + "lint": "next lint", + "format": "prettier --write .", + "format:check": "prettier --check ." + }, + "dependencies": { + "next": "^14", + "next-drupal": "^2.0.0-alpha.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@tailwindcss/typography": "^0.5.10", + "@types/node": "^20.10.0", + "@types/react": "^18.2.39", + "@types/react-dom": "^18.2.17", + "autoprefixer": "^10.4.16", + "eslint": "^8.54.0", + "eslint-config-next": "^14.0.3", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "tailwindcss": "^3.3.5", + "typescript": "^5.3.2" + } +} diff --git a/examples/example-router-migration/pages/_app.tsx b/examples/example-router-migration/pages/_app.tsx new file mode 100644 index 00000000..70739e9b --- /dev/null +++ b/examples/example-router-migration/pages/_app.tsx @@ -0,0 +1,6 @@ +import "@/styles/globals.css" +import type { AppProps } from "next/app" + +export default function App({ Component, pageProps }: AppProps) { + return +} diff --git a/examples/example-router-migration/pages/_document.tsx b/examples/example-router-migration/pages/_document.tsx new file mode 100644 index 00000000..097cb7ff --- /dev/null +++ b/examples/example-router-migration/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from "next/document" + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/examples/example-router-migration/pages/api/exit-preview.ts b/examples/example-router-migration/pages/api/exit-preview.ts new file mode 100644 index 00000000..f8847b39 --- /dev/null +++ b/examples/example-router-migration/pages/api/exit-preview.ts @@ -0,0 +1,9 @@ +import { drupal } from "@/lib/drupal" +import type { NextApiRequest, NextApiResponse } from "next" + +export default async function exit( + request: NextApiRequest, + response: NextApiResponse +) { + await drupal.previewDisable(request, response) +} diff --git a/examples/example-router-migration/pages/api/preview-only.ts b/examples/example-router-migration/pages/api/preview-only.ts new file mode 100644 index 00000000..26c04ffc --- /dev/null +++ b/examples/example-router-migration/pages/api/preview-only.ts @@ -0,0 +1,9 @@ +import { drupal } from "@/lib/drupal" +import type { NextApiRequest, NextApiResponse } from "next" + +export default async function preview( + request: NextApiRequest, + response: NextApiResponse +) { + await drupal.preview(request, response) +} diff --git a/examples/example-router-migration/pages/api/preview.ts b/examples/example-router-migration/pages/api/preview.ts new file mode 100644 index 00000000..a0733440 --- /dev/null +++ b/examples/example-router-migration/pages/api/preview.ts @@ -0,0 +1,10 @@ +import { drupal } from "@/lib/drupal" +import type { NextApiRequest, NextApiResponse } from "next" + +export default async function draft( + request: NextApiRequest, + response: NextApiResponse +) { + // Enables Preview mode and Draft mode. + await drupal.preview(request, response, { enable: true }) +} diff --git a/examples/example-router-migration/pages/pages-router/[...slug].tsx b/examples/example-router-migration/pages/pages-router/[...slug].tsx new file mode 100644 index 00000000..02ad87be --- /dev/null +++ b/examples/example-router-migration/pages/pages-router/[...slug].tsx @@ -0,0 +1,92 @@ +import Head from "next/head" +import { Article } from "@/components/drupal/Article" +import { BasicPage } from "@/components/drupal/BasicPage" +import { Layout } from "@/components/pages-router/Layout" +import { drupal } from "@/lib/drupal" +import type { + GetStaticPaths, + GetStaticProps, + InferGetStaticPropsType, +} from "next" +import type { DrupalNode } from "next-drupal" + +const RESOURCE_TYPES = ["node--page", "node--article"] + +export const getStaticPaths = (async (context) => { + return { + paths: await drupal.getStaticPathsFromContext(RESOURCE_TYPES, context), + fallback: "blocking", + } +}) satisfies GetStaticPaths + +export const getStaticProps = (async (context) => { + const path = await drupal.translatePathFromContext(context) + + if (!path) { + return { + notFound: true, + } + } + + const type = path?.jsonapi?.resourceName + + let params = {} + if (type === "node--article") { + params = { + include: "field_image,uid", + } + } + + const resource = await drupal.getResourceFromContext( + path, + context, + { + params, + } + ) + + // At this point, we know the path exists and it points to a resource. + // If we receive an error, it means something went wrong on Drupal. + // We throw an error to tell revalidation to skip this for now. + // Revalidation can try again on next request. + if (!resource) { + throw new Error(`Failed to fetch resource: ${path?.jsonapi?.individual}`) + } + + // If we're not in preview mode and the resource is not published, + // Return page not found. + if (!context.preview && resource?.status === false) { + return { + notFound: true, + } + } + + return { + props: { + resource, + }, + } +}) satisfies GetStaticProps<{ + resource: DrupalNode +}> + +export default function NodePage({ + resource, +}: InferGetStaticPropsType) { + if (!resource) return null + + return ( + + + {resource.title} + + + {resource.type === "node--page" && } + {resource.type === "node--article" &&
} + + ) +} diff --git a/examples/example-router-migration/pages/pages-router/index.tsx b/examples/example-router-migration/pages/pages-router/index.tsx new file mode 100644 index 00000000..5015df32 --- /dev/null +++ b/examples/example-router-migration/pages/pages-router/index.tsx @@ -0,0 +1,78 @@ +import Head from "next/head" +import { ArticleTeaser } from "@/components/drupal/ArticleTeaser" +import { Link } from "@/components/navigation/Link" +import { Layout } from "@/components/pages-router/Layout" +import { drupal } from "@/lib/drupal" +import type { InferGetStaticPropsType, GetStaticProps } from "next" +import type { DrupalNode } from "next-drupal" + +export const getStaticProps = (async (context) => { + const nodes = await drupal.getResourceCollectionFromContext( + "node--article", + context, + { + params: { + "filter[status]": 1, + "fields[node--article]": "title,path,field_image,uid,created", + include: "field_image,uid", + sort: "-created", + }, + } + ) + + return { + props: { + nodes, + }, + } +}) satisfies GetStaticProps<{ + nodes: DrupalNode[] +}> + +export default function Home({ + nodes, +}: InferGetStaticPropsType) { + return ( + + + Next.js for Drupal + + +

+ Latest Articles. +
+ + Using the Pages Router + +

+

+ Switch to{" "} + + App Router + +

+ {nodes?.length ? ( + nodes.map((node) => ( +
+ +
+
+ )) + ) : ( +

No nodes found

+ )} +
+ ) +} diff --git a/examples/example-router-migration/postcss.config.js b/examples/example-router-migration/postcss.config.js new file mode 100644 index 00000000..3fa0a951 --- /dev/null +++ b/examples/example-router-migration/postcss.config.js @@ -0,0 +1,8 @@ +// If you want to use other PostCSS plugins, see the following: +// https://tailwindcss.com/docs/using-with-preprocessors +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/examples/example-router-migration/public/favicon.ico b/examples/example-router-migration/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ea2f437d9db6552726693be6cc2943a32dc5a964 GIT binary patch literal 15086 zcmdU$X^c%<7ROI1lv$Z)$~+GtJY$|F!80Yq2f; >NSTKFL46_E}iL5mO)AEZS| zhz??kp>#q_Pa+X9S9!)q#7v>f>h;_1zJ2P{J?EY~6zP?$I_K=Y*ZTkWKIiUX?Ol$O z;bc0!dO3Q{aq`JLhPRsE;xZ>o<~Kd63*DEFk1M;XdugYEZ_Oa-Nh{-%0D^{MKAR3)TR78`8wfiHZP zw^UGo?y4J9e^;e_!*cd3hmYSYR;;*T(V|5?L_wL<$ z@a);MWqJ=*I%S|bJEdBpnlwj0+s2I>%gK``wdqCHu3cM_nVAU|n5l~P2F}l_<kDo{eQU1fr(;a=<7HsJ2X}tEcJ1Wh!-s|o-vjk==X>vm4I3s;pFTC^uUxqj`ki_GM4sUaXDiGB z1M$82Uj9v*H1V}>)TogZ6ciZlZ{NNZe!tdVl`2)FLWK&^XPX}43TK2l_;J;Qe7O1F zym?dn_`_}G%9Zlv%NN6c>(;H3m6c_%YSyeNGiT0}YSpSGgdO1uXSn~M`bQ)!w4bp2 zM~)mR&!0ax+^=4}D(&01k7CT8JzEYQJSbJGR*j}5zyr>3|BGrOYY&6v=6~zfEos`c zX&DZrojG&HaA%G)b?Ve;nGPK~n0}ObC*M#4ylq)H!=3ekwl>EI7{*43fg-In$8Z`Q0?)JKaJE#$(53x?l^4Y2;5_qa=u@U!I#kb9lRMnmBQyS&P7(aji?2E@j## zjr`%B)O^j$pT4G5t5(9C3@I)y7W&6n^-m*zxbqHT;sm98`L}J`RyJ+gWVrKZ=gytJ z<5C*=!#yp(Uya>F=k$yR-MV%2ZId+ew=q3mEWdZ)Zhz*&j~_oaZBM^4ckbLke`(}T z|C#dm75!-RX7}#hQoVZhZ^a+(DbF7)|GRhZO3$7>1G)1))71WU{=gh4rTH8DS;x$u zKR+(_H1fCeH|C(sMMGiRa`ZP39z3v3FMIdy73RY>Kj@mK_J=dvk%ZST+1c5qkGy{U zx|}+7D%5wQIHrEXhYy$c@86rcA3l6Ix*o9kA+L7*qQ%WxETQ!y82nC&L)EHPD^$B% zUiIqLGizbiz|8Y(dIDGP`Y}Uo6I#C~fRzltcsAbkyV5Z+iO}(r@5nz^`~?0;xyP>* zBVOAEPQS}LezcU8u3`m?0g{%YrLZ+zejpZEr&yd_fs z{vXe8#A+Aj;Qds;%lR3RqAbz38P#IPX~naS80-j1fer>-^K`?8s}ki8FWvd|Yv9`X z(Q(vF4ZjY0PVN6)i%8FMoC2=;*1Xp|(slOhy^@?x$-`L;oC4Bg@G2r*c{6aj>tpN5 z;+1;ab`YNRPqdv1BJF0|(Y7mPjDBr*KH?nJp{mhyTG{%LoX}TyR$ZXFSM{dq8& zlzY<1qYP!!(ZBP1WF5anwNUl5s@5`4DMQ&UBSwt)K^KF`@@T3))~IqGD;{QjQmLp-B2x4r?6?WX3a8t6!YfI zbNj$2zJrAR>bk1iE}{Frefx4=OZM#9BQ=Vj99`uI<4xp3H{u#VMAfx2z+gK zKRYnI-veXY>Qu1VpU^X2X8&Ztf(3rqHXST5t@PKMKU8ggsVn=C?59qfHqCc$e#(?7 z=DgFsefz@1o4^7S;WxfnwG^iNnX?$gw1_{)+7Is3sgn>dx_|$^aCXUU8*mRM*j-c$ z1In2_{YQ@;32{vKeik;g4SPYHUs$+sp~0?SzrJrfxp2S)oBlGeU*jBzAO39J3n50y z`48ev#E|mx@}y3kI`P^8OxvFami5E$-o3l9N6C4Ke*O9xABz_+ma}KihK+qOUc~Kd zzz3tSurRuxMT{*!Ki};CvqzzR%pN+wJTYo7$Z9lxVKtS|S~UH`6KyPCe0AnTGP zOG?=#i66TCsWW{X?SAy=QQ_Q^n`aXEV2Aqt=zrKl;+)KrCr@PX;K8M^;gJMB*!%{{ z_;RPw{`74K{mjy(ONDb&?lRmbfp6z)ar+;O&-h0F=EY6|-}XOo`(MUY&UCT|JY>j_ znAqSm_Os_l{}W=*qfl+YjywL)*7T?JtwCp^_{}p$>bx_s99&iH_@lRGEc6|JBjtVT zokYHS`~^Sd`A32rR!1q{@cJ12bJ@bbRw$%5zwP#FJo@PuU zjc3-pqeqX9`j3>w2HRbcXOqu4fzzi?NB=)!#flZOYSk*~+_`hCu@l?-ZhPh=*mJ&& z@s9JVy?ghTEnBw4Ix~%JsW}GroIPg#df>nT88c>#Ig^G@&Lz9CBDQ7hIfKf%80LhG z(X2-T>`U=)-MY0|r=L4_PB@ncUVuHerP_~m;IU)JWc>K?QLdb+mU@4D zAIvA*|69Yi$NbuzrgrxImHB==7+y^KY`-5ZZocQh@7Lp#viAGM9^o0EF@Har4la3= q@!D#Vku3e|`}>XLQ6?(ItsJLFQwlOQrkn9qqnVx?n@?G6u>CIsZs3vt literal 0 HcmV?d00001 diff --git a/examples/example-router-migration/public/robots.txt b/examples/example-router-migration/public/robots.txt new file mode 100644 index 00000000..14267e90 --- /dev/null +++ b/examples/example-router-migration/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / \ No newline at end of file diff --git a/examples/example-router-migration/styles/globals.css b/examples/example-router-migration/styles/globals.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/examples/example-router-migration/styles/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/examples/example-router-migration/tailwind.config.ts b/examples/example-router-migration/tailwind.config.ts new file mode 100644 index 00000000..c7f5c8a1 --- /dev/null +++ b/examples/example-router-migration/tailwind.config.ts @@ -0,0 +1,18 @@ +import type { Config } from "tailwindcss" + +const config: Config = { + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [require("@tailwindcss/typography")], +} + +export default config diff --git a/examples/example-router-migration/tsconfig.json b/examples/example-router-migration/tsconfig.json new file mode 100644 index 00000000..23ba4fd5 --- /dev/null +++ b/examples/example-router-migration/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/yarn.lock b/yarn.lock index 527040b8..80d79bc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2045,11 +2045,21 @@ resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.4.tgz#c787837d36fcad75d72ff8df6b57482027d64a47" integrity sha512-H/69Lc5Q02dq3o+dxxy5O/oNxFsZpdL6WREtOOtOM1B/weonIwDXkekr1KV5DPVPr12IHFPrMrcJQ6bgPMfn7A== +"@next/env@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.6.tgz#c1148e2e1aa166614f05161ee8f77ded467062bc" + integrity sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw== + "@next/env@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a" integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ== +"@next/env@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.0.tgz#43d92ebb53bc0ae43dcc64fb4d418f8f17d7a341" + integrity sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw== + "@next/eslint-plugin-next@12.3.4", "@next/eslint-plugin-next@^12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.3.4.tgz#e7dc00e2e89ed361f111d687b8534483ec15518b" @@ -2079,21 +2089,41 @@ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz#14ac8357010c95e67327f47082af9c9d75d5be79" integrity sha512-DqsSTd3FRjQUR6ao0E1e2OlOcrF5br+uegcEGPVonKYJpcr0MJrtYmPxd4v5T6UCJZ+XzydF7eQo5wdGvSZAyA== +"@next/swc-darwin-arm64@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz#b15d139d8971360fca29be3bdd703c108c9a45fb" + integrity sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA== + "@next/swc-darwin-arm64@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618" integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg== +"@next/swc-darwin-arm64@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz#70a57c87ab1ae5aa963a3ba0f4e59e18f4ecea39" + integrity sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ== + "@next/swc-darwin-x64@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz#e7dc63cd2ac26d15fb84d4d2997207fb9ba7da0f" integrity sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ== +"@next/swc-darwin-x64@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz#9c72ee31cc356cb65ce6860b658d807ff39f1578" + integrity sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA== + "@next/swc-darwin-x64@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b" integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw== +"@next/swc-darwin-x64@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz#0863a22feae1540e83c249384b539069fef054e9" + integrity sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g== + "@next/swc-freebsd-x64@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz#fe7ceec58746fdf03f1fcb37ec1331c28e76af93" @@ -2109,71 +2139,141 @@ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz#43a7bc409b03487bff5beb99479cacdc7bd29af5" integrity sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA== +"@next/swc-linux-arm64-gnu@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz#59f5f66155e85380ffa26ee3d95b687a770cfeab" + integrity sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg== + "@next/swc-linux-arm64-gnu@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21" integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w== +"@next/swc-linux-arm64-gnu@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz#893da533d3fce4aec7116fe772d4f9b95232423c" + integrity sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ== + "@next/swc-linux-arm64-musl@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz#4d1db6de6dc982b974cd1c52937111e3e4a34bd3" integrity sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ== +"@next/swc-linux-arm64-musl@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz#f012518228017052736a87d69bae73e587c76ce2" + integrity sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q== + "@next/swc-linux-arm64-musl@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd" integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ== +"@next/swc-linux-arm64-musl@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz#d81ddcf95916310b8b0e4ad32b637406564244c0" + integrity sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g== + "@next/swc-linux-x64-gnu@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz#c3b414d77bab08b35f7dd8943d5586f0adb15e38" integrity sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag== +"@next/swc-linux-x64-gnu@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz#339b867a7e9e7ee727a700b496b269033d820df4" + integrity sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw== + "@next/swc-linux-x64-gnu@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32" integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A== +"@next/swc-linux-x64-gnu@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz#18967f100ec19938354332dcb0268393cbacf581" + integrity sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ== + "@next/swc-linux-x64-musl@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz#187a883ec09eb2442a5ebf126826e19037313c61" integrity sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg== +"@next/swc-linux-x64-musl@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz#ae0ae84d058df758675830bcf70ca1846f1028f2" + integrity sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ== + "@next/swc-linux-x64-musl@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247" integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw== +"@next/swc-linux-x64-musl@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz#77077cd4ba8dda8f349dc7ceb6230e68ee3293cf" + integrity sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg== + "@next/swc-win32-arm64-msvc@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz#89befa84e453ed2ef9a888f375eba565a0fde80b" integrity sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ== +"@next/swc-win32-arm64-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz#a5cc0c16920485a929a17495064671374fdbc661" + integrity sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg== + "@next/swc-win32-arm64-msvc@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3" integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w== +"@next/swc-win32-arm64-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz#5f0b8cf955644104621e6d7cc923cad3a4c5365a" + integrity sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ== + "@next/swc-win32-ia32-msvc@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz#cb50c08f0e40ead63642a7f269f0c8254261f17c" integrity sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ== +"@next/swc-win32-ia32-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz#6a2409b84a2cbf34bf92fe714896455efb4191e4" + integrity sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg== + "@next/swc-win32-ia32-msvc@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600" integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg== +"@next/swc-win32-ia32-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz#21f4de1293ac5e5a168a412b139db5d3420a89d0" + integrity sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw== + "@next/swc-win32-x64-msvc@12.3.4": version "12.3.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz#d28ea15a72cdcf96201c60a43e9630cd7fda168f" integrity sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg== +"@next/swc-win32-x64-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz#4a3e2a206251abc729339ba85f60bc0433c2865d" + integrity sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ== + "@next/swc-win32-x64-msvc@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1" integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A== +"@next/swc-win32-x64-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz#e561fb330466d41807123d932b365cf3d33ceba2" + integrity sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg== + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -4060,6 +4160,11 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.300015 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== +caniuse-lite@^1.0.30001579: + version "1.0.30001580" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz#e3c76bc6fe020d9007647044278954ff8cd17d1e" + integrity sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA== + capture-stack-trace@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz#1c43f6b059d4249e7f3f8724f15f048b927d3a8a" @@ -5154,7 +5259,7 @@ drupal-jsonapi-params@^1.2.2: dependencies: qs "^6.10.0" -drupal-jsonapi-params@^2.0.0, drupal-jsonapi-params@^2.3.1: +drupal-jsonapi-params@^2.0.0, drupal-jsonapi-params@^2.1.0, drupal-jsonapi-params@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/drupal-jsonapi-params/-/drupal-jsonapi-params-2.3.1.tgz#7e91f9d7aa6b2b0793cf6b51fe19a5ffb9591278" integrity sha512-Isx6dudrFhORCl87OWHHO2LpQIAha5d6n7+/fwngsgcQs4PYeZcmTAA3pKUmvfyyFgCJ0N4fLAJQHyf4jtgiDA== @@ -8352,7 +8457,7 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" -jsona@^1.12.1: +jsona@^1.12.1, jsona@^1.9.7: version "1.12.1" resolved "https://registry.yarnpkg.com/jsona/-/jsona-1.12.1.tgz#213f8c55a3460d94179824ac83d1dfdbfa265238" integrity sha512-44WL4ZdsKx//mCDPUFQtbK7mnVdHXcVzbBy7Pzy0LAgXyfpN5+q8Hum7cLUX4wTnRsClHb4eId1hePZYchwczg== @@ -9479,6 +9584,28 @@ next-auth@^4.22.1: preact-render-to-string "^5.1.19" uuid "^8.3.2" +next-drupal-query@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/next-drupal-query/-/next-drupal-query-0.4.0.tgz#26def174b3449fd5365670c83f23f942cab8d845" + integrity sha512-05kDHr/I0OiwaXLQxhBHEZ1q6xIWYNSnli2TsiAgEtfmDo4K2mqGEnjPDUXzkXvtH/XfJ7y5hG83qydLKghiAA== + dependencies: + deepmerge "^4.2.2" + drupal-jsonapi-params "^2.1.0" + next "^12.2.3 || ^13" + type-fest "^2.17.0" + +next-drupal@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/next-drupal/-/next-drupal-1.6.0.tgz#4bc14f73a5dc55763e461da696fc938da2bb6b67" + integrity sha512-IRHgcpidXj45jicVl2wEp2WhyaV384rfubxxWopgbmo4YKYvIrg0GtPj3EQNuuX5/EJxyZcULHmmhSXFSidlpg== + dependencies: + jsona "^1.9.7" + next "^12.2.0 || ^13" + node-cache "^5.1.2" + qs "^6.10.3" + react "^17.0.2 || ^18" + react-dom "^17.0.2 || ^18" + next-i18next@^13.1.5: version "13.3.0" resolved "https://registry.yarnpkg.com/next-i18next/-/next-i18next-13.3.0.tgz#81575b0e17a94efa97319c1e697af893a4cea174" @@ -9529,29 +9656,28 @@ next-seo@^4.23.0: resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-4.29.0.tgz#d281e95ba47914117cc99e9e468599f0547d9b9b" integrity sha512-xmwzcz4uHaYJ8glbuhs6FSBQ7z3irmdPYdJJ5saWm72Uy3o+mPKGaPCXQetTCE6/xxVnpoDV4yFtFlEjUcljSg== -"next@^12.2.0 || ^13 || ^14", "next@^12.2.3 || ^13 || ^14", next@^14: - version "14.0.4" - resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc" - integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA== +"next@^12.2.0 || ^13", "next@^12.2.3 || ^13": + version "13.5.6" + resolved "https://registry.yarnpkg.com/next/-/next-13.5.6.tgz#e964b5853272236c37ce0dd2c68302973cf010b1" + integrity sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw== dependencies: - "@next/env" "14.0.4" + "@next/env" "13.5.6" "@swc/helpers" "0.5.2" busboy "1.6.0" caniuse-lite "^1.0.30001406" - graceful-fs "^4.2.11" postcss "8.4.31" styled-jsx "5.1.1" watchpack "2.4.0" optionalDependencies: - "@next/swc-darwin-arm64" "14.0.4" - "@next/swc-darwin-x64" "14.0.4" - "@next/swc-linux-arm64-gnu" "14.0.4" - "@next/swc-linux-arm64-musl" "14.0.4" - "@next/swc-linux-x64-gnu" "14.0.4" - "@next/swc-linux-x64-musl" "14.0.4" - "@next/swc-win32-arm64-msvc" "14.0.4" - "@next/swc-win32-ia32-msvc" "14.0.4" - "@next/swc-win32-x64-msvc" "14.0.4" + "@next/swc-darwin-arm64" "13.5.6" + "@next/swc-darwin-x64" "13.5.6" + "@next/swc-linux-arm64-gnu" "13.5.6" + "@next/swc-linux-arm64-musl" "13.5.6" + "@next/swc-linux-x64-gnu" "13.5.6" + "@next/swc-linux-x64-musl" "13.5.6" + "@next/swc-win32-arm64-msvc" "13.5.6" + "@next/swc-win32-ia32-msvc" "13.5.6" + "@next/swc-win32-x64-msvc" "13.5.6" next@^12.2.3: version "12.3.4" @@ -9579,6 +9705,53 @@ next@^12.2.3: "@next/swc-win32-ia32-msvc" "12.3.4" "@next/swc-win32-x64-msvc" "12.3.4" +"next@^12.2.3 || ^13 || ^14", next@^14: + version "14.0.4" + resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc" + integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA== + dependencies: + "@next/env" "14.0.4" + "@swc/helpers" "0.5.2" + busboy "1.6.0" + caniuse-lite "^1.0.30001406" + graceful-fs "^4.2.11" + postcss "8.4.31" + styled-jsx "5.1.1" + watchpack "2.4.0" + optionalDependencies: + "@next/swc-darwin-arm64" "14.0.4" + "@next/swc-darwin-x64" "14.0.4" + "@next/swc-linux-arm64-gnu" "14.0.4" + "@next/swc-linux-arm64-musl" "14.0.4" + "@next/swc-linux-x64-gnu" "14.0.4" + "@next/swc-linux-x64-musl" "14.0.4" + "@next/swc-win32-arm64-msvc" "14.0.4" + "@next/swc-win32-ia32-msvc" "14.0.4" + "@next/swc-win32-x64-msvc" "14.0.4" + +"next@^13.4 || ^14": + version "14.1.0" + resolved "https://registry.yarnpkg.com/next/-/next-14.1.0.tgz#b31c0261ff9caa6b4a17c5af019ed77387174b69" + integrity sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q== + dependencies: + "@next/env" "14.1.0" + "@swc/helpers" "0.5.2" + busboy "1.6.0" + caniuse-lite "^1.0.30001579" + graceful-fs "^4.2.11" + postcss "8.4.31" + styled-jsx "5.1.1" + optionalDependencies: + "@next/swc-darwin-arm64" "14.1.0" + "@next/swc-darwin-x64" "14.1.0" + "@next/swc-linux-arm64-gnu" "14.1.0" + "@next/swc-linux-arm64-musl" "14.1.0" + "@next/swc-linux-x64-gnu" "14.1.0" + "@next/swc-linux-x64-musl" "14.1.0" + "@next/swc-win32-arm64-msvc" "14.1.0" + "@next/swc-win32-ia32-msvc" "14.1.0" + "@next/swc-win32-x64-msvc" "14.1.0" + node-abi@^3.3.0: version "3.52.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.52.0.tgz#ffba0a85f54e552547e5849015f40f9514d5ba7c" @@ -12743,6 +12916,11 @@ type-fest@^0.8.0, type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^2.17.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + type-fest@^3.0.0: version "3.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706"