TryGhost/Ghost

View on GitHub
ghost/admin/lib/asset-delivery/index.js

Summary

Maintainability
B
4 hrs
Test Coverage
/* eslint-disable */
'use strict';

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const camelCase = require('lodash/camelCase');

const adminXApps = ['admin-x-demo', 'admin-x-settings', 'admin-x-activitypub'];

function generateHash(filePath) {
    const fileContents = fs.readFileSync(filePath, 'utf8');
    const hash = crypto.createHash('sha256').update(fileContents).digest('hex').slice(0, 10);
    return hash;
}

module.exports = {
    name: 'asset-delivery',

    env: null,

    packageConfig: {},

    config(env) {
        // only set this.env on the first call otherwise when `postBuild()` is
        // called this.env will always be 'test' due to multiple `config()` calls
        if (!this.env) {
            this.env = env;

            const koenigLexicalPath = require.resolve('@tryghost/koenig-lexical');
            this.packageConfig['editorFilename'] = path.basename(koenigLexicalPath);
            this.packageConfig['editorHash'] = process.env.EDITOR_URL ? 'development' : generateHash(koenigLexicalPath);

            // TODO: ideally take this from the package, but that's broken thanks to .cjs file ext
            for (const app of adminXApps) {
                const defaultFilename = `${app}.js`;
                const configName = camelCase(app);
                this.packageConfig[`${configName}Filename`] = defaultFilename;
                this.packageConfig[`${configName}Hash`] = (this.env === 'production') ? generateHash(path.join(`../../apps/${app}/dist`, defaultFilename)) : 'development';
            }

            if (this.env === 'production') {
                for (const [key, value] of Object.entries(this.packageConfig)) {
                    console.log(`Asset-Delivery: ${key} = ${value}`);
                }

                this.packageConfig[`adminXActivitypubCustomUrl`] = 'https://cdn.jsdelivr.net/npm/@tryghost/admin-x-activitypub@0/dist/admin-x-activitypub.js'
            }

            return this.packageConfig;
        }
    },

    isDevelopingAddon() {
        return true;
    },

    postBuild: function (results) {
        const fs = this.project.require('fs-extra');
        const walkSync = this.project.require('walk-sync');

        const assetsOut = path.join(path.dirname(require.resolve('ghost')), `core/built/admin`);
        fs.removeSync(assetsOut);
        fs.ensureDirSync(assetsOut);

        // the dist folder contains more than just index.html and /assets, especially
        // for development builds but for Ghost's purposes it only needs to serve
        // index.html and /assets

        // copy the index.html file
        fs.copySync(`${results.directory}/index.html`, `${assetsOut}/index.html`, {overwrite: true, dereference: true});

        // get all the `/assets` files, except the `icons` folder
        const assets = walkSync(results.directory + '/assets', {
            ignore: ['icons']
        });

        // loop over any sourcemaps and remove `assets/` key from each one
        assets.filter((file) => file.endsWith('.map')).forEach((file) => {
            const mapFilePath = `${results.directory}/assets/${file}`;
            const mapFile = JSON.parse(fs.readFileSync(mapFilePath, 'utf8'));
            // loop over the sources and remove `assets/` from each one
            mapFile.sources = mapFile.sources.map((source) => source.replace('assets/', ''));
            fs.writeFileSync(mapFilePath, JSON.stringify(mapFile));
        });

        // copy the assets to assetsOut
        assets.forEach(function (relativePath) {
            if (relativePath.slice(-1) === '/') { return; }

            fs.copySync(`${results.directory}/assets/${relativePath}`, `${assetsOut}/assets/${relativePath}`, {overwrite: true, dereference: true});
        });

        // copy assets for each admin-x app
        for (const app of adminXApps) {
            const adminXPath = `../../apps/${app}/dist`;
            const assetsAdminXPath = `${assetsOut}/assets/${app}`;
            if (fs.existsSync(adminXPath)) {
                if (this.env === 'production') {
                    fs.copySync(adminXPath, assetsAdminXPath, {overwrite: true, dereference: true});
                } else {
                    fs.ensureSymlinkSync(adminXPath, assetsAdminXPath);
                }
            } else  {
                console.log(`${app} folder not found`);
            }
        }

        // if we are passed a URL for Koenig-Lexical dev server, we don't need to copy the assets
        if (!process.env.EDITOR_URL) {
            // copy the @tryghost/koenig-lexical assets
            const koenigLexicalPath = path.dirname(require.resolve('@tryghost/koenig-lexical'));
            const assetsKoenigLexicalPath = `${assetsOut}/assets/koenig-lexical`;

            if (fs.existsSync(koenigLexicalPath)) {
                fs.copySync(koenigLexicalPath, assetsKoenigLexicalPath, {overwrite: true, dereference: true});
            } else {
                console.log('Koenig-Lexical folder not found');
            }
        }
    }
};