Saltearse al contenido

effects/WordPressAPI/utils

Esta página aún no está disponible en tu idioma.

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:24^

  • { apiEndpoint: Effect<URL, AstroUserError, APIEndpointConfig>; cleanUpHtml: Effect<any, AstroUserError, StringConfig>; downloadAndUpdateImages: Effect<any, boolean | AstroUserError | UnknownException, DownloadPostImageConfig>; downloadPostImage: Effect<undefined | string, boolean | UnknownException, DownloadPostImageConfig>; fetchAll: (url: URL, page: number, results: any[]) => Effect<any[], AstroUserError, never>; stripHtml: Effect<any, UnknownException, StringConfig>; turndown: Effect<any, AstroUserError, StringConfig>; } & { _tag: "WordPressAPIUtils"; }

new WordPressAPIUtils(_: {
apiEndpoint: Effect<URL, AstroUserError, APIEndpointConfig>;
cleanUpHtml: Effect<any, AstroUserError, StringConfig>;
downloadAndUpdateImages: Effect<any, boolean | AstroUserError | UnknownException, DownloadPostImageConfig>;
downloadPostImage: Effect<undefined | string, boolean | UnknownException, DownloadPostImageConfig>;
fetchAll: (url: URL, page: number, results: any[]) => Effect<any[], AstroUserError, never>;
stripHtml: Effect<any, UnknownException, StringConfig>;
turndown: Effect<any, AstroUserError, StringConfig>;
}): WordPressAPIUtils

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Effect.d.ts:26559

Effect<URL, AstroUserError, APIEndpointConfig>

Effect<any, AstroUserError, StringConfig>

Effect<any, boolean | AstroUserError | UnknownException, DownloadPostImageConfig>

Effect<undefined | string, boolean | UnknownException, DownloadPostImageConfig>

(url: URL, page: number, results: any[]) => Effect<any[], AstroUserError, never>

Effect<any, UnknownException, StringConfig>

Effect<any, AstroUserError, StringConfig>

WordPressAPIUtils

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).constructor

readonly _tag: "WordPressAPIUtils";

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Effect.d.ts:26560

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
})._tag

apiEndpoint: Effect<URL, AstroUserError, APIEndpointConfig>;

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:364^

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).apiEndpoint

cleanUpHtml: Effect<any, AstroUserError, StringConfig>;

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:362^

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).cleanUpHtml

downloadAndUpdateImages: Effect<any, boolean | AstroUserError | UnknownException, DownloadPostImageConfig>;

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:366^

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).downloadAndUpdateImages

downloadPostImage: Effect<undefined | string, boolean | UnknownException, DownloadPostImageConfig>;

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:365^

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).downloadPostImage

fetchAll: (url: URL, page: number, results: any[]) => Effect<any[], AstroUserError, never>;

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:363^

Fetch all pages for a paginated WP endpoint.

URL

number = 1

any[] = []

Effect<any[], AstroUserError, never>

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).fetchAll

stripHtml: Effect<any, UnknownException, StringConfig>;

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:361^

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).stripHtml

turndown: Effect<any, AstroUserError, StringConfig>;

Defined in: studiocms/packages/@studiocms/devapps/src/effects/WordPressAPI/utils.ts:360^

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).turndown

readonly static _op: "Tag";

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:28

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
})._op

readonly static [ChannelTypeId]: VarianceStruct<never, unknown, never, unknown, WordPressAPIUtils, unknown, WordPressAPIUtils>;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Channel.d.ts:108

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[ChannelTypeId]

readonly static [EffectTypeId]: VarianceStruct<WordPressAPIUtils, never, WordPressAPIUtils>;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Effect.d.ts:195

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[EffectTypeId]

static optional [ignoreSymbol]: TagUnifyIgnore;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:41

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[ignoreSymbol]

readonly static [SinkTypeId]: VarianceStruct<WordPressAPIUtils, unknown, never, never, WordPressAPIUtils>;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Sink.d.ts:82

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[SinkTypeId]

readonly static [STMTypeId]: {
_A: Covariant<WordPressAPIUtils>;
_E: Covariant<never>;
_R: Covariant<WordPressAPIUtils>;
};

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/STM.d.ts:136

readonly _A: Covariant<WordPressAPIUtils>;
readonly _E: Covariant<never>;
readonly _R: Covariant<WordPressAPIUtils>;
Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[STMTypeId]

readonly static [StreamTypeId]: VarianceStruct<WordPressAPIUtils, never, WordPressAPIUtils>;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Stream.d.ts:111

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[StreamTypeId]

readonly static [TagTypeId]: {
_Identifier: Invariant<WordPressAPIUtils>;
_Service: Invariant<WordPressAPIUtils>;
};

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:31

readonly _Identifier: Invariant<WordPressAPIUtils>;
readonly _Service: Invariant<WordPressAPIUtils>;
Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[TagTypeId]

static optional [typeSymbol]: unknown;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:39

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[typeSymbol]

static optional [unifySymbol]: TagUnify<Class<WordPressAPIUtils, "WordPressAPIUtils", {
effect: Effect<{
apiEndpoint: Effect<URL, AstroUserError, APIEndpointConfig>;
cleanUpHtml: Effect<any, AstroUserError, StringConfig>;
downloadAndUpdateImages: Effect<any, boolean | AstroUserError | UnknownException, DownloadPostImageConfig>;
downloadPostImage: Effect<undefined | string, boolean | UnknownException, DownloadPostImageConfig>;
fetchAll: (url: URL, page: number, results: any[]) => Effect<any[], AstroUserError, never>;
stripHtml: Effect<any, UnknownException, StringConfig>;
turndown: Effect<any, AstroUserError, StringConfig>;
}, never, never>;
}>>;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:40

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[unifySymbol]

readonly static Default: Layer<WordPressAPIUtils, never, never>;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Effect.d.ts:26567

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).Default

readonly static Identifier: WordPressAPIUtils;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:30

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).Identifier

readonly static key: "WordPressAPIUtils";

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:38

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).key

readonly static make: (_: {
apiEndpoint: Effect<URL, AstroUserError, APIEndpointConfig>;
cleanUpHtml: Effect<any, AstroUserError, StringConfig>;
downloadAndUpdateImages: Effect<any, boolean | AstroUserError | UnknownException, DownloadPostImageConfig>;
downloadPostImage: Effect<undefined | string, boolean | UnknownException, DownloadPostImageConfig>;
fetchAll: (url: URL, page: number, results: any[]) => Effect<any[], AstroUserError, never>;
stripHtml: Effect<any, UnknownException, StringConfig>;
turndown: Effect<any, AstroUserError, StringConfig>;
}) => WordPressAPIUtils;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Effect.d.ts:26563

Effect<URL, AstroUserError, APIEndpointConfig>

Effect<any, AstroUserError, StringConfig>

Effect<any, boolean | AstroUserError | UnknownException, DownloadPostImageConfig>

Effect<undefined | string, boolean | UnknownException, DownloadPostImageConfig>

(url: URL, page: number, results: any[]) => Effect<any[], AstroUserError, never>

Effect<any, UnknownException, StringConfig>

Effect<any, AstroUserError, StringConfig>

WordPressAPIUtils

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).make

readonly static Service: WordPressAPIUtils;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:29

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).Service

readonly static optional stack: string;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:37

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).stack

readonly static use: <X>(body: (_: WordPressAPIUtils) => X) => [X] extends [Effect<A, E, R>] ? Effect<A, E,
| WordPressAPIUtils
| R> : [X] extends [PromiseLike<A>] ? Effect<A, UnknownException, WordPressAPIUtils> : Effect<X, never, WordPressAPIUtils>;

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Effect.d.ts:26562

X

(_: WordPressAPIUtils) => X

[X] extends [Effect<A, E, R>] ? Effect<A, E, | WordPressAPIUtils | R> : [X] extends [PromiseLike<A>] ? Effect<A, UnknownException, WordPressAPIUtils> : Effect<X, never, WordPressAPIUtils>

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).use

static iterator: EffectGenerator<Tag<WordPressAPIUtils, WordPressAPIUtils>>

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Effect.d.ts:137

EffectGenerator<Tag<WordPressAPIUtils, WordPressAPIUtils>>

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[iterator]

static NodeInspectSymbol: unknown

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Inspectable.d.ts:22

unknown

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).[NodeInspectSymbol]

static context(self: WordPressAPIUtils): Context<WordPressAPIUtils>

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:36

WordPressAPIUtils

Context<WordPressAPIUtils>

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).context

static of(self: WordPressAPIUtils): WordPressAPIUtils

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Context.d.ts:35

WordPressAPIUtils

WordPressAPIUtils

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).of

static pipe<A>(this: A): A

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:10

A

A

A

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B>(this: A, ab: (_: A) => B): B

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:11

A

B = never

A

(_: A) => B

B

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C): C

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:12

A

B = never

C = never

A

(_: A) => B

(_: B) => C

C

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D): D

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:13

A

B = never

C = never

D = never

A

(_: A) => B

(_: B) => C

(_: C) => D

D

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E): E

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:14

A

B = never

C = never

D = never

E = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

E

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F): F

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:15

A

B = never

C = never

D = never

E = never

F = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

F

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G): G

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:16

A

B = never

C = never

D = never

E = never

F = never

G = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

G

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H): H

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:17

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

H

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I): I

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:18

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

I

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J): J

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:19

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

J

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K): K

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:20

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

K

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L): L

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:21

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

L

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M): M

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:22

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

M

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N): N

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:23

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

N

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O): O

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:24

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

O

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O,
op: (_: O) => P): P

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:25

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

P = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

(_: O) => P

P

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O,
op: (_: O) => P,
pq: (_: P) => Q): Q

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:26

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

P = never

Q = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

(_: O) => P

(_: P) => Q

Q

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O,
op: (_: O) => P,
pq: (_: P) => Q,
qr: (_: Q) => R): R

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:27

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

P = never

Q = never

R = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

(_: O) => P

(_: P) => Q

(_: Q) => R

R

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O,
op: (_: O) => P,
pq: (_: P) => Q,
qr: (_: Q) => R,
rs: (_: R) => S): S

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:28

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

P = never

Q = never

R = never

S = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

(_: O) => P

(_: P) => Q

(_: Q) => R

(_: R) => S

S

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O,
op: (_: O) => P,
pq: (_: P) => Q,
qr: (_: Q) => R,
rs: (_: R) => S,
st: (_: S) => T): T

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:29

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

P = never

Q = never

R = never

S = never

T = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

(_: O) => P

(_: P) => Q

(_: Q) => R

(_: R) => S

(_: S) => T

T

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O,
op: (_: O) => P,
pq: (_: P) => Q,
qr: (_: Q) => R,
rs: (_: R) => S,
st: (_: S) => T,
tu: (_: T) => U): U

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:30

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

P = never

Q = never

R = never

S = never

T = never

U = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

(_: O) => P

(_: P) => Q

(_: Q) => R

(_: R) => S

(_: S) => T

(_: T) => U

U

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe
static pipe<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U>(
this: A,
ab: (_: A) => B,
bc: (_: B) => C,
cd: (_: C) => D,
de: (_: D) => E,
ef: (_: E) => F,
fg: (_: F) => G,
gh: (_: G) => H,
hi: (_: H) => I,
ij: (_: I) => J,
jk: (_: J) => K,
kl: (_: K) => L,
lm: (_: L) => M,
mn: (_: M) => N,
no: (_: N) => O,
op: (_: O) => P,
pq: (_: P) => Q,
qr: (_: Q) => R,
rs: (_: R) => S,
st: (_: S) => T,
tu: (_: T) => U): U

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Pipeable.d.ts:31

A

B = never

C = never

D = never

E = never

F = never

G = never

H = never

I = never

J = never

K = never

L = never

M = never

N = never

O = never

P = never

Q = never

R = never

S = never

T = never

U = never

A

(_: A) => B

(_: B) => C

(_: C) => D

(_: D) => E

(_: E) => F

(_: F) => G

(_: G) => H

(_: H) => I

(_: I) => J

(_: J) => K

(_: K) => L

(_: L) => M

(_: M) => N

(_: N) => O

(_: O) => P

(_: P) => Q

(_: Q) => R

(_: R) => S

(_: S) => T

(_: T) => U

U

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).pipe

static toJSON(): unknown

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Inspectable.d.ts:21

unknown

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).toJSON

static toString(): string

Defined in: node_modules/.pnpm/effect@3.17.3/node_modules/effect/dist/dts/Inspectable.d.ts:20

string

Effect.Service<WordPressAPIUtils>()('WordPressAPIUtils', {
effect: genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect')(function () {
const failedDownloads = new Set<string>();
const TDService = Effect.fn(<T>(fn: (turndown: TurndownService) => T) =>
Effect.try({
try: () => fn(_TurndownService),
catch: (cause) =>
new AstroError(
'Turndown Error',
Failed to convert HTML to Markdown: ${(cause as Error).message}
),
})
);
const turndown = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.turndown')(
function () {
const { str } = yield StringConfig;
return yield TDService((TD) => TD.turndown(str));
}
);
/
Removes all HTML tags from a given string.
@param string - The input string containing HTML tags.
@returns The input string with all HTML tags removed.
/
const stripHtml = genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.stripHtml')(
function () {
const { str } = yield StringConfig;
return yield Effect.try(() => sanitizeHtml(str));
}
);
/
Effectful version of 'cheerio.load()
*/
const loadHTML = Effect.fn(<T>(fn: (load: CheerioLoad) => T) =>
Effect.try({
try: () => fn(cheerio.load),
catch: (err) =>
new AstroError('Error loading content', err instanceof Error ? err.message : ${err}),
})
);
/**
* Cleans up the provided HTML string by removing certain attributes from images
* and modifying specific elements related to WordPress polls.
*
* @param html - The HTML string to be cleaned up.
* @returns The cleaned-up HTML string.
*/
const cleanUpHtml = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.cleanUpHtml'
)(function* () {
const { str } = yield* StringConfig;
const data = yield* loadHTML((fn) => fn(str));
const images = data('img');
for (const image of images) {
data(image)
.removeAttr('class')
.removeAttr('width')
.removeAttr('height')
.removeAttr('data-recalc-dims')
.removeAttr('sizes')
.removeAttr('srcset');
}
data('.wp-polls').html(
'<em>Polls have been temporarily removed while we migrate to a new platform.</em>'
);
data('.wp-polls.loading').remove();
return data.html();
});
/**
* Fetch all pages for a paginated WP endpoint.
*/
const fetchAll = (
url: URL,
page = 1,
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
results: any[] = []
// biome-ignore lint/suspicious/noExplicitAny: This is a dynamic function that could return anything as an array
): Effect.Effect<any[], AstroError, never> =>
genLogger('@studiocms/devapps/effects/WordPressAPI/utils.effect.fetchAll')(function* () {
url.searchParams.set('per_page', '100');
url.searchParams.set('page', String(page));
// Fetch and return data and headers for usage later
const res = yield* Effect.tryPromise({
try: () => fetch(url),
catch: (err) =>
new AstroError(
'Unknown Error while querying API',
err instanceof Error ? err.message : ${err}
),
});
let data = yield* Effect.tryPromise({
try: () => res.json(),
catch: (err) =>
new AstroError(
'Unknown Error while reading API data',
err instanceof Error ? err.message : ${err}
),
});
// Check for errors
if (!Array.isArray(data)) {
if (typeof data === 'object') {
data = Object.entries(data).map(([id, val]) => {
if (typeof val === 'object') return { id, ...val };
return { id };
});
} else {
return yield* Effect.fail(
new AstroError(
'Expected WordPress API to return an array of items.',
Received ${typeof data}:\n\n```json\n${JSON.stringify(data, null, 2)}\n```
)
);
}
}
// Append the results
results.push(...data);
// Check for pagination
const totalPages = Number.parseInt(res.headers.get('X-WP-TotalPages') || '1');
yield* Console.log('Fetched page', page, 'of', totalPages);
// If pagination, Recurse through the pages.
if (page < totalPages) {
yield* Console.log('Fetching next page...');
return yield* fetchAll(url, page + 1, results);
}
// Return final results
return results;
});
/**
* Constructs a WordPress API endpoint URL based on the provided parameters.
*
* @param endpoint - The base URL of the WordPress website.
* @param type - The type of resource to access. Can be 'posts', 'pages', 'media', 'categories', 'tags', or 'settings'.
* @param path - An optional path to append to the endpoint.
* @returns The constructed URL object pointing to the desired API endpoint.
* @throws {AstroError} If the endpoint argument is missing.
*/
const apiEndpoint = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.apiEndpoint'
)(function* () {
const { endpoint, type, path } = yield* APIEndpointConfig;
if (!endpoint) {
return yield* Effect.fail(
new AstroError(
'Missing endpoint argument.',
'Please pass a URL to your WordPress website as the endpoint option to the WordPress importer. Most commonly this looks something like https://example.com/'
)
);
}
let newEndpoint = endpoint;
if (!newEndpoint.endsWith('/')) newEndpoint += '/';
const apiBase = new URL(newEndpoint);
if (type === 'settings') {
apiBase.pathname = 'wp-json/';
return apiBase;
}
apiBase.pathname = wp-json/wp/v2/${type}/${path ? ${path}/ : ''};
return apiBase;
});
/**
* Downloads an image from the specified URL and saves it to the given destination.
*
* @param {string | URL} imageUrl - The URL of the image to download.
* @param {string | URL} destination - The file path where the image should be saved.
*/
const downloadImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadImage'
)(function* () {
const { destination, imageUrl } = yield* DownloadImageConfig;
const fileExists = yield* Effect.tryPromise({
try: () => access(destination, constants.F_OK).then(() => true),
catch: () => false,
});
if (fileExists) {
yield* Console.error('File already exists:', destination);
return true;
}
// Validate destination file extension
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
const ext = path.extname(destination.toString()).toLowerCase();
if (!allowedExtensions.includes(ext)) {
yield* Console.error('Invalid file extension:', ext);
return false;
}
const response = yield* Effect.tryPromise(() => fetch(imageUrl));
// Validate content type
const contentType = response.headers.get('content-type');
if (!contentType?.startsWith('image/')) {
yield* Console.error('Invalid content type:', contentType);
return false;
}
// Check content length
const contentLength = response.headers.get('content-length');
const maxSize = 100 * 1024 * 1024; // 100MB limit
if (contentLength && Number.parseInt(contentLength) > maxSize) {
yield* Console.error('File too large:', contentLength);
return false;
}
if (response.ok && response.body) {
const reader = response.body.getReader();
const chunks = [];
let done = false;
while (!done) {
const { done: readerDone, value } = yield* Effect.tryPromise(() => reader.read());
if (value) chunks.push(value);
done = readerDone;
}
const fileBuffer = Buffer.concat(chunks);
yield* Effect.tryPromise(() => writeFile(destination, fileBuffer, { flag: 'wx' }));
yield* Console.log('Downloaded image:', imageUrl);
return true;
}
yield* Console.error('Failed to download image:', imageUrl);
return false;
});
/**
* Downloads an image from the given source URL and saves it to the specified folder.
*
* @param src - The URL of the image to download.
* @param pathToFolder - The path to the folder where the image should be saved.
* @returns The file name of the downloaded image if successful, otherwise undefined.
*
* @remarks
* - If the src or pathToFolder parameters are not provided, the function will return immediately.
* - If the specified folder does not exist, it will be created recursively.
* - If the image already exists in the specified folder, the function will log a message and skip the download.
* - If the image download fails, the source URL will be added to the imagesNotDownloaded array.
*/
const downloadPostImage = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadPostImage'
)(function* () {
const { str: src, pathToFolder } = yield* DownloadPostImageConfig;
if (!src || !pathToFolder) return;
if (!fs.existsSync(pathToFolder)) {
fs.mkdirSync(pathToFolder, { recursive: true });
}
const baseName = path.basename(src);
const fileName = baseName.split('?')[0];
if (!fileName) {
yield* Console.error('Invalid image URL:', src);
return undefined;
}
const destinationFile = path.resolve(pathToFolder, fileName);
if (fs.existsSync(destinationFile)) {
yield* Console.log(Post/Page image "${destinationFile}" already exists, skipping...);
return fileName;
}
const imageDownloaded = yield downloadImage.pipe(
DownloadImageConfig.makeProvide(src, destinationFile)
);
if (!imageDownloaded) failedDownloads.add(src);
return imageDownloaded ? fileName : undefined;
});
/
Downloads and updates the image sources in the provided HTML string.
This function parses the given HTML string, finds all image elements,
downloads the images to the specified folder, and updates the image
sources to point to the downloaded images.
@param html - The HTML string containing image elements to be processed.
@param pathToFolder - The path to the folder where images should be downloaded.
@returns A promise that resolves to the updated HTML string with new image sources.
/
const downloadAndUpdateImages = genLogger(
'@studiocms/devapps/effects/WordPressAPI/utils.effect.downloadAndUpdateImages'
)(function () {
const { str: html, pathToFolder } = yield DownloadPostImageConfig;
const data = yield loadHTML((fn) => fn(html));
const images = data('img');
for (const image of images) {
const src = data(image).attr('src');
if (src) {
const newSrc = yield downloadPostImage.pipe(
DownloadPostImageConfig.makeProvide(src, pathToFolder)
);
if (newSrc) {
data(image).attr('src', newSrc);
} else {
// Either remove the image or keep original src
data(image).attr('src', src);
}
}
}
return data.html();
});
return {
turndown,
stripHtml,
cleanUpHtml,
fetchAll,
apiEndpoint,
downloadPostImage,
downloadAndUpdateImages,
};
}),
}).toString