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.
 
 
 
 

238 lines
7.5 KiB

#!/usr/bin/env node
import {Command} from 'commander';
import path from 'path';
import fetch from "node-fetch";
import fs from "fs";
import http from "http";
import https from "https";
import StreamZip from "node-stream-zip";
import {fileURLToPath} from 'url';
import memFs from 'mem-fs';
import editor from 'mem-fs-editor';
export const defaultGitRepo = 'git+https://git.devdevdev.life/axe-web-public/create-block.git#master';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const isDev = process.env.NODE_ENV === 'development'; // Check README file in case you get "missing files" error.
const blocksRegistry = isDev ? 'http://localhost:3020' : 'https://axe-web-blocks-registry.captain.devdevdev.life';
const blocksDirectory = isDev ? 'blocks/' : '';
if (isDev) {
console.log(`Development Mode Active`);
}
try {
const blockName = await init();
console.log(`🎉 Done! \n\nCheck the "${blocksDirectory}${blockName}" directory. \n`);
} catch (e) {
console.log(`❌ Fail.`);
}
async function init() {
const program = new Command();
program
.name('create-block')
.description('AXE-WEB Platform Blocks');
const promise = new Promise((resolve, reject) => {
program.command('pull')
.argument('<string>', 'Provide a full name of required block, for example: @axe-web/hero-block')
.action(async (blockName, options) => {
console.log('📦 Block to download:', blockName)
const status = await getBlockSourceFiles(blockName);
if (status) {
resolve(blockName);
} else {
reject();
}
});
});
program.parse();
return promise;
}
async function getBlockSourceFiles(blockName) {
const queryString = new URLSearchParams();
queryString.append('blockName', blockName);
queryString.append('includeDevConfig', 'true');
const response = await fetch(`${blocksRegistry}?${queryString.toString()}`);
const responseData = await response.json();
if (!responseData || !responseData.name) {
console.log("⚠️ Block not found, please contact administrator.");
return;
}
if (responseData.status && responseData.status !== 200) {
console.log("⚠️ [ERROR]", responseData.message || "Server side error.");
throw new Error("⚠️ [ERROR]", responseData.message || "Server side error.");
}
const data = {
title: responseData.title,
name: responseData.name,
preview_image: responseData.preview_image,
remToPx: responseData.config.remToPx ?? 16,
blockFilename: responseData.name,
blockClassName: responseData.name,
blockGroupName: responseData.project,
version: responseData.version,
devToolSource: defaultGitRepo,
};
if (responseData.downloadUrl) {
console.log(`⬇️ Downloading v${responseData.version}`);
const zipFile = await downloadFile(responseData.downloadUrl, responseData.name + '.zip');
// Download, Extract and Remove downloaded file.
try {
const zip = new StreamZip.async({file: zipFile});
await zip.extract(null, `${blocksDirectory}${responseData.name}/src`);
await zip.close();
await fs.promises.access(zipFile, fs.constants.W_OK);
await fs.promises.unlink(zipFile);
} catch (e) {
throw e;
}
} else {
await createSourceFiles(data, __dirname, `${blocksDirectory}${responseData.name}`);
}
await createTechnicalFiles(data, __dirname, `${blocksDirectory}${responseData.name}`);
if (responseData.config.design_files) {
await downloadDesignFiles(responseData.name, responseData.config.design_files);
}
await createDataFiles(responseData.name, responseData.config.data_sources);
await createConfigFile(responseData.name, responseData.config);
return true;
}
async function createConfigFile(blockName, config = {}) {
const obj = {
blockName,
baseView: config.baseView,
remToPx: config.remToPx,
};
const cssAssets = config.assets.filter(item => item.type === 'css').map(item => item.url);
if (cssAssets.length) {
obj.cssUrl = cssAssets;
}
const jsAssets = config.assets.filter(item => item.type === 'js').map(item => item.url);
if (jsAssets.length) {
obj.jsUrl = jsAssets;
}
await setupPath(`${blocksDirectory}${blockName}/config`);
await fs.promises.writeFile(`${blocksDirectory}${blockName}/config/default.json`, JSON.stringify(obj, null, 2));
}
async function downloadDesignFiles(blockName = '', files) {
for (let url of files) {
const fileName = url.split('/').pop();
await setupPath(`${blocksDirectory}${blockName}/design`);
await downloadFile(url, `${blocksDirectory}${blockName}/design/${fileName}`)
}
}
async function createDataFiles(blockName, dataSources = []) {
for (let source of dataSources) {
await setupPath(`${blocksDirectory}${blockName}/data`);
await fs.promises.writeFile(`${blocksDirectory}${blockName}/data/${source.name}.json`, JSON.stringify(source.data, null, 2));
if (typeof source.preview_images !== 'undefined' && source.preview_images.length) {
for (let preview_image of source.preview_images) {
await setupPath(`${blocksDirectory}${blockName}/design/preview`);
await downloadFile(preview_image.url, `${blocksDirectory}${blockName}/design/preview/${source.name}.${preview_image.width}.${preview_image.extension}`)
}
}
}
}
//
// Helpers
//
async function setupPath(path) {
if (!fs.existsSync(path)) {
await fs.promises.mkdir(path);
}
}
async function downloadFile(url, fileName) {
const file = fs.createWriteStream(fileName);
return new Promise((resolve, reject) => {
const protocol = url.startsWith('https://') ? https : http;
protocol.get(url, function (response) {
response.pipe(file);
// Loading Indicator.
const loadingInterval = setInterval(() => console.log('🕐 Download in progress...'), 3000);
// after download completed close filestream
file.on("finish", () => {
clearInterval(loadingInterval);
file.close();
resolve(fileName);
});
});
})
}
export async function createSourceFiles(data, baseDir, distPath) {
const pathDist = distPath + '/src'; //path.join(baseDir, distPath);
const generatorsPath = path.join(baseDir, 'generators/block/templates/src');
const store = memFs.create();
const filesystem = editor.create(store);
const files = [
{from: 'template.template.hbs', to: `${data.name}.template.hbs`},
{from: 'styles/template.scss', to: `styles/${data.name}.scss`},
{from: 'scripts/template.js', to: `scripts/${data.name}.js`},
'images/demo.jpeg',
];
for (let file of files) {
const from = typeof file !== 'string' ? `${generatorsPath}/${file.from}` : `${generatorsPath}/${file}`;
const to = typeof file !== 'string' ? `${pathDist}/${file.to}` : `${pathDist}/${file}`;
await filesystem.copyTplAsync(from, to, data);
}
return filesystem.commit(); // Promise
}
export async function createTechnicalFiles(data, baseDir, distPath) {
const pathDist = distPath; //path.join(baseDir, distPath);
const generatorsPath = path.join(baseDir, 'generators/block/templates');
const store = memFs.create();
const filesystem = editor.create(store);
const files = ['package.json', 'README.md', '.editorconfig', {from: 'gitignore', to: '.gitignore'}, 'block.json'];
for (let file of files) {
const from = typeof file !== 'string' ? `${generatorsPath}/${file.from}` : `${generatorsPath}/${file}`;
const to = typeof file !== 'string' ? `${pathDist}/${file.to}` : `${pathDist}/${file}`;
await filesystem.copyTplAsync(from, to, data);
}
return filesystem.commit(); // Promise
}