diff --git a/.changeset/eleven-experts-feel.md b/.changeset/eleven-experts-feel.md new file mode 100644 index 00000000..afe67725 --- /dev/null +++ b/.changeset/eleven-experts-feel.md @@ -0,0 +1,23 @@ +--- +"@snapwp/blocks": minor +"@snapwp/query": minor +"@snapwp/types": minor +"@snapwp/core": minor +"@snapwp/next": minor +"snapwp": minor +--- + +feat!: Add and improve robust WordPress and internal URI handling. + +**Breaking Changes:** + +The [Environment Variables and Config API](../docs/config-api.md) have been updated, with many of the variables renamed or removed. Please review the updated documentation for the latest changes. + +| Old value | Replace with | +|----------|----------| +| NEXT_PUBLIC_URL | NEXT_PUBLIC_FRONTEND_URL | +| NEXT_PUBLIC_WORDPRESS_URL | NEXT_PUBLIC_WP_HOME_URL | +| NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH | NEXT_PUBLIC_WP_UPLOADS_DIRECTORY | +| NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX | NEXT_PUBLIC_REST_URL_PREFIX | +| getConfig().nextUrl | getConfig().frontendUrl | +| getConfig().homeUrl | getConfig().wpHomeUrl | diff --git a/.changeset/salty-moments-yell.md b/.changeset/salty-moments-yell.md new file mode 100644 index 00000000..1912b04b --- /dev/null +++ b/.changeset/salty-moments-yell.md @@ -0,0 +1,5 @@ +--- +"@snapwp/core": minor +--- + +dev!: Reorganize core `utils` into type-based subdirectories. diff --git a/.env.example b/.env.example index 1f2b863b..782b8721 100644 --- a/.env.example +++ b/.env.example @@ -1,20 +1,27 @@ -# Enable if connecting to a self-signed cert +# Enable if connecting to a self-signed cert. # NODE_TLS_REJECT_UNAUTHORIZED=0 -# The headless frontend domain URL. Uncomment this line and ensure the value matches the URL used by your frontend app. -# NEXT_PUBLIC_URL=http://localhost:3000 +# The URL of the Next.js "headless" frontend. +NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000 -# The WordPress "frontend" domain URL -NEXT_PUBLIC_WORDPRESS_URL=https://headless-demo.local +# The traditional WordPress frontend domain URL. E.g. https://my-headless-site.local +NEXT_PUBLIC_WP_HOME_URL= -# The WordPress GraphQL endpoint +# The backend "WordPress Address" URL where your WordPress core files reside. +# Only needed if different than `NEXT_PUBLIC_WP_HOME_URL`. E.g. https://my-headless-site.local/wp/ +# NEXT_PUBLIC_WP_SITE_URL= + +# The WordPress GraphQL endpoint. NEXT_PUBLIC_GRAPHQL_ENDPOINT=graphql -# The WordPress Uploads directory path -# NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH=/wp-content/uploads +# The WordPress REST API URL prefix. +# NEXT_PUBLIC_REST_URL_PREFIX=/wp-json + +# The relative path to the WordPress uploads directory. +# NEXT_PUBLIC_WP_UPLOADS_DIRECTORY=/wp-content/uploads -# The WordPress REST URL Prefix -# NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX=/wp-json +# The CORS proxy prefix to use when bypassing CORS restrictions from WordPress server. If unset, no proxy will be used. +# NEXT_PUBLIC_CORS_PROXY_PREFIX=/proxy -# Token used for authenticating GraphQL introspection queries +# Token used for authenticating GraphQL introspection queries. INTROSPECTION_TOKEN= diff --git a/.github/workflows/pull_request_examples.yml b/.github/workflows/pull_request_examples.yml index 67d7235d..bcaaed56 100644 --- a/.github/workflows/pull_request_examples.yml +++ b/.github/workflows/pull_request_examples.yml @@ -99,15 +99,18 @@ jobs: npm run --if-present build - name: Install dependencies + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm install + + - name: Copy .env.example to .env + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm install cp .env.example .env + sed -i 's|NEXT_PUBLIC_WP_HOME_URL=.*|NEXT_PUBLIC_WP_HOME_URL=http://localhost|' .env - name: Run ESLint - run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm run --if-present lint + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm run --if-present lint typecheck: name: TS Typecheck @@ -148,16 +151,23 @@ jobs: npm run --if-present build - name: Install dependencies + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm install + + - name: Copy .env.example to .env + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm install cp .env.example .env - npm run codegen + sed -i 's|NEXT_PUBLIC_WP_HOME_URL=.*|NEXT_PUBLIC_WP_HOME_URL=http://localhost|' .env + + - name: Run Codegen + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm run codegen - name: Run TypeScript type check # TODO: Why are we catching the error? - run: | - npm run --if-present typecheck + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm run --if-present typecheck test-build: name: Test Build (Node v${{ matrix.node-version }} | ${{ matrix.example }}) @@ -240,15 +250,18 @@ jobs: npm run --if-present build - name: Install dependencies + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm install + + - name: Copy .env.example to .env + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm install cp .env.example .env + sed -i 's|NEXT_PUBLIC_WP_HOME_URL=.*|NEXT_PUBLIC_WP_HOME_URL=http://localhost|' .env - name: Build assets - run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm run --if-present build + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm run --if-present build unit: name: Unit Tests (Node v${{ matrix.node-version }} | ${{ matrix.example }}) @@ -290,15 +303,18 @@ jobs: npm run --if-present build - name: Install dependencies + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm install + + - name: Copy .env.example to .env + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm install cp .env.example .env + sed -i 's|NEXT_PUBLIC_WP_HOME_URL=.*|NEXT_PUBLIC_WP_HOME_URL=http://localhost|' .env - name: Run unit tests - run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm run --if-present test + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm run --if-present test security: name: Dependencies Audit @@ -339,15 +355,18 @@ jobs: npm run --if-present build - name: Install dependencies + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm install + + - name: Copy .env.example to .env + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm install cp .env.example .env + sed -i 's|NEXT_PUBLIC_WP_HOME_URL=.*|NEXT_PUBLIC_WP_HOME_URL=http://localhost|' .env - name: Run npm audit - run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm audit --audit-level=high + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm audit --audit-level=high prettier: name: Check Formatting @@ -388,12 +407,15 @@ jobs: npm run --if-present build - name: Install dependencies + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm install + + - name: Copy .env.example to .env + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm install cp .env.example .env + sed -i 's|NEXT_PUBLIC_WP_HOME_URL=.*|NEXT_PUBLIC_WP_HOME_URL=http://localhost|' .env - name: Run Prettier - run: | - cd '${{ github.workspace }}/examples/${{ matrix.example }}' - npm run --if-present format-check + working-directory: ${{ github.workspace }}/examples/${{ matrix.example }} + run: npm run --if-present format-check diff --git a/docs/config-api.md b/docs/config-api.md index 92f4728d..c807954e 100644 --- a/docs/config-api.md +++ b/docs/config-api.md @@ -15,16 +15,16 @@ SnapWP uses the following `.env` variables to configure your Next.js app. > We recommend copying the `.env` variables from the SnapWP Helper plugin settings screen and pasting them into your `.env` file, then modifying them as needed. > See the [Getting Started](getting-started.md#backend-setup) guide for more information. -| Variable | Required | Default Value | Description | Available via `getConfig() | -| --------------------------------------- | -------- | ---------------------------------------- | --------------------------------------------------------------------------------- | -------------------------- | -| `NEXT_PUBLIC_URL` | Yes | | The URL of the Next.js site. | `nextUrl` | -| `NEXT_PUBLIC_WORDPRESS_URL` | Yes | | The WordPress frontend domain URL. | `homeUrl` | -| `INTROSPECTION_TOKEN` | Yes | | Token used for authenticating GraphQL introspection queries with GraphQL Codegen. | N/A | -| `NEXT_PUBLIC_GRAPHQL_ENDPOINT` | No | `index.php?graphql` | The relative path to the WordPress GraphQL endpoint. | `graphqlEndpoint` | -| `NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH` | No | `/wp-content/uploads` | The relative path to the WordPress uploads directory. | `uploadsDirectory` | -| `NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX` | No | `/wp-json` | The WordPress REST API URL prefix. | `restUrlPrefix` | -| `NEXT_PUBLIC_USE_CORS_PROXY` | No | `process.env.NODE_ENV === 'development'` | Whether to use a CORS proxy for the WordPress API. | `useCorsProxy` | -| `NEXT_PUBLIC_CORS_PROXY_PREFIX` | No | `/proxy` | The prefix of the CORS proxy. | `corsProxyPrefix` | +| Variable | Required | Default Value | Description | Available via `getConfig() | +| ---------------------------------- | -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- | +| `NEXT_PUBLIC_FRONTEND_URL` | Yes | | The URL of the Next.js (headless) frontend.
E.g. `http://localhost:3000` | `frontendUrl` | +| `NEXT_PUBLIC_WP_HOME_URL` | Yes | | The traditional WordPress _frontend_ domain URL.
E.g `https://mywpsite.local` | `wpHomeUrl` | +| `NEXT_PUBLIC_WP_SITE_URL` | No | | The _backend_ "WordPress Address" domain URL where your WordPress core files reside.
Only necessary if different than `NEXT_PUBLIC_WP_HOME_URL`
E.g. `https://api.mywpsite.local/wp` . | `wpSiteUrl` | +| `NEXT_PUBLIC_GRAPHQL_ENDPOINT` | No | `index.php?graphql` | The relative path to the WordPress GraphQL endpoint. | `graphqlEndpoint` | +| `NEXT_PUBLIC_REST_URL_PREFIX` | No | `/wp-json` | The WordPress REST API URL prefix. | `restUrlPrefix` | +| `NEXT_PUBLIC_WP_UPLOADS_DIRECTORY` | No | `/wp-content/uploads` | The relative path to the WordPress uploads directory. | `uploadsDirectory` | +| `NEXT_PUBLIC_CORS_PROXY_PREFIX` | No | | The prefix for the CORS proxy. If unset, no proxy will be used. | `corsProxyPrefix` | +| `INTROSPECTION_TOKEN` | Yes | | Token used for authenticating GraphQL introspection queries with GraphQL Codegen. | N/A | Additionally, if you are running a local development environment without a valid SSL certificate, you can set the following environment variable: @@ -82,5 +82,5 @@ You can access the configuration values in your application code using the `getC import { getConfig } from '@snapwp/core/config'; // Or any other valid configuration property. -const { nextUrl, homeUrl, parserOptions } = getConfig(); +const { frontendUrl, wpHomeUrl, parserOptions } = getConfig(); ``` diff --git a/docs/cors.md b/docs/cors.md index a9f9c430..a3c30e32 100644 --- a/docs/cors.md +++ b/docs/cors.md @@ -62,25 +62,8 @@ To further mitigate CORS issues, SnapWP includes a CORS middleware that proxies ### Enabling the CORS Middleware -To enable the CORS proxy feature, update your `snapwp.config.mjs` file: +To enable the CORS proxy feature, update your `.env` file: -```javascript -/** @type {import('@snapwp/core/config').SnapWPConfig} */ -const config = { - // Other configuration options - useCorsProxy: true, -}; - -export default config; -``` - -### Customizing the Proxy Prefix - -By default, the proxy prefix is set to `/proxy`. If needed, you can override this by specifying `corsProxyPrefix` in the configuration. - -```javascript -const config = { - useCorsProxy: process.env.NODE_ENV !== 'production', // Enable CORS proxy in nonproduction environments. - corsProxyPrefix: '/custom-proxy', // Optional custom prefix -}; +```dotenv +NEXT_PUBLIC_CORS_PROXY_PREFIX={prefix-value} ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 7c4f47ac..a742045d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -70,7 +70,7 @@ To create a new headless WordPress app using SnapWP, follow these steps: 1. Navigate to the newly created app. 2. Run `npm install`. 3. Run `npm run dev` (for development) or `npm run build && npm run start` (for production) - 4. Visit the `NEXT_PUBLIC_URL` from `.env` (updated in Step 2), in your browser to see SnapWP in action! + 4. Visit the `NEXT_PUBLIC_FRONTEND_URL` from `.env` (updated in Step 2), in your browser to see SnapWP in action! ### Manual Installation diff --git a/examples/nextjs/starter/.env.example b/examples/nextjs/starter/.env.example index 55d19010..782b8721 100644 --- a/examples/nextjs/starter/.env.example +++ b/examples/nextjs/starter/.env.example @@ -1,20 +1,27 @@ -# Enable if connecting to a self-signed cert -# NODE_TLS_REJECT_UNAUTHORIZED="0" +# Enable if connecting to a self-signed cert. +# NODE_TLS_REJECT_UNAUTHORIZED=0 -# The headless frontend domain URL -NEXT_PUBLIC_URL="http://localhost:3000" +# The URL of the Next.js "headless" frontend. +NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000 -# The WordPress "frontend" domain URL -NEXT_PUBLIC_WORDPRESS_URL="http://localhost" +# The traditional WordPress frontend domain URL. E.g. https://my-headless-site.local +NEXT_PUBLIC_WP_HOME_URL= -# The WordPress GraphQL endpoint -NEXT_PUBLIC_GRAPHQL_ENDPOINT="graphql" +# The backend "WordPress Address" URL where your WordPress core files reside. +# Only needed if different than `NEXT_PUBLIC_WP_HOME_URL`. E.g. https://my-headless-site.local/wp/ +# NEXT_PUBLIC_WP_SITE_URL= -# The WordPress Uploads directory path -# NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH=/wp-content/uploads +# The WordPress GraphQL endpoint. +NEXT_PUBLIC_GRAPHQL_ENDPOINT=graphql -# The WordPress REST URL Prefix -# NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX=/wp-json +# The WordPress REST API URL prefix. +# NEXT_PUBLIC_REST_URL_PREFIX=/wp-json -# Token used for authenticating GraphQL introspection queries +# The relative path to the WordPress uploads directory. +# NEXT_PUBLIC_WP_UPLOADS_DIRECTORY=/wp-content/uploads + +# The CORS proxy prefix to use when bypassing CORS restrictions from WordPress server. If unset, no proxy will be used. +# NEXT_PUBLIC_CORS_PROXY_PREFIX=/proxy + +# Token used for authenticating GraphQL introspection queries. INTROSPECTION_TOKEN= diff --git a/examples/nextjs/starter/codegen.ts b/examples/nextjs/starter/codegen.ts index b583c1a2..fdec7992 100644 --- a/examples/nextjs/starter/codegen.ts +++ b/examples/nextjs/starter/codegen.ts @@ -14,7 +14,7 @@ const config: CodegenConfig = { schema: process.env.GRAPHQL_SCHEMA_FILE ?? [ { [ generateGraphqlUrl( - process.env.NEXT_PUBLIC_WORDPRESS_URL, + process.env.NEXT_PUBLIC_WP_HOME_URL, process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT ) ]: { headers: { diff --git a/packages/blocks/jest.setup.js b/packages/blocks/jest.setup.js index 11972c02..0c75fdfe 100644 --- a/packages/blocks/jest.setup.js +++ b/packages/blocks/jest.setup.js @@ -4,17 +4,18 @@ import 'isomorphic-fetch'; global.__snapWPConfig = {}; global.__envConfig = { - nextUrl: 'https://env-next.example.com', - homeUrl: 'https://env-home.example.com', + frontendUrl: 'https://env-next.example.com', + wpHomeUrl: 'https://env-home.example.com', + wpSiteUrl: 'https://env-home.example.com', graphqlEndpoint: 'env-index.php?graphql', uploadsDirectory: '/env-wp-content/uploads', restUrlPrefix: '/env-wp-json', }; -process.env.NEXT_PUBLIC_URL = global.__envConfig.nextUrl; -process.env.NEXT_PUBLIC_WORDPRESS_URL = global.__envConfig.homeUrl; +process.env.NEXT_PUBLIC_FRONTEND_URL = global.__envConfig.frontendUrl; +process.env.NEXT_PUBLIC_WP_HOME_URL = global.__envConfig.wpHomeUrl; +process.env.NEXT_PUBLIC_WP_SITE_URL = global.__envConfig.wpSiteUrl; process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT = global.__envConfig.graphqlEndpoint; -process.env.NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH = +process.env.NEXT_PUBLIC_WP_UPLOADS_DIRECTORY = global.__envConfig.uploadsDirectory; -process.env.NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX = - global.__envConfig.restUrlPrefix; +process.env.NEXT_PUBLIC_REST_URL_PREFIX = global.__envConfig.restUrlPrefix; diff --git a/packages/blocks/src/blocks/core-button.tsx b/packages/blocks/src/blocks/core-button.tsx index 55a8461e..d173526a 100644 --- a/packages/blocks/src/blocks/core-button.tsx +++ b/packages/blocks/src/blocks/core-button.tsx @@ -1,11 +1,10 @@ -import type { ButtonHTMLAttributes } from 'react'; -import { cn, getStylesFromAttributes, replaceHostUrl } from '@snapwp/core'; -import { getConfig } from '@snapwp/core/config'; +import { cn, getStylesFromAttributes } from '@snapwp/core'; import { Link, Parse } from '@snapwp/next'; import type { CoreButton as CoreButtonType, CoreButtonProps, } from '@snapwp/types'; +import type { ButtonHTMLAttributes } from 'react'; /** * Renders the core/button block. @@ -31,7 +30,6 @@ const CoreButton: CoreButtonType = ( { attributes }: CoreButtonProps ) => { const classNames = cn( cssClassName ); const styleObject = getStylesFromAttributes( { style } ); - const { homeUrl, nextUrl } = getConfig(); const commonProps = { className: linkClassName ?? undefined, @@ -55,12 +53,10 @@ const CoreButton: CoreButtonType = ( { attributes }: CoreButtonProps ) => { } if ( url ) { - const href = replaceHostUrl( url, homeUrl, nextUrl ); - return (
{ } ); test( 'renders with corrected url', () => { - const { homeUrl, nextUrl } = getConfig(); + const { wpHomeUrl } = getConfig(); const exampleAttributes = { cssClassName: 'test-class', linkClassName: 'link-class', style: JSON.stringify( { color: 'red' } ), text: 'Click Me', title: 'Test Button', - url: homeUrl + '/sub', + url: wpHomeUrl + '/sub', tagName: 'a', rel: 'noopener', linkTarget: '_blank', @@ -79,7 +79,7 @@ describe( 'CoreButton Component', () => { ); const linkElement = screen.getByRole( 'link', { name: 'Click Me' } ); - expect( linkElement ).toHaveAttribute( 'href', nextUrl + '/sub' ); + expect( linkElement ).toHaveAttribute( 'href', '/sub' ); expect( asFragment() ).toMatchSnapshot(); } ); diff --git a/packages/cli/README.md b/packages/cli/README.md index 65a41d3f..dfbd7f6b 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -56,7 +56,7 @@ To create a new headless WordPress app using SnapWP, follow these steps: 1. Navigate to the newly created app. 2. Run `npm install`. 3. Run `npm run dev` (for development) or `npm run build && npm run start` (for production) - 4. Visit the `NEXT_PUBLIC_URL` from `.env` (updated in Step 2), in your browser to see SnapWP in action! + 4. Visit the `NEXT_PUBLIC_FRONTEND_URL` from `.env` (updated in Step 2), in your browser to see SnapWP in action! ## Contributing diff --git a/packages/core/README.md b/packages/core/README.md index 3d8cdb0a..5f964bb3 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -27,7 +27,7 @@ const classNames = findElementAndGetClassNames( renderedHTML, '#div-2' ); // cla ```javascript import { generateGraphqlUrl } from '@snapwp/core'; -const url = generateGraphqlUrl( homeUrl, graphqlEndpoint ); +const url = generateGraphqlUrl( wpHomeUrl, graphqlEndpoint ); ``` ```javascript diff --git a/packages/core/jest.setup.js b/packages/core/jest.setup.js index daf3a7db..64f5ee61 100644 --- a/packages/core/jest.setup.js +++ b/packages/core/jest.setup.js @@ -1,17 +1,18 @@ global.__snapWPConfig = {}; global.__envConfig = { - nextUrl: 'https://env-next.example.com', - homeUrl: 'https://env-home.example.com', + frontendUrl: 'https://env-next.example.com', + wpHomeUrl: 'https://env-home.example.com', + wpSiteUrl: 'https://env-home.example.com/wp', graphqlEndpoint: 'env-index.php?graphql', uploadsDirectory: '/env-wp-content/uploads', restUrlPrefix: '/env-wp-json', }; -process.env.NEXT_PUBLIC_URL = global.__envConfig.nextUrl; -process.env.NEXT_PUBLIC_WORDPRESS_URL = global.__envConfig.homeUrl; +process.env.NEXT_PUBLIC_FRONTEND_URL = global.__envConfig.frontendUrl; +process.env.NEXT_PUBLIC_WP_HOME_URL = global.__envConfig.wpHomeUrl; +process.env.NEXT_PUBLIC_WP_SITE_URL = global.__envConfig.wpSiteUrl; process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT = global.__envConfig.graphqlEndpoint; -process.env.NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH = +process.env.NEXT_PUBLIC_WP_UPLOADS_DIRECTORY = global.__envConfig.uploadsDirectory; -process.env.NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX = - global.__envConfig.restUrlPrefix; +process.env.NEXT_PUBLIC_REST_URL_PREFIX = global.__envConfig.restUrlPrefix; diff --git a/packages/core/src/config/snapwp-config-manager.ts b/packages/core/src/config/snapwp-config-manager.ts index b6dc5d16..fa0c44e8 100644 --- a/packages/core/src/config/snapwp-config-manager.ts +++ b/packages/core/src/config/snapwp-config-manager.ts @@ -6,35 +6,33 @@ import type { HTMLReactParserOptions } from 'html-react-parser'; export interface SnapWPEnv { /** - * The URL of the Next.js site. Defaults to `process.env.NEXT_PUBLIC_URL`. + * URL prefix for WP assets loaded from 'wp-includes' dir . Defaults to `/proxy`. */ - nextUrl: string; + corsProxyPrefix?: string | undefined; /** - * The home URL of the WordPress site. Defaults to `process.env.NEXT_PUBLIC_WORDPRESS_URL`. - * - * TODO: Update variable name from homeUrl to siteUrl. ref: https://github.com/rtCamp/headless/pull/272#discussion_r1907601796 + * The URL of the Next.js site. Defaults to `process.env.NEXT_PUBLIC_FRONTEND_URL`. */ - homeUrl: string; + frontendUrl: string; /** * The GraphQL endpoint. Defaults to `graphql`. */ graphqlEndpoint: string; - /** - * Uploads directory. Defaults to `/wp-content/uploads`. - */ - uploadsDirectory: string; /** * REST URL prefix. Defaults to `/wp-json`. */ restUrlPrefix: string; /** - * URL prefix for WP assets loaded from 'wp-includes' dir . Defaults to `/proxy`. + * Uploads directory. Defaults to `/wp-content/uploads`. + */ + uploadsDirectory: string; + /** + * The home URL of the WordPress site. Defaults to `process.env.NEXT_PUBLIC_WP_HOME_URL`. */ - corsProxyPrefix: string; + wpHomeUrl: string; /** - * Flag to enable cors middleware which proxies assets from WP server. + * The site URL of the WordPress site. Defaults to `process.env.NEXT_PUBLIC_WP_SITE_URL`. */ - useCorsProxy?: boolean; + wpSiteUrl: string; } export interface SnapWPConfig { @@ -63,12 +61,12 @@ type ConfigSchema< T > = { * Default configuration. */ const defaultConfig: Partial< SnapWPEnv & SnapWPConfig > = { + corsProxyPrefix: + // eslint-disable-next-line n/no-process-env -- We're using `NODE_ENV` to derive a default value. + process.env.NODE_ENV === 'development' ? '/proxy' : undefined, graphqlEndpoint: 'index.php?graphql', - uploadsDirectory: '/wp-content/uploads', restUrlPrefix: '/wp-json', - corsProxyPrefix: '/proxy', - // eslint-disable-next-line n/no-process-env -- We're using `NODE_ENV` to derive a default value. - useCorsProxy: process.env.NODE_ENV === 'development', + uploadsDirectory: '/wp-content/uploads', }; /** @@ -81,13 +79,16 @@ const defaultConfig: Partial< SnapWPEnv & SnapWPConfig > = { // @ts-ignore - ignore check for nextUrl,homeUrl to run missing environment variable test. const envConfig = (): Partial< SnapWPEnv > => ( { /* eslint-disable n/no-process-env -- These are the env variables we want to manage. */ - nextUrl: process.env.NEXT_PUBLIC_URL, - homeUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL, - graphqlEndpoint: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT, - uploadsDirectory: process.env.NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH, - restUrlPrefix: process.env.NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX, - useCorsProxy: process.env.NEXT_PUBLIC_USE_CORS_PROXY === 'true', corsProxyPrefix: process.env.NEXT_PUBLIC_CORS_PROXY_PREFIX, + frontendUrl: process.env.NEXT_PUBLIC_FRONTEND_URL, + graphqlEndpoint: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT, + wpHomeUrl: process.env.NEXT_PUBLIC_WP_HOME_URL, + restUrlPrefix: process.env.NEXT_PUBLIC_REST_URL_PREFIX, + // If `wpSiteUrl` is not provided, use `wpHomeUrl`. + wpSiteUrl: + process.env.NEXT_PUBLIC_WP_SITE_URL || + process.env.NEXT_PUBLIC_WP_HOME_URL, + uploadsDirectory: process.env.NEXT_PUBLIC_WP_UPLOADS_DIRECTORY, /* eslint-enable n/no-process-env -- Rule restored. */ } ); @@ -123,7 +124,11 @@ class SnapWPConfigManager { * The schema used to validate the configuration. */ static snapWPConfigEnvSchema: ConfigSchema< SnapWPEnv > = { - nextUrl: { + corsProxyPrefix: { + type: 'string', + required: false, + }, + frontendUrl: { type: 'string', required: true, /** @@ -134,11 +139,15 @@ class SnapWPConfigManager { */ validate: ( value ) => { if ( value && ! isValidUrl( value ) ) { - throw new Error( '`nextUrl` should be a valid URL.' ); + throw new Error( '`frontendUrl` should be a valid URL.' ); } }, }, - homeUrl: { + graphqlEndpoint: { + type: 'string', + required: false, + }, + wpHomeUrl: { type: 'string', required: true, /** @@ -149,29 +158,22 @@ class SnapWPConfigManager { */ validate: ( value ) => { if ( value && ! isValidUrl( value ) ) { - throw new Error( '`homeUrl` should be a valid URL.' ); + throw new Error( '`wpHomeUrl` should be a valid URL.' ); } }, }, - graphqlEndpoint: { - type: 'string', - required: false, - }, - uploadsDirectory: { + wpSiteUrl: { type: 'string', required: false, /** - * Validate the uploads directory. + * Validate the URL. * * @param value The value to validate. - * * @throws {Error} If the value is invalid. */ validate: ( value ) => { - if ( value && ! value.startsWith( '/' ) ) { - throw new Error( - '`uploadsDirectory` should start with a forward slash.' - ); + if ( value && ! isValidUrl( value ) ) { + throw new Error( '`wpSiteUrl` should be a valid URL.' ); } }, }, @@ -193,11 +195,11 @@ class SnapWPConfigManager { } }, }, - corsProxyPrefix: { + uploadsDirectory: { type: 'string', required: false, /** - * Validate the CORS proxy prefix. + * Validate the uploads directory. * * @param value The value to validate. * @@ -206,15 +208,11 @@ class SnapWPConfigManager { validate: ( value ) => { if ( value && ! value.startsWith( '/' ) ) { throw new Error( - '`corsProxyPrefix` should start with a forward slash.' + '`uploadsDirectory` should start with a forward slash.' ); } }, }, - useCorsProxy: { - type: 'boolean', - required: false, - }, }; /** @@ -231,7 +229,9 @@ class SnapWPConfigManager { delete cfg[ key ]; } else if ( // @todo this should probably be moved into the schema as a sanitize callback. - ( key === 'homeUrl' || key === 'nextUrl' ) && + ( key === 'wpHomeUrl' || + key === 'frontendUrl' || + key === 'wpSiteUrl' ) && typeof cfg[ key ] === 'string' ) { const value = cfg[ key ] as string; @@ -368,7 +368,7 @@ class SnapWPConfigManager { */ static getGraphqlUrl(): string { return generateGraphqlUrl( - SnapWPConfigManager.getConfig().homeUrl, + SnapWPConfigManager.getConfig().wpHomeUrl, SnapWPConfigManager.getConfig().graphqlEndpoint ); } diff --git a/packages/core/src/config/tests/snapwp-config-manager.test.tsx b/packages/core/src/config/tests/snapwp-config-manager.test.tsx index c2e80453..75bf6bed 100644 --- a/packages/core/src/config/tests/snapwp-config-manager.test.tsx +++ b/packages/core/src/config/tests/snapwp-config-manager.test.tsx @@ -6,6 +6,7 @@ import { getGraphqlUrl, type SnapWPEnv, } from '@/config/snapwp-config-manager'; + const SnapWPConfigManager = _private.SnapWPConfigManager!; /** @@ -19,19 +20,21 @@ describe( 'SnapWPConfigManager functions', () => { let ORIG_ENV: NodeJS.ProcessEnv; const validSnapWPEnvConfig: Partial< SnapWPEnv > = { - nextUrl: 'https://env-next.example.com', - homeUrl: 'https://env-home.example.com', + corsProxyPrefix: + process.env.NODE_ENV === 'development' ? '/proxy' : undefined, + frontendUrl: 'https://env-next.example.com', + wpHomeUrl: 'https://env-home.example.com', graphqlEndpoint: 'env-index.php?graphql', uploadsDirectory: '/env-wp-content/uploads', restUrlPrefix: '/env-wp-json', }; const defaultConfig: Partial< SnapWPEnv > = { + corsProxyPrefix: + process.env.NODE_ENV === 'development' ? '/proxy' : undefined, graphqlEndpoint: 'index.php?graphql', uploadsDirectory: '/wp-content/uploads', restUrlPrefix: '/wp-json', - corsProxyPrefix: '/proxy', - useCorsProxy: false, }; beforeEach( () => { @@ -65,7 +68,7 @@ describe( 'SnapWPConfigManager functions', () => { } catch ( e ) { if ( e instanceof Error ) { expect( e.message ).toBe( - 'Missing required property: nextUrl.' + 'Missing required property: frontendUrl.' ); } } @@ -88,57 +91,57 @@ describe( 'SnapWPConfigManager functions', () => { ); } ); - it( 'should throw an error if nextUrl is missing', () => { - process.env.NEXT_PUBLIC_URL = ''; + it( 'should throw an error if frontendUrl is missing', () => { + process.env.NEXT_PUBLIC_FRONTEND_URL = ''; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( - 'Missing required property: nextUrl' + 'Missing required property: frontendUrl' ); } ); - it( 'should throw an error if nextUrl is not a string', () => { - process.env.NEXT_PUBLIC_URL = 123 as unknown as string; + it( 'should throw an error if frontendUrl is not a string', () => { + process.env.NEXT_PUBLIC_FRONTEND_URL = 123 as unknown as string; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( - 'Property nextUrl should be of type string' + 'Property frontendUrl should be of type string' ); } ); - it( 'should throw an error if nextUrl is not a valid URL', () => { - process.env.NEXT_PUBLIC_URL = 'invalid-url'; + it( 'should throw an error if frontendUrl is not a valid URL', () => { + process.env.NEXT_PUBLIC_FRONTEND_URL = 'invalid-url'; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( - '`nextUrl` should be a valid URL.' + '`frontendUrl` should be a valid URL.' ); } ); - it( 'should throw an error if homeUrl is missing', () => { - process.env.NEXT_PUBLIC_WORDPRESS_URL = ''; + it( 'should throw an error if wpHomeUrl is missing', () => { + process.env.NEXT_PUBLIC_WP_HOME_URL = ''; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( - 'Missing required property: homeUrl' + 'Missing required property: wpHomeUrl' ); } ); - it( 'should throw an error if homeUrl is not a string', () => { - process.env.NEXT_PUBLIC_WORDPRESS_URL = 123 as unknown as string; + it( 'should throw an error if wpHomeUrl is not a string', () => { + process.env.NEXT_PUBLIC_WP_HOME_URL = 123 as unknown as string; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( - 'Property homeUrl should be of type string' + 'Property wpHomeUrl should be of type string' ); } ); - it( 'should throw an error if homeUrl is not a valid URL', () => { - process.env.NEXT_PUBLIC_WORDPRESS_URL = 'invalid-url'; + it( 'should throw an error if wpHomeUrl is not a valid URL', () => { + process.env.NEXT_PUBLIC_WP_HOME_URL = 'invalid-url'; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( - '`homeUrl` should be a valid URL.' + '`wpHomeUrl` should be a valid URL.' ); } ); @@ -146,15 +149,15 @@ describe( 'SnapWPConfigManager functions', () => { const graphqlUrl = getGraphqlUrl(); expect( graphqlUrl ).toBe( // @ts-ignore Allow setting global variable for testing - `${ global.__envConfig.homeUrl }/${ global.__envConfig.graphqlEndpoint }` + `${ global.__envConfig.wpHomeUrl }/${ global.__envConfig.graphqlEndpoint }` ); } ); it( 'should prioritize envConfig over validConfig and defaultConfig', () => { // @ts-ignore Allow setting global variable for testing const envConfig = global.__envConfig; - expect( getConfig().nextUrl ).toBe( envConfig.nextUrl ); - expect( getConfig().homeUrl ).toBe( envConfig.homeUrl ); + expect( getConfig().frontendUrl ).toBe( envConfig.frontendUrl ); + expect( getConfig().wpHomeUrl ).toBe( envConfig.wpHomeUrl ); expect( getConfig().graphqlEndpoint ).toBe( envConfig.graphqlEndpoint ); @@ -166,11 +169,12 @@ describe( 'SnapWPConfigManager functions', () => { } ); it( 'should handle missing environment variables correctly', () => { - delete process.env.NEXT_PUBLIC_URL; - delete process.env.NEXT_PUBLIC_WORDPRESS_URL; + delete process.env.NEXT_PUBLIC_FRONTEND_URL; + delete process.env.NEXT_PUBLIC_WP_HOME_URL; delete process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT; - delete process.env.NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH; - delete process.env.NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX; + delete process.env.NEXT_PUBLIC_WP_UPLOADS_DIRECTORY; + delete process.env.NEXT_PUBLIC_REST_URL_PREFIX; + delete process.env.NEXT_PUBLIC_WP_SITE_URL; expect( getConfig() ).toEqual( { ...defaultConfig, @@ -188,7 +192,7 @@ describe( 'SnapWPConfigManager functions', () => { } ); it( 'should throw an error if restUrlPrefix does not have forward slash', () => { - process.env.NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX = 'wp-json'; + process.env.NEXT_PUBLIC_REST_URL_PREFIX = 'wp-json'; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( @@ -197,7 +201,7 @@ describe( 'SnapWPConfigManager functions', () => { } ); it( 'should throw an error if uploadsDirectory does not have forward slash', () => { - process.env.NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH = 'wp-content/uploads'; + process.env.NEXT_PUBLIC_WP_UPLOADS_DIRECTORY = 'wp-content/uploads'; // @ts-ignore Allow setting global variable for testing global.__snapWPConfig = {}; expect( () => getConfig() ).toThrow( @@ -206,14 +210,13 @@ describe( 'SnapWPConfigManager functions', () => { } ); it( 'should correctly normalize URLs by removing trailing slashes', () => { - process.env.NEXT_PUBLIC_URL = 'https://localhost:3000/'; - process.env.NEXT_PUBLIC_WORDPRESS_URL = - 'https://wordpress.example.com/'; + process.env.NEXT_PUBLIC_FRONTEND_URL = 'https://localhost:3000/'; + process.env.NEXT_PUBLIC_WP_HOME_URL = 'https://wordpress.example.com/'; const config = getConfig(); - expect( config.nextUrl ).toBe( 'https://localhost:3000' ); - expect( config.homeUrl ).toBe( 'https://wordpress.example.com' ); + expect( config.frontendUrl ).toBe( 'https://localhost:3000' ); + expect( config.wpHomeUrl ).toBe( 'https://wordpress.example.com' ); } ); it( 'should not include blockDefinitions if not set', () => { @@ -241,4 +244,35 @@ describe( 'SnapWPConfigManager functions', () => { setConfig( { blockDefinitions: 'invalid' } ); } ).toThrow( 'Property blockDefinitions should be of type object.' ); } ); + + it( 'should throw an error if wpSiteUrl is not a string', () => { + process.env.NEXT_PUBLIC_WP_SITE_URL = 123 as unknown as string; + // @ts-ignore Allow setting global variable for testing + global.__snapWPConfig = {}; + expect( () => getConfig() ).toThrow( + 'Property wpSiteUrl should be of type string' + ); + } ); + + it( 'should throw an error if wpSiteUrl is not a valid URL', () => { + process.env.NEXT_PUBLIC_WP_SITE_URL = 'invalid-url'; + // @ts-ignore Allow setting global variable for testing + global.__snapWPConfig = {}; + expect( () => getConfig() ).toThrow( + '`wpSiteUrl` should be a valid URL.' + ); + } ); + + it( 'should correctly normalize wpSiteUrl by removing trailing slashes', () => { + process.env.NEXT_PUBLIC_WP_SITE_URL = 'https://wordpress.example.com/'; + const config = getConfig(); + expect( config.wpSiteUrl ).toBe( 'https://wordpress.example.com' ); + } ); + + it( 'should use wpHomeUrl if wpSiteUrl is empty', () => { + delete process.env.NEXT_PUBLIC_WP_SITE_URL; + process.env.NEXT_PUBLIC_WP_HOME_URL = 'https://wordpress.example.com'; + const config = getConfig(); + expect( config.wpSiteUrl ).toBe( 'https://wordpress.example.com' ); + } ); } ); diff --git a/packages/core/src/utils/generate-graphql-url.ts b/packages/core/src/utils/generate-graphql-url.ts index 07c45d24..8e18aa59 100644 --- a/packages/core/src/utils/generate-graphql-url.ts +++ b/packages/core/src/utils/generate-graphql-url.ts @@ -12,9 +12,9 @@ export default function generateGraphqlUrl( homeUrl?: string, graphqlEndpoint?: string ): string { - // Adding extra check because there is high chances of process.env.NEXT_PUBLIC_WORDPRESS_URL and process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT being undefined + // Adding extra check because there is high chances of process.env.NEXT_PUBLIC_WP_HOME_URL and process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT being undefined if ( ! homeUrl ) { - Logger.error( 'homeUrl is not set' ); + Logger.error( 'wpHomeUrl is not set' ); return ''; } diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index cd979d23..d1f8c557 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,19 +1,28 @@ export { default as generateGraphqlUrl } from './generate-graphql-url'; -export { default as getStyleObjectFromString } from './get-style-object-from-string'; -export { default as replaceHostUrl } from './replace-host-url'; -export { default as getClassNamesFromString } from './get-class-names-from-string'; -export { default as getSpacingPresetCssVar } from './get-spacing-preset-css-var'; -export { default as getStylesFromAttributes } from './get-styles-from-attributes'; -export { default as cn } from './cn'; -export { default as isValidUrl } from './is-valid-url'; -export { default as getImageSizeFromAttributes } from './get-image-size-from-attributes'; -export { default as getPxForSizeAttribute } from './get-px-for-size-attribute'; export { default as parseExternalRemotePatterns } from './parse-external-remote-patterns'; -export { default as findElementAndGetClassNames } from './find-element-and-get-class-names'; -export { default as getColorClassName } from './get-color-class-name'; + +// Style Attribute utilities +export { default as cn } from './styles/cn'; +export { default as findElementAndGetClassNames } from './styles/find-element-and-get-class-names'; +export { default as getClassNamesFromString } from './styles/get-class-names-from-string'; +export { default as getColorClassName } from './styles/get-color-class-name'; +export { default as getImageSizeFromAttributes } from './styles/get-image-size-from-attributes'; +export { default as getPxForSizeAttribute } from './styles/get-px-for-size-attribute'; +export { default as getSpacingPresetCssVar } from './styles/get-spacing-preset-css-var'; +export { default as getStyleObjectFromString } from './styles/get-style-object-from-string'; +export { default as getStylesFromAttributes } from './styles/get-styles-from-attributes'; + +// URL utilities export { addTrailingSlash, removeTrailingSlash, addLeadingSlash, removeLeadingSlash, -} from './url-slash-modify'; + toFrontendUri, +} from './url/transformers'; +export { + isWPHomeUrl, + isWPSiteUrl, + isInternalUrl, + isValidUrl, +} from './url/validators'; diff --git a/packages/core/src/utils/is-valid-url.ts b/packages/core/src/utils/is-valid-url.ts deleted file mode 100644 index 1bb163d7..00000000 --- a/packages/core/src/utils/is-valid-url.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @param str A string to be validated as a Url. - * @return flag signifying validity. - * - * @internal - */ -export default function isValidUrl( str: string ) { - // When an invalid URL is passed, URL() throws an error. If an error is thrown, we will return false. - try { - const url = new URL( str ); - return !! url; - } catch ( e ) { - return false; - } -} diff --git a/packages/core/src/utils/replace-host-url.ts b/packages/core/src/utils/replace-host-url.ts deleted file mode 100644 index 61aa1c0d..00000000 --- a/packages/core/src/utils/replace-host-url.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Converts the WordPress server URL to an internal one. - * - * @param url - WP url. - * @param hostUrl - Host url to be replaced. - * @param newHostUrl - Host url to be replaced with. - * @return The internal url. - * - * @internal - */ -export default function replaceHostUrl( - url: string, - hostUrl: string, - newHostUrl: string -) { - if ( url && hostUrl && url.startsWith( hostUrl ) ) { - return url.replace( hostUrl, newHostUrl ); - } - return url; -} diff --git a/packages/core/src/utils/cn.ts b/packages/core/src/utils/styles/cn.ts similarity index 100% rename from packages/core/src/utils/cn.ts rename to packages/core/src/utils/styles/cn.ts diff --git a/packages/core/src/utils/find-element-and-get-class-names.ts b/packages/core/src/utils/styles/find-element-and-get-class-names.ts similarity index 93% rename from packages/core/src/utils/find-element-and-get-class-names.ts rename to packages/core/src/utils/styles/find-element-and-get-class-names.ts index 25051dd0..6cf889ce 100644 --- a/packages/core/src/utils/find-element-and-get-class-names.ts +++ b/packages/core/src/utils/styles/find-element-and-get-class-names.ts @@ -1,4 +1,5 @@ -import { getClassNamesFromString, cn } from '@/utils'; +import cn from './cn'; +import getClassNamesFromString from './get-class-names-from-string'; /** * Extracts the class names from the specified HTML element within the rendered HTML string. diff --git a/packages/core/src/utils/get-class-names-from-string.ts b/packages/core/src/utils/styles/get-class-names-from-string.ts similarity index 100% rename from packages/core/src/utils/get-class-names-from-string.ts rename to packages/core/src/utils/styles/get-class-names-from-string.ts diff --git a/packages/core/src/utils/get-color-class-name.ts b/packages/core/src/utils/styles/get-color-class-name.ts similarity index 100% rename from packages/core/src/utils/get-color-class-name.ts rename to packages/core/src/utils/styles/get-color-class-name.ts diff --git a/packages/core/src/utils/get-image-size-from-attributes.ts b/packages/core/src/utils/styles/get-image-size-from-attributes.ts similarity index 100% rename from packages/core/src/utils/get-image-size-from-attributes.ts rename to packages/core/src/utils/styles/get-image-size-from-attributes.ts diff --git a/packages/core/src/utils/get-px-for-size-attribute.ts b/packages/core/src/utils/styles/get-px-for-size-attribute.ts similarity index 100% rename from packages/core/src/utils/get-px-for-size-attribute.ts rename to packages/core/src/utils/styles/get-px-for-size-attribute.ts diff --git a/packages/core/src/utils/get-spacing-preset-css-var.ts b/packages/core/src/utils/styles/get-spacing-preset-css-var.ts similarity index 100% rename from packages/core/src/utils/get-spacing-preset-css-var.ts rename to packages/core/src/utils/styles/get-spacing-preset-css-var.ts diff --git a/packages/core/src/utils/get-style-object-from-string.ts b/packages/core/src/utils/styles/get-style-object-from-string.ts similarity index 100% rename from packages/core/src/utils/get-style-object-from-string.ts rename to packages/core/src/utils/styles/get-style-object-from-string.ts diff --git a/packages/core/src/utils/get-styles-from-attributes.ts b/packages/core/src/utils/styles/get-styles-from-attributes.ts similarity index 100% rename from packages/core/src/utils/get-styles-from-attributes.ts rename to packages/core/src/utils/styles/get-styles-from-attributes.ts diff --git a/packages/core/src/utils/tests/cn.test.ts b/packages/core/src/utils/styles/tests/cn.test.ts similarity index 100% rename from packages/core/src/utils/tests/cn.test.ts rename to packages/core/src/utils/styles/tests/cn.test.ts diff --git a/packages/core/src/utils/tests/find-element-and-get-class-names.test.ts b/packages/core/src/utils/styles/tests/find-element-and-get-class-names.test.ts similarity index 96% rename from packages/core/src/utils/tests/find-element-and-get-class-names.test.ts rename to packages/core/src/utils/styles/tests/find-element-and-get-class-names.test.ts index 9818139d..0f3af8e9 100644 --- a/packages/core/src/utils/tests/find-element-and-get-class-names.test.ts +++ b/packages/core/src/utils/styles/tests/find-element-and-get-class-names.test.ts @@ -1,4 +1,4 @@ -import findElementAndGetClassNames from '@/utils/find-element-and-get-class-names'; +import findElementAndGetClassNames from '../find-element-and-get-class-names'; describe( 'findElementAndGetClassNames', () => { it( 'returns class names when renderedHtml contains the specified class selector', () => { diff --git a/packages/core/src/utils/tests/get-class-names-from-string.test.ts b/packages/core/src/utils/styles/tests/get-class-names-from-string.test.ts similarity index 100% rename from packages/core/src/utils/tests/get-class-names-from-string.test.ts rename to packages/core/src/utils/styles/tests/get-class-names-from-string.test.ts diff --git a/packages/core/src/utils/tests/get-color-class-name.test.ts b/packages/core/src/utils/styles/tests/get-color-class-name.test.ts similarity index 94% rename from packages/core/src/utils/tests/get-color-class-name.test.ts rename to packages/core/src/utils/styles/tests/get-color-class-name.test.ts index 830b75c0..f80ba3f9 100644 --- a/packages/core/src/utils/tests/get-color-class-name.test.ts +++ b/packages/core/src/utils/styles/tests/get-color-class-name.test.ts @@ -1,4 +1,4 @@ -import getColorClassName from '@/utils/get-color-class-name'; +import getColorClassName from '../get-color-class-name'; describe( 'getColorClassName', () => { it( 'returns the correct class name for valid color context and slug', () => { diff --git a/packages/core/src/utils/tests/get-image-size-from-attributes.test.ts b/packages/core/src/utils/styles/tests/get-image-size-from-attributes.test.ts similarity index 98% rename from packages/core/src/utils/tests/get-image-size-from-attributes.test.ts rename to packages/core/src/utils/styles/tests/get-image-size-from-attributes.test.ts index b0bd5438..7fb176ec 100644 --- a/packages/core/src/utils/tests/get-image-size-from-attributes.test.ts +++ b/packages/core/src/utils/styles/tests/get-image-size-from-attributes.test.ts @@ -3,7 +3,7 @@ import getImageSizeFromAttributes, { } from '../get-image-size-from-attributes'; import getPxForSizeAttribute from '../get-px-for-size-attribute'; -jest.mock( '../../utils/get-px-for-size-attribute' ); +jest.mock( '../get-px-for-size-attribute' ); describe( 'getImageSizeFromAttributes', () => { beforeEach( () => { diff --git a/packages/core/src/utils/tests/get-px-for-size-attribute.test.ts b/packages/core/src/utils/styles/tests/get-px-for-size-attribute.test.ts similarity index 100% rename from packages/core/src/utils/tests/get-px-for-size-attribute.test.ts rename to packages/core/src/utils/styles/tests/get-px-for-size-attribute.test.ts diff --git a/packages/core/src/utils/tests/get-spacing-preset-css-var.test.ts b/packages/core/src/utils/styles/tests/get-spacing-preset-css-var.test.ts similarity index 100% rename from packages/core/src/utils/tests/get-spacing-preset-css-var.test.ts rename to packages/core/src/utils/styles/tests/get-spacing-preset-css-var.test.ts diff --git a/packages/core/src/utils/tests/get-style-object-from-string.test.ts b/packages/core/src/utils/styles/tests/get-style-object-from-string.test.ts similarity index 100% rename from packages/core/src/utils/tests/get-style-object-from-string.test.ts rename to packages/core/src/utils/styles/tests/get-style-object-from-string.test.ts diff --git a/packages/core/src/utils/tests/get-styles-from-attributes.test.ts b/packages/core/src/utils/styles/tests/get-styles-from-attributes.test.ts similarity index 100% rename from packages/core/src/utils/tests/get-styles-from-attributes.test.ts rename to packages/core/src/utils/styles/tests/get-styles-from-attributes.test.ts diff --git a/packages/core/src/utils/tests/generate-graphql-url.test.ts b/packages/core/src/utils/tests/generate-graphql-url.test.ts index ce62b5ee..06a0583a 100644 --- a/packages/core/src/utils/tests/generate-graphql-url.test.ts +++ b/packages/core/src/utils/tests/generate-graphql-url.test.ts @@ -8,10 +8,10 @@ describe( 'generateGraphqlUrl', () => { jest.clearAllMocks(); } ); - it( 'should return an empty string and log an error if homeUrl is not provided', () => { + it( 'should return an empty string and log an error if wpHomeUrl is not provided', () => { const result = generateGraphqlUrl( undefined, '/graphql' ); expect( result ).toBe( '' ); - expect( Logger.error ).toHaveBeenCalledWith( 'homeUrl is not set' ); + expect( Logger.error ).toHaveBeenCalledWith( 'wpHomeUrl is not set' ); } ); it( 'should return an empty string and log an error if graphqlEndpoint is not provided', () => { @@ -22,12 +22,12 @@ describe( 'generateGraphqlUrl', () => { ); } ); - it( 'should return the correct URL when both homeUrl and graphqlEndpoint are provided', () => { + it( 'should return the correct URL when both wpHomeUrl and graphqlEndpoint are provided', () => { const result = generateGraphqlUrl( 'https://example.com', '/graphql' ); expect( result ).toBe( 'https://example.com/graphql' ); } ); - it( 'should handle homeUrl with trailing slash', () => { + it( 'should handle wpHomeUrl with trailing slash', () => { const result = generateGraphqlUrl( 'https://example.com/', '/graphql' ); expect( result ).toBe( 'https://example.com/graphql' ); } ); @@ -37,19 +37,19 @@ describe( 'generateGraphqlUrl', () => { expect( result ).toBe( 'https://example.com/graphql' ); } ); - it( 'should handle both homeUrl with trailing slash and graphqlEndpoint without leading slash', () => { + it( 'should handle both wpHomeUrl with trailing slash and graphqlEndpoint without leading slash', () => { const result = generateGraphqlUrl( 'https://example.com/', 'graphql' ); expect( result ).toBe( 'https://example.com/graphql' ); } ); - it( 'should handle both homeUrl without trailing slash and graphqlEndpoint with leading slash', () => { + it( 'should handle both wpHomeUrl without trailing slash and graphqlEndpoint with leading slash', () => { const result = generateGraphqlUrl( 'https://example.com', '/graphql' ); expect( result ).toBe( 'https://example.com/graphql' ); } ); - it( 'should handle empty strings for homeUrl and graphqlEndpoint', () => { + it( 'should handle empty strings for wpHomeUrl and graphqlEndpoint', () => { const result = generateGraphqlUrl( '', '' ); expect( result ).toBe( '' ); - expect( Logger.error ).toHaveBeenCalledWith( 'homeUrl is not set' ); + expect( Logger.error ).toHaveBeenCalledWith( 'wpHomeUrl is not set' ); } ); } ); diff --git a/packages/core/src/utils/tests/is-valid-url.test.ts b/packages/core/src/utils/tests/is-valid-url.test.ts deleted file mode 100644 index 5ebed146..00000000 --- a/packages/core/src/utils/tests/is-valid-url.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import isValidUrl from '../is-valid-url'; - -describe( 'isValidUrl', () => { - it( 'should return true for a valid URL with http', () => { - const result = isValidUrl( 'http://example.com' ); - expect( result ).toBe( true ); - } ); - - it( 'should return true for a valid URL with https', () => { - const result = isValidUrl( 'https://example.com' ); - expect( result ).toBe( true ); - } ); - - it( 'should return true for a valid URL with subdomain', () => { - const result = isValidUrl( 'https://sub.example.com' ); - expect( result ).toBe( true ); - } ); - - it( 'should return true for a valid URL with path', () => { - const result = isValidUrl( 'https://example.com/path' ); - expect( result ).toBe( true ); - } ); - - it( 'should return true for a valid URL with query parameters', () => { - const result = isValidUrl( 'https://example.com/path?name=value' ); - expect( result ).toBe( true ); - } ); - - it( 'should return true for a valid URL with fragment', () => { - const result = isValidUrl( 'https://example.com/path#section' ); - expect( result ).toBe( true ); - } ); - - it( 'should return false for an invalid URL without protocol', () => { - const result = isValidUrl( 'example.com' ); - expect( result ).toBe( false ); - } ); - - it( 'should return false for an invalid URL with spaces', () => { - const result = isValidUrl( 'https:// example .com' ); - expect( result ).toBe( false ); - } ); - - it( 'should return false for an invalid URL with special characters', () => { - const result = isValidUrl( 'https://exa mple.com' ); - expect( result ).toBe( false ); - } ); - - it( 'should return false for an empty string', () => { - const result = isValidUrl( '' ); - expect( result ).toBe( false ); - } ); - - it( 'should return false for a non-string input', () => { - const result = isValidUrl( 123 as unknown as string ); - expect( result ).toBe( false ); - } ); -} ); diff --git a/packages/core/src/utils/tests/replace-host-url.test.ts b/packages/core/src/utils/tests/replace-host-url.test.ts deleted file mode 100644 index 2d9b58c3..00000000 --- a/packages/core/src/utils/tests/replace-host-url.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import replaceHostUrl from '../replace-host-url'; - -describe( 'replaceHostUrl', () => { - it( 'should replace the host URL with the new host URL when the URL starts with the host URL', () => { - const url = 'https://old-host.com/path/to/resource'; - const hostUrl = 'https://old-host.com'; - const newHostUrl = 'https://new-host.com'; - const expected = 'https://new-host.com/path/to/resource'; - const result = replaceHostUrl( url, hostUrl, newHostUrl ); - expect( result ).toBe( expected ); - } ); - - it( 'should return the same URL if it does not start with the host URL', () => { - const url = 'https://other-host.com/path/to/resource'; - const hostUrl = 'https://old-host.com'; - const newHostUrl = 'https://new-host.com'; - const expected = 'https://other-host.com/path/to/resource'; - const result = replaceHostUrl( url, hostUrl, newHostUrl ); - expect( result ).toBe( expected ); - } ); - - it( 'should return the same URL if the URL is empty', () => { - const url = ''; - const hostUrl = 'https://old-host.com'; - const newHostUrl = 'https://new-host.com'; - const result = replaceHostUrl( url, hostUrl, newHostUrl ); - expect( result ).toBe( '' ); - } ); - - it( 'should return the same URL if the host URL is empty', () => { - const url = 'https://old-host.com/path/to/resource'; - const hostUrl = ''; - const newHostUrl = 'https://new-host.com'; - const result = replaceHostUrl( url, hostUrl, newHostUrl ); - expect( result ).toBe( url ); - } ); - - it( 'should handle URLs with query parameters and fragments correctly', () => { - const url = 'https://old-host.com/path/to/resource?query=1#fragment'; - const hostUrl = 'https://old-host.com'; - const newHostUrl = 'https://new-host.com'; - const expected = - 'https://new-host.com/path/to/resource?query=1#fragment'; - const result = replaceHostUrl( url, hostUrl, newHostUrl ); - expect( result ).toBe( expected ); - } ); - - it( 'should handle URLs where newHostUrl is empty', () => { - const url = 'https://old-host.com/path/to/resource'; - const hostUrl = 'https://old-host.com'; - const newHostUrl = ''; - const expected = '/path/to/resource'; - const result = replaceHostUrl( url, hostUrl, newHostUrl ); - expect( result ).toBe( expected ); - } ); - - it( 'should handle cases where hostUrl and newHostUrl are identical', () => { - const url = 'https://old-host.com/path/to/resource'; - const hostUrl = 'https://old-host.com'; - const newHostUrl = 'https://old-host.com'; - const expected = 'https://old-host.com/path/to/resource'; - const result = replaceHostUrl( url, hostUrl, newHostUrl ); - expect( result ).toBe( expected ); - } ); -} ); diff --git a/packages/core/src/utils/tests/url-slash-modify.test.ts b/packages/core/src/utils/url/tests/transformers.test.ts similarity index 66% rename from packages/core/src/utils/tests/url-slash-modify.test.ts rename to packages/core/src/utils/url/tests/transformers.test.ts index a0c1cde8..893f692f 100644 --- a/packages/core/src/utils/tests/url-slash-modify.test.ts +++ b/packages/core/src/utils/url/tests/transformers.test.ts @@ -3,9 +3,10 @@ import { addTrailingSlash, removeLeadingSlash, addLeadingSlash, -} from '../url-slash-modify'; + toFrontendUri, +} from '../transformers'; -describe( 'URL Slash Modify Functions', () => { +describe( 'URL Transformer functions', () => { describe( 'removeTrailingSlash', () => { it( 'should remove trailing slash from the URL', () => { expect( removeTrailingSlash( 'https://example.com/' ) ).toBe( @@ -77,4 +78,35 @@ describe( 'URL Slash Modify Functions', () => { ); } ); } ); + + describe( 'toFrontendUri', () => { + beforeEach( () => { + jest.resetAllMocks(); + } ); + + it( 'should return the original URL if it is not an internal URL', () => { + const url = 'http://external.com'; + expect( toFrontendUri( url ) ).toBe( url ); + } ); + + it( 'should return url as it is if it starts with /', () => { + const url = '/path'; + expect( toFrontendUri( url ) ).toBe( '/path' ); + } ); + + it( 'should convert an absolute internal URL to the internal URL with the same path, search, hash, username, and password', () => { + const url = 'https://env-home.example.com/path?query=1#hash'; + expect( toFrontendUri( url ) ).toBe( '/path?query=1#hash' ); + } ); + + it( 'should handle http vs https when ignoreProtocol is true', () => { + const url = 'http://env-home.example.com/path'; + expect( toFrontendUri( url ) ).toBe( '/path' ); + } ); + + it( 'should handle http vs https when ignoreProtocol is false', () => { + const url = 'http://env-home.example.com/path'; + expect( toFrontendUri( url ) ).toBe( '/path' ); + } ); + } ); } ); diff --git a/packages/core/src/utils/url/tests/validators.test.ts b/packages/core/src/utils/url/tests/validators.test.ts new file mode 100644 index 00000000..51d3b55f --- /dev/null +++ b/packages/core/src/utils/url/tests/validators.test.ts @@ -0,0 +1,129 @@ +import { + isWPHomeUrl, + isWPSiteUrl, + isInternalUrl, + isValidUrl, +} from '../validators'; + +describe( 'URL Validator functions', () => { + beforeEach( () => { + jest.resetAllMocks(); + } ); + + describe( 'isWPHomeUrl', () => { + it( 'should return true if the URL is the home URL', () => { + expect( isWPHomeUrl( 'https://env-home.example.com' ) ).toBe( + true + ); + } ); + + it( 'should return true if the URL starts with the home URL', () => { + expect( isWPHomeUrl( 'https://env-home.example.com/path' ) ).toBe( + true + ); + } ); + + it( 'should return false if the URL starts with a slash', () => { + expect( isWPHomeUrl( '/path' ) ).toBe( false ); + } ); + + it( 'should return false if the URL does not match the home URL', () => { + expect( isWPHomeUrl( 'https://other.example.com' ) ).toBe( false ); + } ); + } ); + + describe( 'isWPSiteUrl', () => { + it( 'should return true if the URL starts with the site URL', () => { + expect( + isWPSiteUrl( 'https://env-home.example.com/wp/path' ) + ).toBe( true ); + } ); + + it( 'should return false if the URL does not start with the site URL', () => { + expect( isWPSiteUrl( 'https://other.example.com' ) ).toBe( false ); + } ); + } ); + + describe( 'isInternalUrl', () => { + it( 'should return true if the URL is the home URL', () => { + expect( isInternalUrl( 'https://env-home.example.com' ) ).toBe( + true + ); + } ); + + it( 'should return true if the URL starts with the home URL', () => { + expect( isInternalUrl( 'https://env-home.example.com/path' ) ).toBe( + true + ); + } ); + + it( 'should return true if the URL starts with the site URL', () => { + expect( + isInternalUrl( 'https://env-home.example.com/wp/path' ) + ).toBe( true ); + } ); + + it( 'should return false if the URL does not match the home or site URL', () => { + expect( isInternalUrl( 'https://other.example.com' ) ).toBe( + false + ); + } ); + } ); + + describe( 'isValidUrl', () => { + it( 'should return true for a valid URL with http', () => { + const result = isValidUrl( 'http://example.com' ); + expect( result ).toBe( true ); + } ); + + it( 'should return true for a valid URL with https', () => { + const result = isValidUrl( 'https://example.com' ); + expect( result ).toBe( true ); + } ); + + it( 'should return true for a valid URL with subdomain', () => { + const result = isValidUrl( 'https://sub.example.com' ); + expect( result ).toBe( true ); + } ); + + it( 'should return true for a valid URL with path', () => { + const result = isValidUrl( 'https://example.com/path' ); + expect( result ).toBe( true ); + } ); + + it( 'should return true for a valid URL with query parameters', () => { + const result = isValidUrl( 'https://example.com/path?name=value' ); + expect( result ).toBe( true ); + } ); + + it( 'should return true for a valid URL with fragment', () => { + const result = isValidUrl( 'https://example.com/path#section' ); + expect( result ).toBe( true ); + } ); + + it( 'should return false for an invalid URL without protocol', () => { + const result = isValidUrl( 'example.com' ); + expect( result ).toBe( false ); + } ); + + it( 'should return false for an invalid URL with spaces', () => { + const result = isValidUrl( 'https:// example .com' ); + expect( result ).toBe( false ); + } ); + + it( 'should return false for an invalid URL with special characters', () => { + const result = isValidUrl( 'https://exa mple.com' ); + expect( result ).toBe( false ); + } ); + + it( 'should return false for an empty string', () => { + const result = isValidUrl( '' ); + expect( result ).toBe( false ); + } ); + + it( 'should return false for a non-string input', () => { + const result = isValidUrl( 123 as unknown as string ); + expect( result ).toBe( false ); + } ); + } ); +} ); diff --git a/packages/core/src/utils/url-slash-modify.ts b/packages/core/src/utils/url/transformers.ts similarity index 54% rename from packages/core/src/utils/url-slash-modify.ts rename to packages/core/src/utils/url/transformers.ts index c0f6ff07..b7e3b0de 100644 --- a/packages/core/src/utils/url-slash-modify.ts +++ b/packages/core/src/utils/url/transformers.ts @@ -1,3 +1,34 @@ +import { getConfig } from '@/config'; +import { isInternalUrl, isWPSiteUrl } from './validators'; + +/** + * Convert a URL to an internal URL. + * + * @param url The URL to convert. + * + * @return The internal URL. + */ +export const toFrontendUri = ( url: string ): string => { + if ( ! isInternalUrl( url ) ) { + return url; + } + + // Bail if already a URI. + if ( url.startsWith( '/' ) ) { + return url; + } + + const { wpHomeUrl, wpSiteUrl } = getConfig(); + + const domainToUpdate = isWPSiteUrl( url ) ? wpSiteUrl : wpHomeUrl; + + // Remove protocol before replacement. + const normalizedDomain = domainToUpdate.replace( /^https?:\/\//, '' ); + const normalizedUrl = url.replace( /^https?:\/\//, '' ); + + return addLeadingSlash( normalizedUrl.replace( normalizedDomain, '' ) ); +}; + /** * Remove trailing slash from the URL. * diff --git a/packages/core/src/utils/url/validators.ts b/packages/core/src/utils/url/validators.ts new file mode 100644 index 00000000..dc4b5c2c --- /dev/null +++ b/packages/core/src/utils/url/validators.ts @@ -0,0 +1,95 @@ +import { getConfig } from '@/config'; + +/** + * Check if a URL is the home URL. + * + * @param url The URL to check. + * @param ignoreProtocol - Ignore HTTP protocol when comparing URLs. + * + * @return Whether the URL is the home URL. + */ +export const isWPHomeUrl = ( + url: string, + ignoreProtocol: boolean = true +): boolean => { + if ( ! isValidUrl( url ) ) { + return false; + } + + const { wpHomeUrl } = getConfig(); + + if ( ignoreProtocol ) { + // Compare just the domains. + return new URL( url ).host === new URL( wpHomeUrl ).host; + } + + // Compare with the protocol. + return url === wpHomeUrl || url.startsWith( wpHomeUrl ); +}; + +/** + * Check if a URL is the site URL. + * + * @param url The URL to check. + * @param {boolean} ignoreProtocol - Whether to ignore the HTTP protocol when comparing URLs. + * + * @return Whether the URL is the site URL. + */ +export const isWPSiteUrl = ( + url: string, + ignoreProtocol: boolean = true +): boolean => { + if ( ! isValidUrl( url ) ) { + return false; + } + + const { wpSiteUrl } = getConfig(); + + if ( ignoreProtocol ) { + const urlObject = new URL( url ); + const siteUrlObject = new URL( wpSiteUrl ); + // Compare domain and path. + return ( + urlObject.host === siteUrlObject.host && + urlObject.pathname.startsWith( siteUrlObject.pathname ) + ); + } + + // Compare with the protocol. + return url === wpSiteUrl || url.startsWith( wpSiteUrl ); +}; + +/** + * Check if a URL is internal (Site URL or Home Url). + * + * @param url The URL to check. + * @param ignoreProtocol - Ignore HTTP protocol when comparing URLs. + * + * @return Whether the URL is internal. + */ +export const isInternalUrl = ( + url: string, + ignoreProtocol: boolean = true +): boolean => { + return ( + url.startsWith( '/' ) || + isWPSiteUrl( url, ignoreProtocol ) || + isWPHomeUrl( url, ignoreProtocol ) + ); +}; + +/** + * @param str A string to be validated as a Url. + * @return flag signifying validity. + * + * @internal + */ +export const isValidUrl = ( str: string ) => { + // When an invalid URL is passed, URL() throws an error. If an error is thrown, we will return false. + try { + const url = new URL( str ); + return !! url; + } catch ( e ) { + return false; + } +}; diff --git a/packages/next/src/components/image.tsx b/packages/next/src/components/image.tsx index 75a1bd3e..590e798b 100644 --- a/packages/next/src/components/image.tsx +++ b/packages/next/src/components/image.tsx @@ -105,8 +105,8 @@ export default function Image( { } // @todo replace src?.startsWith conditional check with something more robust that will incorporate both frontend/backend domain & anything in the list of allowed images domain in the config (ref: https://github.com/rtCamp/headless/pull/241#discussion_r1824274200). TBD after https://github.com/rtCamp/headless/issues/218. - const { homeUrl } = getConfig(); - const normalizedHomeUrl = homeUrl?.replace( /https?:\/\//, '' ); + const { wpHomeUrl } = getConfig(); + const normalizedHomeUrl = wpHomeUrl?.replace( /https?:\/\//, '' ); const normalizedSrc = src?.replace( /https?:\/\//, '' ); if ( diff --git a/packages/next/src/components/link.tsx b/packages/next/src/components/link.tsx index 261deaa8..58e918eb 100644 --- a/packages/next/src/components/link.tsx +++ b/packages/next/src/components/link.tsx @@ -3,7 +3,7 @@ import { type CSSProperties, type PropsWithChildren, } from 'react'; -import { replaceHostUrl } from '@snapwp/core'; +import { toFrontendUri, isInternalUrl } from '@snapwp/core'; import { getConfig } from '@snapwp/core/config'; import type { PartialWithUndefined } from '@snapwp/types'; import NextLink, { type LinkProps } from 'next/link'; @@ -38,22 +38,13 @@ export default function Link( { | PartialWithUndefined< LinkProps > ) > ) { - const { homeUrl, nextUrl, graphqlEndpoint } = getConfig(); + const { graphqlEndpoint } = getConfig(); const internalUri = href - ? replaceHostUrl( - href, - homeUrl, - nextUrl - // @todo: Remove replace when the graphql endpoint is removed from the pagination links. - )?.replace( `/${ graphqlEndpoint }`, '' ) + ? toFrontendUri( href )?.replace( `/${ graphqlEndpoint }`, '' ) // @todo: Remove replace when the graphql endpoint is removed from the pagination links. : ''; - // @todo replace internalUri?.startsWith conditional check with something more robust that will incorporate both frontend/backend domain & anything in the list of allowed images domain in the config (ref: https://github.com/rtCamp/headless/pull/241#discussion_r1824274200). TBD after https://github.com/rtCamp/headless/issues/218. - if ( - ! internalUri?.startsWith( '/' ) && - ! internalUri?.startsWith( nextUrl ) - ) { + if ( ! isInternalUrl( internalUri ) ) { return ( => { snapWPConfigPath = url.pathToFileURL( snapWPConfigPath ).href; setConfig(); - const homeUrl = new URL( getConfig().homeUrl ); + const homeUrl = new URL( getConfig().wpHomeUrl ); const userImages = nextConfig?.images ?? {}; const userRemotePatterns = userImages.remotePatterns ?? []; diff --git a/packages/next/src/middleware/cors.ts b/packages/next/src/middleware/cors.ts index bb90bc3c..56077947 100644 --- a/packages/next/src/middleware/cors.ts +++ b/packages/next/src/middleware/cors.ts @@ -13,7 +13,7 @@ import { Logger } from '@snapwp/core'; * as the first path element will be proxied to WP server. * * eg: http://localhost:3000/proxy/assets/api.js will get resouce at https://examplewp/assets/api.js - * assuming env vars NEXT_PUBLIC_URL had its value set to http://localhost:3000 and NEXT_PUBLIC_WORDPRESS_URL to https://examplewp.com + * assuming env vars NEXT_PUBLIC_FRONTEND_URL had its value set to http://localhost:3000 and NEXT_PUBLIC_WP_HOME_URL to https://examplewp.com * * @param next - Next middleware * @return The response object with modified headers @@ -22,15 +22,18 @@ export const corsProxyMiddleware: MiddlewareFactory = ( next: NextMiddleware ) => { return async ( request: NextRequest, _next: NextFetchEvent ) => { - const { homeUrl, corsProxyPrefix } = getConfig(); + const { wpHomeUrl, corsProxyPrefix } = getConfig(); - if ( ! request.nextUrl.pathname.startsWith( corsProxyPrefix ) ) { + // Adding nonnull assertion because this middleware will only be called if corsProxyPrefix is set + if ( ! request.nextUrl.pathname.startsWith( corsProxyPrefix! ) ) { return next( request, _next ); } // Construct the target URL const targetUrl = - homeUrl + request.nextUrl.pathname.replace( corsProxyPrefix, '' ); + wpHomeUrl + + // Adding nonnull assertion because this middleware will only be called if corsProxyPrefix is set + request.nextUrl.pathname.replace( corsProxyPrefix!, '' ); try { // Forward the request to the external API const response = await fetch( targetUrl, { diff --git a/packages/next/src/middleware/proxies.ts b/packages/next/src/middleware/proxies.ts index 501b4234..4213a586 100644 --- a/packages/next/src/middleware/proxies.ts +++ b/packages/next/src/middleware/proxies.ts @@ -20,7 +20,7 @@ export const proxies: MiddlewareFactory = ( next: NextMiddleware ) => { return async ( request: NextRequest, _next: NextFetchEvent ) => { const nextPath = request.nextUrl.pathname; - const { homeUrl, uploadsDirectory, restUrlPrefix } = getConfig(); + const { wpHomeUrl, uploadsDirectory, restUrlPrefix } = getConfig(); // Proxy for WordPress uploads. const uploadsRegex = new RegExp( @@ -31,24 +31,28 @@ export const proxies: MiddlewareFactory = ( next: NextMiddleware ) => { const match = uploadsRegex.exec( nextPath ); if ( match && match[ 0 ] ) { - return NextResponse.redirect( new URL( match[ 0 ], homeUrl ) ); + return NextResponse.redirect( + new URL( match[ 0 ], wpHomeUrl ) + ); } } // Proxy for WordPress APIs. - // If nextPath starts with restUrlPrefix, redirect to homeUrl/pathName. + // If nextPath starts with restUrlPrefix, redirect to wpHomeUrl/pathName. if ( nextPath.startsWith( restUrlPrefix ) ) { const APIRegex = new RegExp( `${ restUrlPrefix }.*` ); const match = APIRegex.exec( request.nextUrl.toString() ); if ( match && match[ 0 ] ) { - return NextResponse.redirect( new URL( match[ 0 ], homeUrl ) ); + return NextResponse.redirect( + new URL( match[ 0 ], wpHomeUrl ) + ); } } // Proxy for Admin AJAX. if ( '/wp-admin/admin-ajax.php' === nextPath ) { return NextResponse.redirect( - new URL( '/wp-admin/admin-ajax.php', homeUrl ) + new URL( '/wp-admin/admin-ajax.php', wpHomeUrl ) ); } diff --git a/packages/next/src/middleware/utils.ts b/packages/next/src/middleware/utils.ts index 6695e7a1..5a128557 100644 --- a/packages/next/src/middleware/utils.ts +++ b/packages/next/src/middleware/utils.ts @@ -48,11 +48,11 @@ export function appMiddlewares( * @return Array combining default middlewares and custom middlewares. */ export function stackMiddlewares( middlewares: MiddlewareFactory[] = [] ) { - const { useCorsProxy } = getConfig(); + const { corsProxyPrefix } = getConfig(); const defaultMiddlewares = [ cm, proxies ]; - if ( useCorsProxy ) { + if ( corsProxyPrefix ) { defaultMiddlewares.push( corsProxyMiddleware ); } diff --git a/packages/next/src/template-renderer/template-scripts.tsx b/packages/next/src/template-renderer/template-scripts.tsx index fb1b39a4..6d1fcf08 100644 --- a/packages/next/src/template-renderer/template-scripts.tsx +++ b/packages/next/src/template-renderer/template-scripts.tsx @@ -39,14 +39,14 @@ const ImportMap = ( { scriptModules: ScriptModuleProps[]; } ) => { // Generate import map from all module dependencies - const { homeUrl, corsProxyPrefix, useCorsProxy } = getConfig(); + const { wpHomeUrl, corsProxyPrefix } = getConfig(); const imports = scriptModules.reduce< Record< string, string > >( ( acc, module ) => { module.dependencies?.forEach( ( dep ) => { const { handle, src } = dep?.connectedScriptModule!; - acc[ handle ] = useCorsProxy - ? src.replace( homeUrl, corsProxyPrefix ) + acc[ handle ] = corsProxyPrefix + ? src.replace( wpHomeUrl, corsProxyPrefix ) : src; } ); return acc; @@ -86,7 +86,7 @@ const ScriptModuleMap = ( { }: { scriptModules?: ScriptModuleProps[]; } ) => { - const { homeUrl, corsProxyPrefix, useCorsProxy } = getConfig(); + const { wpHomeUrl, corsProxyPrefix } = getConfig(); // Array to store handles of script modules that should not be loaded const uniqueScriptModuleDependencies = new Set< string >(); @@ -131,8 +131,8 @@ const ScriptModuleMap = ( { return null; } - src = useCorsProxy - ? src.replace( homeUrl, corsProxyPrefix ) + src = corsProxyPrefix + ? src.replace( wpHomeUrl, corsProxyPrefix ) : src; // We use this to prevent (re)loading the main script module if it's already included in the page. diff --git a/packages/query/codegen.ts b/packages/query/codegen.ts index b92e74dc..72cbcf89 100644 --- a/packages/query/codegen.ts +++ b/packages/query/codegen.ts @@ -12,7 +12,7 @@ const config: CodegenConfig = { schema: process.env.GRAPHQL_SCHEMA_FILE ?? [ { [ generateGraphqlUrl( - process.env.NEXT_PUBLIC_WORDPRESS_URL, + process.env.NEXT_PUBLIC_WP_HOME_URL, process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT ) ]: { headers: { diff --git a/packages/query/src/query-engine/index.ts b/packages/query/src/query-engine/index.ts index 4b43667a..a21aea1b 100644 --- a/packages/query/src/query-engine/index.ts +++ b/packages/query/src/query-engine/index.ts @@ -34,8 +34,8 @@ export class QueryEngine { public static initialize() { QueryEngine.graphqlEndpoint = getGraphqlUrl(); - const { homeUrl } = getConfig(); - QueryEngine.homeUrl = homeUrl; + const { wpHomeUrl } = getConfig(); + QueryEngine.homeUrl = wpHomeUrl; QueryEngine.apolloClient = new ApolloClient( { uri: QueryEngine.graphqlEndpoint, diff --git a/packages/types/src/global/env.ts b/packages/types/src/global/env.ts index 5eee4fb2..d6e1d433 100644 --- a/packages/types/src/global/env.ts +++ b/packages/types/src/global/env.ts @@ -5,12 +5,12 @@ declare global { GRAPHQL_SCHEMA_FILE?: string; INTROSPECTION_TOKEN?: string; NEXT_PUBLIC_CORS_PROXY_PREFIX?: string; + NEXT_PUBLIC_FRONTEND_URL?: string; NEXT_PUBLIC_GRAPHQL_ENDPOINT?: string; - NEXT_PUBLIC_URL?: string; - NEXT_PUBLIC_USE_CORS_PROXY?: string; - NEXT_PUBLIC_WORDPRESS_REST_URL_PREFIX?: string; - NEXT_PUBLIC_WORDPRESS_UPLOADS_PATH?: string; - NEXT_PUBLIC_WORDPRESS_URL?: string; + NEXT_PUBLIC_REST_URL_PREFIX?: string; + NEXT_PUBLIC_WP_HOME_URL?: string; + NEXT_PUBLIC_WP_SITE_URL?: string; + NEXT_PUBLIC_WP_UPLOADS_DIRECTORY?: string; NODE_ENV?: 'development' | 'production' | 'test'; } }