From e2553358be26c12271c6c86c0df52143e6384d6f Mon Sep 17 00:00:00 2001 From: Roman Axelrod Date: Sat, 12 Aug 2023 21:01:45 +0300 Subject: [PATCH] Added support of Project Global Assets. --- helpers.js | 77 ++++++++++++++++++++++++- layouts/partials/head.hbs | 2 + layouts/partials/scripts.hbs | 5 +- package.json | 6 +- server.js | 107 ++++++++++++++++++++++++++++------- 5 files changed, 173 insertions(+), 24 deletions(-) diff --git a/helpers.js b/helpers.js index ac3fedf..5e60101 100644 --- a/helpers.js +++ b/helpers.js @@ -9,6 +9,8 @@ import archiver from "archiver"; import {buildWordPress} from "./platforms/wordpress/wordpress-adapter.js"; import {buildHubspotEmail} from "./platforms/hubspot/hubspot-email-adapter.js"; import {buildHubspotPage} from "./platforms/hubspot/hubspot-page-adapter.js"; +import fs from "fs/promises"; +import fetch from "node-fetch"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -53,7 +55,7 @@ export async function getBlockData(jsonFileName = 'default', {projectPath} = {js } export function getBlockConfigs(args = {modulesPath: '', dataFiles: []}) { - return Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object. + const updatedConfig = Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object. { projectDir: args.modulesPath, dataFiles: args.dataFiles.map((name) => { return { @@ -61,6 +63,14 @@ export function getBlockConfigs(args = {modulesPath: '', dataFiles: []}) { }; }), remToPx: config.has('remToPx') ? config.get('remToPx') : 16, }); + + // Avoid cache conflict on Global Project css/js files. + if (updatedConfig.project) { + updatedConfig.project.css = updatedConfig.project.css ? updatedConfig.project.css + '?cache=' + Date.now() : undefined; + updatedConfig.project.js = updatedConfig.project.js ? updatedConfig.project.js + '?cache=' + Date.now() : undefined; + } + + return updatedConfig; } export function getBlockName(name = '') { @@ -160,3 +170,68 @@ export async function buildExportFiles(blockName, platform) { await buildHubspotPage(blockName) } } + +export function removeCommentsFromCss(content) { + return content.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, ''); +} + +export function removeCommentsFromJs(content) { + return content.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, ''); +} + +export async function uploadFile(filePath, uploadUrl, validator) { + let body = await fs.readFile(filePath, {encoding: 'utf8'}); + + if (validator) { + body = validator(body); + } + + try { + const response = await fetch(uploadUrl, { + method: 'PUT', + body: body, + headers: {'Content-Type': 'application/javascript'} + }); + + return response.status !== 200; + } catch (err) { + const fileName = filePath.split('/').pop(); + throw new Error(`Can't upload "${fileName}" file. Server permission error.`); + } +} + +export async function getImagesList(rootFolder, subFolder = '') { + const imagesPath = path.join(rootFolder, subFolder); + const images = await fs.readdir(imagesPath); + const imagesList = []; + + for (const image of images) { + const stats = await fs.stat(path.join(imagesPath, image)); + + if (stats.isDirectory()) { + const subImages = await getImagesList(rootFolder, image); + imagesList.push(...subImages); + } else { + imagesList.push(path.join(subFolder, image)); + } + } + + return imagesList; +} + +export async function isFileEmpty(filePath, ignoreComments = false) { + const fileContent = await fs.readFile(filePath, 'utf8'); + const lines = fileContent.split('\n'); + + for (const line of lines) { + if (ignoreComments && line.trim().startsWith('//')) { + continue; + } + + if (line.trim().length > 0) { + return false; + } + } + + return true; +} diff --git a/layouts/partials/head.hbs b/layouts/partials/head.hbs index d2ebb29..7a1c7bf 100644 --- a/layouts/partials/head.hbs +++ b/layouts/partials/head.hbs @@ -3,6 +3,8 @@ +{{#if config.project.css }} +{{/if}} {{#if config.cssUrl }} {{#each config.cssUrl }} diff --git a/layouts/partials/scripts.hbs b/layouts/partials/scripts.hbs index e0117a7..eece0f5 100644 --- a/layouts/partials/scripts.hbs +++ b/layouts/partials/scripts.hbs @@ -2,7 +2,10 @@ -{{#if config.jsUrl }} + +{{#if config.project.js }} +{{/if}} +{{#if config.jsUrl }} {{#each config.jsUrl }} {{/each}} {{/if}} diff --git a/package.json b/package.json index 9bb365d..61f4120 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "url": "https://axe-web.com/" }, "scripts": { - "info": "NODE_ENV=development BLOCK_NAME=comparison-table MODULE_PATH= node debug.js", - "dev": "NODE_ENV=development BLOCK_NAME=comparison-table MODULE_PATH= node server.js", - "build-platform": "NODE_ENV=development BLOCK_NAME=comparison-table MODULE_PATH= node ./build.js", + "info": "NODE_ENV=development BLOCK_NAME=content MODULE_PATH= node debug.js", + "dev": "NODE_ENV=development BLOCK_NAME=content MODULE_PATH= node server.js", + "build-platform": "NODE_ENV=development BLOCK_NAME=content MODULE_PATH= node ./build.js", "dev-dev-tool": "NODE_ENV=development rollup --config rollup.config.js --watch", "build-dev-tool": "rollup --config rollup.config.js" }, diff --git a/server.js b/server.js index fa47238..cbf4a7e 100755 --- a/server.js +++ b/server.js @@ -19,7 +19,19 @@ import open from "open"; import {sanitizeUrl} from "@braintree/sanitize-url"; import sanitizeHtml from 'sanitize-html'; import {escape} from "lodash-es"; -import {getBlockData, getBlockConfigs, getConfigs, readJSONFile, zipProject} from "./helpers.js"; +import { + getBlockConfigs, + getBlockData, + getBlockName, + getConfigs, + getImagesList, + isFileEmpty, + readJSONFile, + removeCommentsFromCss, + removeCommentsFromJs, + uploadFile, + zipProject +} from "./helpers.js"; import PluginError from 'plugin-error'; import {Server} from "socket.io"; import {createServer} from 'http'; @@ -93,7 +105,6 @@ app.get('/', (req, res) => { data.baseView = baseView; data.port = `/${baseViewUrl}`; data.previewFrameUrl = `${previewFrameUrl}/${baseViewUrl}`; - // data.previewFrameUrl = `/${baseViewUrl}`; data.shareUrl = shareUrl; if (req.headers.referer) { @@ -127,8 +138,24 @@ app.get('/view/:baseView', (req, res) => { app.get('/publish', async (req, res) => { const data = await readJSONFile(path.join(projectPath, `block.json`)); + + data.static_files = { + css: getBlockName(data.name).name + '.min.css', + js: getBlockName(data.name).name + '.min.js', + images: await getImagesList(path.join(projectPath, 'src', 'images')), + } + + if (await isFileEmpty(path.join(projectPath, `src/scripts`, data.static_files.js), true)) { + delete data.static_files.js; + } + + if (!data.static_files.images.length) { + delete data.static_files.images; + } + let responseData = { - uploadUrl: undefined + uploadBundleUrl: undefined, + staticFilesUrls: {} }; try { @@ -149,27 +176,53 @@ app.get('/publish', async (req, res) => { return; } - if (responseData.uploadUrl) { - await zipProject(path.join(projectPath, 'src'), path.join(projectPath, 'dist.zip')); - const body = await fs.readFile(path.join(projectPath, 'dist.zip')); - const response = await fetch(`${responseData.uploadUrl}`, { - method: 'PUT', - body, - headers: {'Content-Type': 'application/zip'} - }); + // Start files uploading process. + try { + if (responseData.uploadBundleUrl) { + await zipProject(path.join(projectPath, 'src'), path.join(projectPath, 'dist.zip')); + await uploadFile(path.join(projectPath, 'dist.zip'), responseData.uploadBundleUrl); // Bundle + } + + // TODO: Upload CSS/JS/Images files only if the type of the unit is `foundation` or `component`. + if (responseData.staticFilesUrls.css) { + await uploadFile( + path.join(projectPath, 'src/styles', data.static_files.css), + responseData.staticFilesUrls.css.uploadUrl, + (data) => removeCommentsFromCss(data)); // CSS + } - if (response.status !== 200) { - res.json({success: false, message: "Can't upload the archive, permissions error."}); - // TODO: Need to update the registry server. - await fs.unlink(path.join(projectPath, 'dist.zip')); - return; + if (responseData.staticFilesUrls.js) { + await uploadFile( + path.join(projectPath, 'src/scripts', data.static_files.js), + responseData.staticFilesUrls.js.uploadUrl, + (data) => removeCommentsFromJs(data)); // JS + } + + if (responseData.staticFilesUrls.images) { + for (let i = 0; i < data.static_files.images.length; i++) { + await uploadFile(path.join(projectPath, 'src/images', data.static_files.images[i]), + responseData.staticFilesUrls.images[i].uploadUrl); // Images + } } + } catch (err) { + // TODO: Need to update the registry server. + await fs.unlink(path.join(projectPath, 'dist.zip')); // Remove local bundle + + res.json({success: false, message: err.message}); + return; } + await fs.unlink(path.join(projectPath, 'dist.zip')); // Remove local bundle - res.json({success: true}); + // TODO: Trigger build on the registry server only if the type of the unit is `foundation` or `component`. + try { + await triggerGlobalProjectFilesBuild(getBlockName(data.name).project); + } catch (err) { + res.json({success: false, message: 'Something wrong with Project Builder.'}); + return; + } - await fs.unlink(path.join(projectPath, 'dist.zip')); + res.json({success: true}); }); app.get('/data', async (req, res) => { @@ -177,7 +230,13 @@ app.get('/data', async (req, res) => { const dataFiles = prepareListOfDataFiles(await fs.readdir(path.join(projectPath, 'data'))); const data = await getBlockData(jsonDataFileName, {projectPath}); - const designPreviewFiles = getListOfDesignPreviewFiles(jsonDataFileName, await fs.readdir(path.join(projectPath, 'design', 'preview'))); + + let designPreviewFiles = []; + try { + designPreviewFiles = getListOfDesignPreviewFiles(jsonDataFileName, await fs.readdir(path.join(projectPath, 'design', 'preview'))); + } catch (err) { + console.log('Preview Design doesn\'t exist'); + } return res.json({ dataOptions: dataFiles, @@ -439,3 +498,13 @@ async function getShareableUrl() { return url; } + +async function triggerGlobalProjectFilesBuild(project) { + const response = await fetch(`${blocksRegistry}/project-files`, { + method: 'POST', + body: JSON.stringify({project}), + headers: {'Content-Type': 'application/json'} + }); + + return response.json(); +}