wuespace/telestion-client

View on GitHub
packages/telestion-client-cli/src/actions/psc.mts

Summary

Maintainability
Test Coverage
import { join, resolve } from 'node:path';
import {
    exists,
    getLogger,
    readDir,
    readFile,
    stat,
    writeFile
} from '../lib/index.mjs';

const logger = getLogger('PSC Action');

/**
 * Checks, if the given project directory is a PSC root.
 * @param projectDir - path to a possible PSC root
 */
export async function isPSCRoot(projectDir: string): Promise<boolean> {
    const [packageExists, widgetsExists] = await Promise.all([
        exists(join(projectDir, 'package.json')),
        exists(join(projectDir, 'src', 'widgets'))
    ]);

    return packageExists && widgetsExists;
}

/**
 * Searches the PSC root up from the working directory.
 * If no PSC is found, it throws.
 * @param workingDir - the current working directory to check
 */
export async function getPSCRoot(workingDir: string): Promise<string> {
    // remove dangling relative paths
    const searchDir = resolve(workingDir);
    logger.debug('Search directory:', searchDir);
    // return if this is a PSC root
    if (await isPSCRoot(searchDir)) {
        logger.debug('Resolved PSC root:', searchDir);
        return searchDir;
    }

    // search higher up in the tree
    const nextSearchDir = resolve(searchDir, '..');
    logger.debug('Next search directory:', nextSearchDir);
    // we've reached the top
    if (searchDir === nextSearchDir) {
        throw new Error('Not in a Telestion Frontend Project');
    }

    // recursively search next
    return getPSCRoot(nextSearchDir);
}

/**
 * Returns `true` if the widget is installed in the PSC.
 * @param projectDir - path to the root directory of the PSC
 * @param widgetName - the widget name to test
 */
export async function isWidget(
    projectDir: string,
    widgetName: string
): Promise<boolean> {
    const widgetPath = join(projectDir, 'src', 'widgets', widgetName);
    logger.debug('Check widget path:', widgetPath);

    if (!(await stat(widgetPath)).isDirectory()) {
        // widget is not a directory
        return false;
    }

    const widgetIndexPath = join(widgetPath, 'index.ts');
    logger.debug('Widget index.ts path:', widgetIndexPath);
    if (!(await exists(widgetIndexPath))) {
        // index.ts doesn't exist
        return false;
    }
    if (!(await stat(widgetIndexPath)).isFile()) {
        // index.ts isn't a file
        return false;
    }

    const widgetIndex = await readFile(widgetIndexPath);
    logger.debug(`Widget '${widgetName}' index.ts:`, widgetIndex);

    // TODO: Better check
    return widgetIndex.includes('export const widget:');
}

/**
 * Returns a list of installed widgets in the PSC.
 * @param projectDir - the root directory of the PSC
 */
export async function getWidgets(projectDir: string): Promise<string[]> {
    if (!(await isPSCRoot(projectDir))) {
        throw new Error(`The directory '${projectDir}' is not a PSC root`);
    }

    const widgetsDir = join(projectDir, 'src', 'widgets');
    logger.debug('Widget directory:', widgetsDir);
    if (!(await exists(widgetsDir))) {
        throw new Error(
            `/src/widgets does not exist in the project '${projectDir}'`
        );
    }
    if (!(await stat(widgetsDir)).isDirectory()) {
        throw new Error(
            `/src/widgets is not a directory in the project '${projectDir}'`
        );
    }

    const possibleWidgets = await readDir(widgetsDir);
    logger.debug('Found possible widgets:', possibleWidgets);

    const isWidgetArr = await Promise.all(
        possibleWidgets.map(widget => isWidget(projectDir, widget))
    );
    logger.debug('List of is widget state of all possible widgets:', isWidgetArr);

    const widgets = possibleWidgets.filter((widget, index) => isWidgetArr[index]);
    logger.debug('Real widgets:', widgets);

    return widgets;
}

/**
 * Reads, parses and returns the `package.json` for the specified project.
 * @param projectDir - path to the project root
 */
export async function getPackageJson(
    projectDir: string
): Promise<Record<string, unknown>> {
    logger.debug('Parse package.json for project:', projectDir);

    const packageJson = JSON.parse(
        await readFile(join(projectDir, 'package.json'))
    ) as Record<string, unknown>;
    logger.debug('PSC package.json:', packageJson);
    return packageJson;
}

/**
 * Stringifies and overwrites the `package.json` of the specified project.
 * @param projectDir - path to the project root
 * @param packageJson - the new content of the `package.json`
 */
export async function setPackageJson(
    projectDir: string,
    packageJson: Record<string, unknown>
): Promise<void> {
    logger.debug('Write package.json for project:', projectDir);

    await writeFile(
        join(projectDir, 'package.json'),
        JSON.stringify(packageJson, null, '\t')
    );
}