Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/utilities/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# @faustjs/utilities

A collection of utility functions for Faust.js applications.

## Installation

```bash
npm install @faustjs/utilities
```

Or if you're using pnpm:

```bash
pnpm add @faustjs/utilities
```

## Requirements

- Node.js >= 18
- npm >= 8

## Available Utilities

This package provides several utility functions commonly used in Faust.js applications:

- **Assert**: Assertion utilities for runtime type checking
- **Convert**: Conversion utilities
- **Flat List to Hierarchical**: Convert flat lists to hierarchical structures
- **Debug Mode**: Check if debug mode is enabled
- **WordPress Preview**: Utilities for handling WordPress preview functionality
- **Logging**: Enhanced logging utilities with chalk integration

## Usage

Import the utilities you need from the package:

```typescript
import { isDebug, isWordPressPreview, log } from '@faustjs/utilities';
```

## Building

To build the package:

```bash
pnpm build
```

This will build both ESM and CommonJS versions of the package.

## Contributing

For information about contributing to this package, please refer to the main [Faust.js repository](https://github.com/wpengine/faustjs).

## License

This package is part of the Faust.js project. See the project's LICENSE file for details.

## About Faust.js

Faust.js is a framework for building headless WordPress sites using modern JavaScript tools and frameworks. For more information, visit the [Faust.js documentation](https://faustjs.org).
25 changes: 25 additions & 0 deletions packages/utilities/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@faustjs/utilities",
"version": "0.0.1",
"description": "A collection of utility functions for Faust.js",
"main": "dist/cjs/index.js",
"module": "dist/mjs/index.js",
"types": "dist/mjs/index.d.ts",
"devDependencies": {
"@types/node": "^18.0.6"
},
"dependencies": {
"chalk": "^4.1.2",
"lodash": "^4.17.21"
},
"scripts": {
"build": "npm run build-esm && npm run build-cjs",
"build-esm": "tsc -p .",
"build-cjs": "tsc -p tsconfig.cjs.json"
},
"author": "Faust.js Team",
"engines": {
"node": ">=18",
"npm": ">=8"
}
}
38 changes: 38 additions & 0 deletions packages/utilities/src/assert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import isString from 'lodash/isString.js';

/**
* Returns whether or not the app execution context is currently Server-Side or Client-Side
*
* @export
* @returns {boolean}
*/
export function isServerSide(): boolean {
return typeof window === 'undefined';
}

/**
* Returns whether or not a string is a base64 encoded string
*
* @export
* @param {string} str
* @returns
*/
export function isBase64(str: string): boolean {
if (!isString(str) || str.length === 0) {
return false;
}

return /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?\n?$/.test(
str.replace(/\n/g, ''),
);
}

export const previewRegex = /\/preview(\/\w|\?)/;

export function isPreviewPath(uri: string): boolean {
if (!isString(uri)) {
return false;
}

return previewRegex.test(uri);
}
222 changes: 222 additions & 0 deletions packages/utilities/src/convert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import isArrayLike from 'lodash/isArrayLike.js';
import isEmpty from 'lodash/isEmpty.js';
import isString from 'lodash/isString.js';
import isUndefined from 'lodash/isUndefined.js';
import { isBase64, isServerSide, previewRegex } from './assert.js';

/**
* The result of parsing a URL into its parts
*
* @export
* @interface ParsedUrlInfo
*/
export interface ParsedUrlInfo {
href: string;
protocol: string;
baseUrl: string;
host: string;
pathname: string;
search: string;
hash: string;
}

/**
* Decodes a base64 string, compatible server-side and client-side
*
* @export
* @param {string} str
* @returns
*/
export function base64Decode(str: string): string {
if (!isBase64(str)) {
return str;
}

if (isServerSide()) {
return Buffer.from(str, 'base64').toString('utf8');
}

return atob(str);
}

/**
* Encodes a string to base64, compatible server-side and client-side
*
* @export
* @param {string} str
* @returns
*/
export function base64Encode(str: string): string {
if (!isString(str)) {
return '';
}

if (isServerSide()) {
return Buffer.from(str, 'utf8').toString('base64');
}

return btoa(str);
}

const URL_REGEX = /^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;

/* eslint-disable consistent-return */
/**
* Parses a url into various parts
*
* @export
* @param {(string | undefined)} url
* @returns {ParsedUrlInfo}
*/
export function parseUrl(url: string | undefined): ParsedUrlInfo | undefined {
if (!url) {
return;
}

const parsed = URL_REGEX.exec(url);

if (
!isArrayLike(parsed) ||
isEmpty(parsed) ||
(isUndefined(parsed[4]) && url[0] !== '/')
) {
return;
}

return {
href: parsed[0],
protocol: parsed[1],
baseUrl: `${parsed[1]}${parsed[3]}`,
host: parsed[4],
pathname: parsed[5],
search: parsed[6],
hash: parsed[8],
};
}
/* eslint-enable consistent-return */

/**
* Gets query parameters from a url or search string
*
* @export
* @param {string} url
* @param {string} param
* @returns {string}
*/
export function getQueryParam(url: string, param: string): string {
if (!isString(url) || !isString(param) || isEmpty(url) || isEmpty(param)) {
return '';
}

const parsedUrl = parseUrl(url);

if (isUndefined(parsedUrl) || !isString(parsedUrl.search)) {
return '';
}

let query = parsedUrl.search;

if (query[0] === '?') {
query = query.substring(1);
}

const params = query.split('&');

for (let i = 0; i < params.length; i += 1) {
const pair = params[i].split('=');
if (decodeURIComponent(pair[0]) === param) {
return decodeURIComponent(pair[1]);
}
}

return '';
}

/**
* Gets the path without the protocol/host/port from a full URL string
*
* @export
* @param {string} [url]
* @returns
*/
export function getUrlPath(url?: string): string {
const parsedUrl = parseUrl(url);

if (isUndefined(parsedUrl)) {
return '/';
}

return `${parsedUrl.pathname || '/'}${parsedUrl.search || ''}`;
}

export function stripPreviewFromUrlPath(urlPath: string): string {
if (!urlPath) {
return urlPath;
}

return urlPath.replace(previewRegex, '$1');
}

/**
* Ensures that a url does not have the specified prefix in it.
*
* @export
* @param {string} url
* @param {string} [prefix]
* @returns
*/
export function resolvePrefixedUrlPath(url: string, prefix?: string): string {
let resolvedUrl = url;

if (prefix) {
resolvedUrl = url.replace(prefix, '');
}

if (resolvedUrl === '') {
resolvedUrl = '/';
}

return resolvedUrl;
}

/* eslint-disable consistent-return, @typescript-eslint/explicit-module-boundary-types */
export function getCookiesFromContext(context?: any): string | undefined {
if (!context) {
return;
}

if (context.previewData?.serverInfo) {
return context.previewData.serverInfo.cookie as string | undefined;
}

if (context.req?.headers?.cookie) {
return context.req.headers.cookie as string | undefined;
}

if (context.headers?.cookie) {
return context.headers.cookie as string | undefined;
}

if (context.cookie) {
return context.cookie as string | undefined;
}
}
/* eslint-enable consistent-return, @typescript-eslint/explicit-module-boundary-types */

export function removeURLParam(url: string, parameter: string): string {
const parts = url.split('?');
if (parts.length >= 2) {
const prefix = `${encodeURIComponent(parameter)}=`;
const pars = parts[1].split(/[&;]/g);

// eslint-disable-next-line no-plusplus
for (let i = pars.length; i-- > 0; ) {
if (pars[i].lastIndexOf(prefix, 0) !== -1) {
pars.splice(i, 1);
}
}

return parts[0] + (pars.length > 0 ? `?${pars.join('&')}` : '');
}
return url;
}
47 changes: 47 additions & 0 deletions packages/utilities/src/flatListToHierarchical.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export interface Params {
idKey?: string;
parentKey?: string;
childrenKey?: string;
}

type Data = Record<string | number, unknown>;

/**
* Converts a flat list to hierarchical.
*
* @param data The data items as an array.
* @param param1 The data item property keys.
* @returns Data Array
*/
export function flatListToHierarchical(
data: Data[] = [],
{
idKey = 'id',
parentKey = 'parentId',
childrenKey = 'children',
}: Params = {},
) {
const tree: Data[] = [];
const childrenOf: { [key: string | number]: Data[] } = {};

data.forEach((item) => {
const newItem = { ...item };

const id = newItem?.[idKey] as string | number | undefined;
const parentId = (newItem?.[parentKey] ?? 0) as string | undefined;

if (!id) {
return;
}

childrenOf[id] = childrenOf[id] || [];
newItem[childrenKey] = childrenOf[id];

// eslint-disable-next-line @typescript-eslint/no-unused-expressions
parentId
? (childrenOf[parentId] = childrenOf[parentId] || []).push(newItem)
: tree.push(newItem);
});

return tree;
}
Loading