diff --git a/packages/cli-kit/src/public/common/url.test.ts b/packages/cli-kit/src/public/common/url.test.ts index c3dea50d3e..ed72607d17 100644 --- a/packages/cli-kit/src/public/common/url.test.ts +++ b/packages/cli-kit/src/public/common/url.test.ts @@ -1,4 +1,4 @@ -import {isValidURL, safeParseURL} from './url.js' +import {extractHost, extractMyshopifyHandle, isValidURL, safeParseURL} from './url.js' import {describe, expect, test} from 'vitest' describe('isValidURL', () => { @@ -57,3 +57,34 @@ describe('safeParseURL', () => { expect(result).toBeUndefined() }) }) + +describe('extractHost', () => { + test('returns the hostname for a full URL', () => { + expect(extractHost('https://Shop.MyShopify.com/admin')).toBe('shop.myshopify.com') + }) + + test('strips the scheme and path for a bare host string', () => { + expect(extractHost('shop.myshopify.com/admin')).toBe('shop.myshopify.com') + }) + + test('returns undefined for null/undefined/empty input', () => { + expect(extractHost(null)).toBeUndefined() + expect(extractHost(undefined)).toBeUndefined() + expect(extractHost('')).toBeUndefined() + }) +}) + +describe('extractMyshopifyHandle', () => { + test('extracts the subdomain from a myshopify.com URL', () => { + expect(extractMyshopifyHandle('https://my-shop.myshopify.com')).toBe('my-shop') + }) + + test('returns undefined when the host is not a myshopify.com domain', () => { + expect(extractMyshopifyHandle('https://example.com')).toBeUndefined() + }) + + test('returns undefined for null/undefined input', () => { + expect(extractMyshopifyHandle(null)).toBeUndefined() + expect(extractMyshopifyHandle(undefined)).toBeUndefined() + }) +}) diff --git a/packages/cli-kit/src/public/common/url.ts b/packages/cli-kit/src/public/common/url.ts index 6a6086efe8..12b3316b32 100644 --- a/packages/cli-kit/src/public/common/url.ts +++ b/packages/cli-kit/src/public/common/url.ts @@ -28,3 +28,32 @@ export function safeParseURL(url: string): URL | undefined { return undefined } } + +/** + * Extracts the lowercased hostname from a URL-shaped string. Tolerates + * bare hosts (without a scheme) and inputs that come back from APIs as + * either `https://shop.myshopify.com` or `shop.myshopify.com`. + * + * @param value - A URL or bare host string, possibly null/undefined. + * @returns The lowercased hostname, or undefined when the input is empty. + */ +export function extractHost(value: string | null | undefined): string | undefined { + if (!value) return undefined + const lowered = value.toLowerCase() + const parsed = safeParseURL(lowered) + if (parsed) return parsed.hostname + return lowered.replace(/^https?:\/\//, '').split('/')[0] +} + +/** + * Extracts the subdomain handle from a `*.myshopify.com` URL or host. + * + * @param value - A URL or host string, possibly null/undefined. + * @returns The myshopify subdomain handle, or undefined when the input isn't a `*.myshopify.com` URL. + */ +export function extractMyshopifyHandle(value: string | null | undefined): string | undefined { + const host = extractHost(value) + if (!host) return undefined + const match = host.match(/^([^.]+)\.myshopify\.com$/) + return match ? match[1] : undefined +}