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';
}
}