You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
8.4 KiB
306 lines
8.4 KiB
#!/usr/bin/env node
|
|
|
|
import fetch from "node-fetch";
|
|
import express from 'express';
|
|
import {create} from 'express-handlebars';
|
|
import fsExtra from 'fs-extra';
|
|
import browserSync from 'browser-sync';
|
|
import config from 'config';
|
|
import gulp from 'gulp';
|
|
import babel from "gulp-babel";
|
|
import uglify from "gulp-uglify";
|
|
import rename from "gulp-rename";
|
|
import dartSass from 'sass';
|
|
import gulpSass from 'gulp-sass';
|
|
import sourcemaps from "gulp-sourcemaps";
|
|
import fs from "fs/promises";
|
|
import open from "open";
|
|
import {sanitizeUrl} from "@braintree/sanitize-url";
|
|
import sanitizeHtml from 'sanitize-html';
|
|
import {escape} from "lodash-es";
|
|
import archiver from 'archiver';
|
|
|
|
/**
|
|
* Constants
|
|
*/
|
|
|
|
const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error.
|
|
const blocksRegistry = isDev ? 'http://localhost:3020' : 'http://localhost:3020';
|
|
const modulePath = isDev ? '' : 'node_modules/create-block-dev-tool/';
|
|
const projectDir = modulePath;
|
|
|
|
const sass = gulpSass(dartSass);
|
|
|
|
buildStyleFiles()
|
|
buildScriptFiles()
|
|
|
|
/**
|
|
* Init server
|
|
*/
|
|
|
|
let port = 3000;
|
|
const app = express();
|
|
|
|
const hbs = create({
|
|
extname: '.hbs', defaultLayout: false, partialsDir: ['.'], helpers: {
|
|
esc_attr(attr) {
|
|
return escape(attr);
|
|
}, esc_url(url) {
|
|
return sanitizeUrl(url);
|
|
}, esc_html(html) {
|
|
// TODO: Check if we can remove this helper.
|
|
return html;
|
|
}, safe_html(html) {
|
|
return sanitizeHtml(html);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.engine('.hbs', hbs.engine);
|
|
app.set('view engine', '.hbs');
|
|
app.set('views', projectDir + 'layouts');
|
|
|
|
const dataFiles = prepareListOfDataFiles(await fs.readdir('./data'));
|
|
|
|
app.get('/', async (req, res) => {
|
|
let jsonFileName = req.query.data ? req.query.data : 'default';
|
|
const data = await getBlockConfigs(jsonFileName);
|
|
if (data.error && data.errorMessage) {
|
|
return res.send(data.errorMessage);
|
|
}
|
|
|
|
data.helpers = {
|
|
port: port,
|
|
include_partial: (path) => projectDir + path,
|
|
baseView: config.has('baseView') ? config.get('baseView') : 'container',
|
|
}
|
|
|
|
res.render('index', data);
|
|
});
|
|
|
|
app.get('/view/:baseView', async (req, res) => {
|
|
let jsonFileName = req.query.data ? req.query.data : 'default';
|
|
const data = await getBlockConfigs(jsonFileName);
|
|
if (data.error && data.errorMessage) {
|
|
return res.send(data.errorMessage);
|
|
}
|
|
|
|
data.helpers = {
|
|
include_partial: (path) => projectDir + path,
|
|
include_block_template: (path) => 'src/' + (config.has('blockName') ? config.get('blockName') : 'development') + '.template',
|
|
}
|
|
|
|
const baseView = req.params.baseView ?? 'container';
|
|
|
|
res.render(baseView, data)
|
|
});
|
|
|
|
app.get('/publish', async (req, res) => {
|
|
const data = await readJSONFile('./block.json');
|
|
|
|
const response = await fetch(`${blocksRegistry}`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
headers: {'Content-Type': 'application/json'}
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
|
|
if (responseData.statusCode !== 200) {
|
|
console.log(responseData);
|
|
res.json({success: false, message: 'Error on registry level.'});
|
|
return;
|
|
}
|
|
|
|
if (responseData.uploadUrl) {
|
|
await zipProject();
|
|
const body = await fs.readFile('./dist.zip');
|
|
const response = await fetch(`${responseData.uploadUrl}`, {
|
|
method: 'PUT',
|
|
body,
|
|
headers: {'Content-Type': 'application/zip'}
|
|
});
|
|
|
|
if (response.status !== 200) {
|
|
res.json({success: false, message: "Can't upload the archive, permissions error."});
|
|
await fs.unlink('./dist.zip');
|
|
return;
|
|
}
|
|
}
|
|
|
|
res.json({success: true});
|
|
|
|
await fs.unlink('./dist.zip');
|
|
});
|
|
|
|
app.use(express.static('src'));
|
|
app.use(express.static(projectDir + 'layouts'));
|
|
|
|
const listener = app.listen(0, async () => {
|
|
const PORT = listener.address().port;
|
|
await open(`http://localhost:${PORT}`);
|
|
|
|
console.log(`The web server has started on port ${PORT}`);
|
|
|
|
const bs = browserSync.create();
|
|
|
|
gulp.watch(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'], {delay: 400}, gulp.series([buildScriptFiles, function (cb) {
|
|
browserSyncReload(bs, 'js', 'Script Files Change');
|
|
return cb();
|
|
}]));
|
|
|
|
gulp.watch('src/**/*.scss', {delay: 400}, gulp.series([buildStyleFiles, function (cb) {
|
|
browserSyncReload(bs, 'css', 'Style Files Change');
|
|
return cb();
|
|
}]));
|
|
|
|
// bs.watch("src/**/*.js", function (event, file) {});
|
|
// bs.watch("src/**/*.css", function (event, file) {});
|
|
|
|
bs.watch("src/**/*.hbs", function (event, file) {
|
|
browserSyncReload(bs, '', 'Template File Change: ' + file)
|
|
});
|
|
|
|
bs.init({
|
|
proxy: `http://localhost:${PORT}`, open: false
|
|
}, (err, bs) => {
|
|
port = bs.server._connectionKey.split(':').pop();
|
|
});
|
|
|
|
});
|
|
|
|
/**
|
|
* Functions
|
|
*/
|
|
|
|
function browserSyncReload(bs, extension = '', message = '') {
|
|
if (isDev) {
|
|
// console.log(event, file);
|
|
console.log(message);
|
|
}
|
|
|
|
if (extension) {
|
|
extension = "*." + extension;
|
|
}
|
|
|
|
bs.reload(extension);
|
|
}
|
|
|
|
function buildScriptFiles() {
|
|
return gulp.src(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'])
|
|
.pipe(sourcemaps.init({}))
|
|
.pipe(babel())
|
|
.pipe(gulp.src('vendor/*.js'))
|
|
// .pipe(gulp.dest('src/'))
|
|
.pipe(uglify())
|
|
.pipe(rename({extname: '.min.js'}))
|
|
.pipe(sourcemaps.write('.'))
|
|
.pipe(gulp.dest('src/'));
|
|
}
|
|
|
|
function buildStyleFiles() {
|
|
return gulp.src('src/**/*.scss')
|
|
.pipe(sourcemaps.init({}))
|
|
.pipe(sass.sync().on('error', sass.logError))
|
|
// .pipe(gulp.dest('src/'))
|
|
.pipe(rename({extname: '.min.css'}))
|
|
.pipe(sourcemaps.write('.'))
|
|
.pipe(gulp.dest('src/'))
|
|
}
|
|
|
|
function prepareListOfDataFiles(dataFiles) {
|
|
return dataFiles
|
|
.filter((fileName) => fileName.split('.').pop() === 'json')
|
|
.map((fileName) => {
|
|
const splitName = fileName.split('.');
|
|
splitName.pop();
|
|
return splitName.join('');
|
|
})
|
|
.sort();
|
|
}
|
|
|
|
async function readJSONFile(jsonFile) {
|
|
let data = {};
|
|
|
|
try {
|
|
data = await fsExtra.readJson(jsonFile);
|
|
} catch (e) {
|
|
return {
|
|
error: true, errorMessage: getErrorHtml("JSON Syntax error. Please make sure the dataFile is valid.", e),
|
|
};
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
async function getBlockConfigs(jsonFileName = 'default') {
|
|
let data = await readJSONFile(`./data/${jsonFileName}.json`);
|
|
if (data.error) {
|
|
return data;
|
|
}
|
|
|
|
Object.assign(data, {
|
|
config: Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object.
|
|
{
|
|
projectDir, activeDataFile: jsonFileName, dataFiles: dataFiles.map((name) => {
|
|
return {
|
|
name, active: jsonFileName === name,
|
|
};
|
|
}), remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
|
|
})
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
function getErrorHtml(message = '', errorMessage = '') {
|
|
return `<div style="padding: 10px 15px; font-family: Arial, sans-serif">
|
|
<p>${message}</p>
|
|
<pre style="padding: 10px 15px; background-color: #ffd0d0; border: 1px solid red;">${errorMessage}</pre>
|
|
</div>`;
|
|
}
|
|
|
|
async function zipProject() {
|
|
// create a file to stream archive data to.
|
|
const output = await fsExtra.createWriteStream('dist.zip');
|
|
const archive = archiver('zip', {});
|
|
|
|
// listen for all archive data to be written
|
|
// 'close' event is fired only when a file descriptor is involved
|
|
output.on('close', function () {
|
|
console.log(archive.pointer() + ' total bytes');
|
|
console.log('archiver has been finalized and the output file descriptor has closed.');
|
|
});
|
|
|
|
// This event is fired when the data source is drained no matter what was the data source.
|
|
// It is not part of this library but rather from the NodeJS Stream API.
|
|
// @see: https://nodejs.org/api/stream.html#stream_event_end
|
|
output.on('end', function () {
|
|
console.log('Data has been drained');
|
|
});
|
|
|
|
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
|
archive.on('warning', function (err) {
|
|
if (err.code === 'ENOENT') {
|
|
// log warning
|
|
} else {
|
|
// throw error
|
|
throw err;
|
|
}
|
|
});
|
|
|
|
// good practice to catch this error explicitly
|
|
archive.on('error', function (err) {
|
|
throw err;
|
|
});
|
|
|
|
// pipe archive data to the file
|
|
archive.pipe(output);
|
|
|
|
// append files from a sub-directory, putting its contents at the root of archive
|
|
archive.directory('src/', false);
|
|
|
|
// finalize the archive (ie we are done appending files but streams have to finish yet)
|
|
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
|
|
await archive.finalize();
|
|
}
|
|
|