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();
+}