Skip to content
Open
2 changes: 0 additions & 2 deletions packages/url-utils/index.js

This file was deleted.

9 changes: 4 additions & 5 deletions packages/url-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@
},
"author": "Ghost Foundation",
"license": "MIT",
"main": "index.js",
"types": "lib/UrlUtils.d.ts",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"dev": "echo \"Implement me!\"",
"pretest": "yarn build",
"test": "NODE_ENV=testing c8 --src lib --all --reporter text --reporter cobertura --reporter html mocha './test/**/*.test.js'",
"build": "tsc -p tsconfig.json",
"lint": "eslint src test index.js --ext .js,.ts --cache",
"lint": "eslint src test --ext .js,.ts --cache",
"prepare": "NODE_ENV=production yarn build",
"posttest": "yarn lint"
},
"files": [
"lib/",
"index.js"
"lib/"
],
"publishConfig": {
"access": "public"
Expand Down
464 changes: 303 additions & 161 deletions packages/url-utils/src/UrlUtils.ts

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/url-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import UrlUtils from './UrlUtils';

export = UrlUtils;
29 changes: 20 additions & 9 deletions packages/url-utils/src/utils/absolute-to-relative.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// @ts-nocheck
// require the whatwg compatible URL library (same behaviour in node and browser)
const {URL} = require('url');
const stripSubdirectoryFromPath = require('./strip-subdirectory-from-path');
import {URL} from 'url';
import stripSubdirectoryFromPath from './strip-subdirectory-from-path';

export interface AbsoluteToRelativeOptions {
ignoreProtocol: boolean;
withoutSubdirectory: boolean;
assetsOnly: boolean;
staticImageUrlPrefix: string;
}

export type AbsoluteToRelativeOptionsInput = Partial<AbsoluteToRelativeOptions>;

/**
* Convert an absolute URL to a root-relative path if it matches the supplied root domain.
Expand All @@ -13,8 +20,8 @@ const stripSubdirectoryFromPath = require('./strip-subdirectory-from-path');
* @param {boolean} [options.withoutSubdirectory=false] Strip the root subdirectory from the returned path
* @returns {string} The passed-in url or a relative path
*/
const absoluteToRelative = function absoluteToRelative(url, rootUrl, _options = {}) {
const defaultOptions = {
const absoluteToRelative = function absoluteToRelative(url: string, rootUrl?: string, _options: AbsoluteToRelativeOptionsInput = {}): string {
const defaultOptions: AbsoluteToRelativeOptions = {
ignoreProtocol: true,
withoutSubdirectory: false,
assetsOnly: false,
Expand All @@ -29,8 +36,8 @@ const absoluteToRelative = function absoluteToRelative(url, rootUrl, _options =
}
}

let parsedUrl;
let parsedRoot;
let parsedUrl: URL;
let parsedRoot: URL | undefined;

try {
parsedUrl = new URL(url, 'http://relative');
Expand All @@ -44,6 +51,10 @@ const absoluteToRelative = function absoluteToRelative(url, rootUrl, _options =
return url;
}

if (!parsedRoot) {
return url;
}

const matchesHost = parsedUrl.host === parsedRoot.host;
const matchesProtocol = parsedUrl.protocol === parsedRoot.protocol;
const matchesPath = parsedUrl.pathname.indexOf(parsedRoot.pathname) === 0;
Expand All @@ -61,4 +72,4 @@ const absoluteToRelative = function absoluteToRelative(url, rootUrl, _options =
return url;
};

module.exports = absoluteToRelative;
export default absoluteToRelative;
35 changes: 26 additions & 9 deletions packages/url-utils/src/utils/absolute-to-transform-ready.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
// @ts-nocheck
const {URL} = require('url');
const absoluteToRelative = require('./absolute-to-relative');
import type {TransformReadyReplacementOptions} from './types';
import absoluteToRelative, {type AbsoluteToRelativeOptions} from './absolute-to-relative';
import {URL} from 'url';

function isRelative(url) {
let parsedInput;
export interface AbsoluteToTransformReadyOptions extends TransformReadyReplacementOptions, AbsoluteToRelativeOptions {
withoutSubdirectory: boolean;
staticFilesUrlPrefix?: string;
staticMediaUrlPrefix?: string;
imageBaseUrl?: string | null;
filesBaseUrl?: string | null;
mediaBaseUrl?: string | null;
}

export type AbsoluteToTransformReadyOptionsInput = Partial<AbsoluteToTransformReadyOptions>;

function isRelative(url: string): boolean {
let parsedInput: URL;
try {
parsedInput = new URL(url, 'http://relative');
} catch (e) {
Expand All @@ -14,16 +25,22 @@ function isRelative(url) {
return parsedInput.origin === 'http://relative';
}

const absoluteToTransformReady = function (url, root, _options = {}) {
const defaultOptions = {
const absoluteToTransformReady = function (
url: string,
root: string,
_options: AbsoluteToTransformReadyOptionsInput = {}
): string {
const defaultOptions: AbsoluteToTransformReadyOptions = {
replacementStr: '__GHOST_URL__',
withoutSubdirectory: true,
staticImageUrlPrefix: 'content/images',
staticFilesUrlPrefix: 'content/files',
staticMediaUrlPrefix: 'content/media',
imageBaseUrl: null,
filesBaseUrl: null,
mediaBaseUrl: null
mediaBaseUrl: null,
ignoreProtocol: true,
assetsOnly: false
};
const options = Object.assign({}, defaultOptions, _options);

Expand Down Expand Up @@ -66,4 +83,4 @@ const absoluteToTransformReady = function (url, root, _options = {}) {
return url;
};

module.exports = absoluteToTransformReady;
export default absoluteToTransformReady;
19 changes: 12 additions & 7 deletions packages/url-utils/src/utils/build-early-exit-match.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
// @ts-nocheck
function escapeRegExp(string) {
import type {BaseUrlOptionsInput} from './types';

function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

type BuildEarlyExitMatchOptions = BaseUrlOptionsInput & {
ignoreProtocol?: boolean;
};

/**
* Build a regex pattern that matches any of the configured base URLs (site URL + CDN URLs).
* This is used for early exit optimizations - if content doesn't contain any of these URLs,
Expand All @@ -16,14 +21,14 @@ function escapeRegExp(string) {
* @param {boolean} [options.ignoreProtocol=true] - Whether to strip protocol from URLs
* @returns {string|null} Regex pattern matching any configured base URL, or null if none configured
*/
function buildEarlyExitMatch(siteUrl, options = {}) {
function buildEarlyExitMatch(siteUrl: string, options: BuildEarlyExitMatchOptions = {}): string | null {
const candidates = [siteUrl, options.imageBaseUrl, options.filesBaseUrl, options.mediaBaseUrl]
.filter(Boolean)
.map((value) => {
.filter((value: string | null | undefined): value is string => typeof value === 'string' && value.length > 0)
.map((value: string) => {
let normalized = options.ignoreProtocol ? value.replace(/http:|https:/, '') : value;
return normalized.replace(/\/$/, '');
})
.filter(Boolean)
.filter((value: string): boolean => Boolean(value))
.map(escapeRegExp);

if (!candidates.length) {
Expand All @@ -37,7 +42,7 @@ function buildEarlyExitMatch(siteUrl, options = {}) {
return `(?:${candidates.join('|')})`;
}

module.exports = {
export default {
buildEarlyExitMatch,
escapeRegExp
};
5 changes: 2 additions & 3 deletions packages/url-utils/src/utils/deduplicate-double-slashes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// @ts-nocheck
function deduplicateDoubleSlashes(url) {
function deduplicateDoubleSlashes(url: string): string {
// Preserve protocol slashes (e.g., http://, https://) and only deduplicate
// slashes in the path portion. The pattern (^|[^:])\/\/+ matches double slashes
// that are either at the start of the string or not preceded by a colon.
return url.replace(/(^|[^:])\/\/+/g, '$1/');
}

module.exports = deduplicateDoubleSlashes;
export default deduplicateDoubleSlashes;
7 changes: 3 additions & 4 deletions packages/url-utils/src/utils/deduplicate-subdirectory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
const {URL} = require('url');
import {URL} from 'url';

/**
* Remove duplicated directories from the start of a path or url's path
Expand All @@ -8,7 +7,7 @@ const {URL} = require('url');
* @param {string} rootUrl Root URL with an optional subdirectory
* @returns {string} URL or pathname with any duplicated subdirectory removed
*/
const deduplicateSubdirectory = function deduplicateSubdirectory(url, rootUrl) {
const deduplicateSubdirectory = function deduplicateSubdirectory(url: string, rootUrl: string): string {
// force root url to always have a trailing-slash for consistent behaviour
if (!rootUrl.endsWith('/')) {
rootUrl = `${rootUrl}/`;
Expand All @@ -29,4 +28,4 @@ const deduplicateSubdirectory = function deduplicateSubdirectory(url, rootUrl) {
return url.replace(subdirRegex, `$1${subdir}/`);
};

module.exports = deduplicateSubdirectory;
export default deduplicateSubdirectory;
21 changes: 13 additions & 8 deletions packages/url-utils/src/utils/html-absolute-to-relative.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
// @ts-nocheck
const htmlTransform = require('./html-transform');
const absoluteToRelative = require('./absolute-to-relative');
import type {HtmlTransformOptionsInput} from './types';
import type {AbsoluteToRelativeOptionsInput} from './absolute-to-relative';
import htmlTransform from './html-transform';
import absoluteToRelative from './absolute-to-relative';

function htmlAbsoluteToRelative(html = '', siteUrl, _options) {
const defaultOptions = {assetsOnly: false, ignoreProtocol: true};
const options = Object.assign({}, defaultOptions, _options || {});
function htmlAbsoluteToRelative(
html: string = '',
siteUrl: string,
_options: AbsoluteToRelativeOptionsInput = {}
): string {
const defaultOptions: AbsoluteToRelativeOptionsInput = {assetsOnly: false, ignoreProtocol: true};
const options: HtmlTransformOptionsInput = Object.assign({}, defaultOptions, _options || {});

// exit early and avoid parsing if the content does not contain the siteUrl
options.earlyExitMatchStr = options.ignoreProtocol ? siteUrl.replace(/http:|https:/, '') : siteUrl;
options.earlyExitMatchStr = options.earlyExitMatchStr.replace(/\/$/, '');

// need to ignore itemPath because absoluteToRelative doesn't take that option
const transformFunction = function (_url, _siteUrl, _itemPath, __options) {
const transformFunction = function (_url: string, _siteUrl: string, _itemPath: string | null, __options: AbsoluteToRelativeOptionsInput): string {
return absoluteToRelative(_url, _siteUrl, __options);
};

return htmlTransform(html, siteUrl, transformFunction, '', options);
}

module.exports = htmlAbsoluteToRelative;
export default htmlAbsoluteToRelative;
28 changes: 18 additions & 10 deletions packages/url-utils/src/utils/html-absolute-to-transform-ready.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
// @ts-nocheck
const htmlTransform = require('./html-transform');
const absoluteToTransformReady = require('./absolute-to-transform-ready');
const {buildEarlyExitMatch} = require('./build-early-exit-match');
import type {HtmlTransformOptionsInput, AbsoluteToTransformReadyOptionsInput} from './types';
import htmlTransform from './html-transform';
import absoluteToTransformReady from './absolute-to-transform-ready';
import buildEarlyExitMatchModule from './build-early-exit-match';
const {buildEarlyExitMatch} = buildEarlyExitMatchModule;

const htmlAbsoluteToTransformReady = function (html = '', siteUrl, _options) {
const defaultOptions = {assetsOnly: false, ignoreProtocol: true};
const options = Object.assign({}, defaultOptions, _options || {});
const htmlAbsoluteToTransformReady = function (
html: string = '',
siteUrl: string,
_options: AbsoluteToTransformReadyOptionsInput = {}
): string {
const defaultOptions: AbsoluteToTransformReadyOptionsInput = {assetsOnly: false, ignoreProtocol: true};
const options: HtmlTransformOptionsInput = Object.assign({}, defaultOptions, _options || {});

// exit early and avoid parsing if the content does not contain the siteUrl or configured asset bases
options.earlyExitMatchStr = buildEarlyExitMatch(siteUrl, options);
const earlyExitMatch = buildEarlyExitMatch(siteUrl, options);
if (earlyExitMatch) {
options.earlyExitMatchStr = earlyExitMatch;
}

// need to ignore itemPath because absoluteToRelative doesn't take that option
const transformFunction = function (_url, _siteUrl, _itemPath, __options) {
const transformFunction = function (_url: string, _siteUrl: string, _itemPath: string | null, __options: AbsoluteToTransformReadyOptionsInput): string {
return absoluteToTransformReady(_url, _siteUrl, __options);
};

return htmlTransform(html, siteUrl, transformFunction, '', options);
};

module.exports = htmlAbsoluteToTransformReady;
export default htmlAbsoluteToTransformReady;
19 changes: 12 additions & 7 deletions packages/url-utils/src/utils/html-relative-to-absolute.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
// @ts-nocheck
const htmlTransform = require('./html-transform');
const relativeToAbsolute = require('./relative-to-absolute');
import type {HtmlTransformOptionsInput, SecureOptionsInput} from './types';
import htmlTransform from './html-transform';
import relativeToAbsolute from './relative-to-absolute';

function htmlRelativeToAbsolute(html = '', siteUrl, itemPath, _options) {
const defaultOptions = {assetsOnly: false, secure: false};
const options = Object.assign({}, defaultOptions, _options || {});
function htmlRelativeToAbsolute(
html: string = '',
siteUrl: string,
itemPath: string | null,
_options: SecureOptionsInput = {}
): string {
const defaultOptions: SecureOptionsInput = {assetsOnly: false, secure: false};
const options: HtmlTransformOptionsInput = Object.assign({}, defaultOptions, _options || {});

// exit early and avoid parsing if the content does not contain an attribute we might transform
options.earlyExitMatchStr = 'href=|src=|srcset=';
Expand All @@ -15,4 +20,4 @@ function htmlRelativeToAbsolute(html = '', siteUrl, itemPath, _options) {
return htmlTransform(html, siteUrl, relativeToAbsolute, itemPath, options);
}

module.exports = htmlRelativeToAbsolute;
export default htmlRelativeToAbsolute;
34 changes: 22 additions & 12 deletions packages/url-utils/src/utils/html-relative-to-transform-ready.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
// @ts-nocheck
const htmlTransform = require('./html-transform');
const relativeToTransformReady = require('./relative-to-transform-ready');
import type {HtmlTransformOptionsInput} from './types';
import htmlTransform from './html-transform';
import relativeToTransformReady, {type RelativeToTransformReadyOptionsInput as RelativeToTransformReadyOptionsInputType} from './relative-to-transform-ready';

const htmlRelativeToTransformReady = function (html = '', root, itemPath, _options) {
const htmlRelativeToTransformReady = function (
html: string = '',
root: string,
itemPath: string | null | RelativeToTransformReadyOptionsInputType,
_options?: RelativeToTransformReadyOptionsInputType
): string {
// itemPath is optional, if it's an object may be the options param instead
if (typeof itemPath === 'object' && !_options) {
_options = itemPath;
itemPath = null;
let finalItemPath: string | null = null;
let finalOptions: RelativeToTransformReadyOptionsInputType = _options || {};

if (typeof itemPath === 'object' && itemPath !== null && !_options) {
finalOptions = itemPath;
finalItemPath = null;
} else if (typeof itemPath === 'string') {
finalItemPath = itemPath;
}

const defaultOptions = {
const defaultOptions: RelativeToTransformReadyOptionsInputType = {
replacementStr: '__GHOST_URL__'
};
const overrideOptions = {
const overrideOptions: RelativeToTransformReadyOptionsInputType = {
secure: false
};
const options = Object.assign({}, defaultOptions, _options, overrideOptions);
const options: HtmlTransformOptionsInput = Object.assign({}, defaultOptions, finalOptions, overrideOptions);

// exit early and avoid parsing if the content does not contain an attribute we might transform
options.earlyExitMatchStr = 'href=|src=|srcset=';
if (options.assetsOnly) {
options.earlyExitMatchStr = options.staticImageUrlPrefix;
}

return htmlTransform(html, root, relativeToTransformReady, itemPath, options);
return htmlTransform(html, root, relativeToTransformReady, finalItemPath, options);
};

module.exports = htmlRelativeToTransformReady;
export default htmlRelativeToTransformReady;
Loading