nvisionative / nvQuickTheme

nvQuickTheme is more than just a great minimalist DNN (DotNetNuke) theme. It is a powerful theme building framework and developer workflow.
https://nvisionative.github.io/nvQuickTheme/
Other
41 stars 40 forks source link

Implement a custom plugin for packaging automation using `rollup` #373

Open david-poindexter opened 5 months ago

david-poindexter commented 5 months ago

Is your feature request related to a problem?

Since gulp will no longer be used, we'll need to automate packaging using rollup.

Describe the solution you'd like

We'll need to develop a custom rollup plugin for this feature. We will use the following from the DNN 10 theme Aperture as inspiration.

import { Plugin } from 'rollup';
import * as fs from 'fs';
import * as path from 'path';
import { glob } from 'glob';
import { Zip } from 'zip-lib';

interface RollupPluginDnnPackageOptions
{
    name: string;
    version: string;
    destinationDirectory: string;
};

type RollupPluginDnnPackage = (dnnPackageOptions: RollupPluginDnnPackageOptions) => Plugin;

function ensureEmptyDirectory(dirPath: string): void {
    // Ensure that the directory exists
    if (!fs.existsSync(dirPath)) {
        // Directory does not exist, create it
        fs.mkdirSync(dirPath, { recursive: true });
    }

    // Directory exists, empty it
    const files = fs.readdirSync(dirPath);

    for (const file of files) {
        const currentPath = path.join(dirPath, file);
        if (fs.lstatSync(currentPath).isDirectory()) {
            // Recursive call for directories
            ensureEmptyDirectory(currentPath);
            // After emptying the subdirectory, remove it
            fs.rmdirSync(currentPath);
        } else {
            // Delete file
            fs.unlinkSync(currentPath);
        }
    }
}

function copyFileToPath(src: string, dest: string): void {
    // Ensure that the destination directory exists
    const dir = path.dirname(dest);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }

    // Now that we know the directory exists, copy the file
    fs.copyFileSync(src, dest);
}

const dnnPackage: RollupPluginDnnPackage = (dnnPackageOptions) =>
{
    return {
        name: 'rollup-plugin-dnn-package',
        async writeBundle(options, _bundle)
        {
            const skinDist = options.dir as string;
            const containersDist = skinDist.replace('/Skins/', '/Containers/');
            const artifactsDir = "./artifacts";
            ensureEmptyDirectory(artifactsDir);
            const stagingDir = `${artifactsDir}/staging`;
            ensureEmptyDirectory(stagingDir);

            // Skin resources
            var skinResources = await glob(
                [
                    `${skinDist}/css/**/*`,
                    `${skinDist}/fonts/**/*`,
                    `${skinDist}/js/**/*`,
                    `${skinDist}/menus/**/*`,
                    `${skinDist}/patials/**/*`,
                    `${skinDist}/**/*.ascx`,
                    `${skinDist}/**/*.xml`,
                    `${skinDist}/**/*.png`,
                ],
                { nodir: true }
            );
            skinResources.forEach((skinResource) => {
                const relativePath = path.relative(skinDist, skinResource).replace(/\\/g, '/');
                const targetPath = path.resolve(`${stagingDir}/skinResources/${relativePath}`);
                copyFileToPath(skinResource, targetPath);
            });
            let zip = new Zip();
            zip.addFolder(`${stagingDir}/skinResources`);
            await zip.archive(`${stagingDir}/skin.zip`)
            fs.rmSync(`${stagingDir}/skinResources`, { recursive: true, force: true });

            // Container resources
            var containerResources = await glob(
                [
                    `${containersDist}/**/*`,
                ],
                { nodir: true }
            );
            containerResources.forEach((containerResource) => {
                const relativePath = path.relative(containersDist, containerResource).replace(/\\/g, '/');
                const targetPath = path.resolve(`${stagingDir}/containerResources/${relativePath}`);
                copyFileToPath(containerResource, targetPath);
            });
            zip = new Zip();
            zip.addFolder(`${stagingDir}/containerResources`);
            await zip.archive(`${stagingDir}/container.zip`);
            fs.rmSync(`${stagingDir}/containerResources`, { recursive: true, force: true });

            // Root files
            var rootResources = await glob(
                [
                    `${skinDist}/*.png`,
                    `${skinDist}/LICENSE`,
                    `${skinDist}/*.txt`,
                    `${skinDist}/*.dnn`,
                ],
                { nodir: true }
            );
            rootResources.forEach((rootResource) => {
                const relativePath = path.relative(skinDist, rootResource).replace(/\\/g, '/');
                const targetPath = path.resolve(`${stagingDir}/${relativePath}`);
                copyFileToPath(rootResource, targetPath);
            });

            // Package ZIP
            zip = new Zip();
            zip.addFolder(stagingDir);
            var packageName = `${dnnPackageOptions.name}_${dnnPackageOptions.version}_install.zip`;
            var packagePath = `${artifactsDir}/${packageName}`;
            await zip.archive(packagePath);
            fs.rmSync(stagingDir, { recursive: true, force: true });

            var skinInstallPath = `${path.resolve(dnnPackageOptions.destinationDirectory)}`;
            console.log(`Copying ${packageName} to ${skinInstallPath}`);
            fs.copyFileSync(
                packagePath,
                `${skinInstallPath}/${packageName}`);
            fs.rmSync(artifactsDir, { recursive: true, force: true });
        },
    };
}

export default dnnPackage;

Describe alternatives you've considered

n/a

Additional context

n/a