piece-utils.ts•6.66 kB
import { readdir, stat } from 'node:fs/promises'
import * as path from 'path'
import { cwd } from 'node:process'
import { readPackageJson, readProjectJson } from './files'
import { exec } from './exec'
import axios from 'axios'
import chalk from 'chalk'
import FormData from 'form-data';
import fs from 'fs';
export const piecesPath = () => path.join(cwd(), 'packages', 'pieces')
export const customPiecePath = () => path.join(piecesPath(), 'custom')
/**
* Finds and returns the paths of specific pieces or all available pieces in a given directory.
*
* @param inputPath - The root directory to search for pieces. If not provided, a default path to custom pieces is used.
* @param pieces - An optional array of piece names to search for. If not provided, all pieces in the directory are returned.
* @returns A promise resolving to an array of strings representing the paths of the found pieces.
*/
export async function findPieces(inputPath?: string, pieces?: string[]): Promise<string[]> {
const piecesPath = inputPath ?? customPiecePath()
const piecesFolders = await traverseFolder(piecesPath)
if (pieces) {
return pieces.flatMap((piece) => {
const folder = piecesFolders.find((p) => {
const normalizedPath = path.normalize(p);
return normalizedPath.endsWith(path.sep + piece);
});
if (!folder) {
return [];
}
return [folder];
});
} else {
return piecesFolders
}
}
/**
* Finds and returns the path of a single piece. Exits the process if the piece is not found.
*
* @param pieceName - The name of the piece to search for.
* @returns A promise resolving to a string representing the path of the found piece. If not found, the process exits.
*/
export async function findPiece(pieceName: string): Promise<string | null> {
return (await findPieces(piecesPath(), [pieceName]))[0] ?? null;
}
export async function buildPiece(pieceFolder: string): Promise<{ outputFolder: string, outputFile: string }> {
const projectJson = await readProjectJson(pieceFolder);
await buildPackage(projectJson.name);
const compiledPath = `dist/packages/${removeStartingSlashes(pieceFolder).split(path.sep + 'packages')[1]}`;
const { stdout } = await exec('npm pack --json', { cwd: compiledPath });
const tarFileName = JSON.parse(stdout)[0].filename;
return {
outputFolder: compiledPath,
outputFile: path.join(compiledPath, tarFileName)
};
}
export async function buildPackage(projectName:string) {
await exec(`npx nx build ${projectName} --skip-cache`);
return {
outputFolder: `dist/packages/${projectName}`,
}
}
export async function publishPieceFromFolder(
{pieceFolder, apiUrl, apiKey, failOnError}:
{pieceFolder: string,
apiUrl: string,
apiKey: string,
failOnError: boolean,}
) {
const projectJson = await readProjectJson(pieceFolder);
const packageJson = await readPackageJson(pieceFolder);
await buildPackage(projectJson.name);
const { outputFile } = await buildPiece(pieceFolder);
const formData = new FormData();
console.log(chalk.blue(`Uploading ${outputFile}`));
formData.append('pieceArchive', fs.createReadStream(outputFile));
formData.append('pieceName', packageJson.name);
formData.append('pieceVersion', packageJson.version);
formData.append('packageType', 'ARCHIVE');
formData.append('scope', 'PLATFORM');
try {
await axios.post(`${apiUrl}/v1/pieces`, formData, {
headers: {
'Authorization': `Bearer ${apiKey}`,
...formData.getHeaders()
}
});
console.info(chalk.green(`Piece '${packageJson.name}' published.`));
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response.status === 409) {
console.info(chalk.yellow(`Piece '${packageJson.name}' and '${packageJson.version}' already published.`));
} else if (Math.floor(error.response.status / 100) !== 2) {
console.info(chalk.red(`Error publishing piece '${packageJson.name}', ${error}` ));
if (failOnError) {
console.info(chalk.yellow(`Terminating process due to publish failure for piece '${packageJson.name}' (fail-on-error is enabled)`));
process.exit(1);
}
} else {
console.error(chalk.red(`Unexpected error: ${error.message}`));
if (failOnError) {
console.info(chalk.yellow(`Terminating process due to unexpected error for piece '${packageJson.name}' (fail-on-error is enabled)`));
process.exit(1);
}
}
} else {
console.error(chalk.red(`Unexpected error: ${error.message}`));
if (failOnError) {
console.info(chalk.yellow(`Terminating process due to unexpected error for piece '${packageJson.name}' (fail-on-error is enabled)`));
process.exit(1);
}
}
}
}
async function traverseFolder(folderPath: string): Promise<string[]> {
const paths: string[] = []
const directoryExists = await stat(folderPath).catch(() => null)
if (directoryExists && directoryExists.isDirectory()) {
const files = await readdir(folderPath)
for (const file of files) {
const filePath = path.join(folderPath, file)
const fileStats = await stat(filePath)
if (fileStats.isDirectory() && file !== 'node_modules' && file !== 'dist') {
paths.push(...await traverseFolder(filePath))
}
else if (file === 'package.json') {
paths.push(folderPath)
}
}
}
return paths
}
export function displayNameToKebabCase(displayName: string): string {
return displayName.toLowerCase().replace(/\s+/g, '-');
}
export function displayNameToCamelCase(input: string): string {
const words = input.split(' ');
const camelCaseWords = words.map((word, index) => {
if (index === 0) {
return word.toLowerCase();
} else {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}
});
return camelCaseWords.join('');
}
export const assertPieceExists = async (pieceName: string | null) => {
if (!pieceName) {
console.error(chalk.red(`🚨 Piece ${pieceName} not found`));
process.exit(1);
}
};
export const removeStartingSlashes = (str: string) => {
return str.startsWith('/') ? str.slice(1) : str;
}