@underpostnet/underpost
Version:
805 lines (719 loc) • 31.6 kB
JavaScript
;
import fs from 'fs-extra';
import { srcFormatted, componentFormatted, viewFormatted, ssrFactory, JSONweb } from './client-formatted.js';
import { loggerFactory } from './logger.js';
import {
cap,
getCapVariableName,
newInstance,
orderArrayFromAttrInt,
titleFormatted,
uniqueArray,
} from '../client/components/core/CommonJs.js';
import UglifyJS from 'uglify-js';
import { minify } from 'html-minifier-terser';
import dotenv from 'dotenv';
import AdmZip from 'adm-zip';
import * as dir from 'path';
import { shellExec } from './process.js';
import swaggerAutoGen from 'swagger-autogen';
import { SitemapStream, streamToPromise } from 'sitemap';
import { Readable } from 'stream';
import { buildIcons, buildTextImg, getBufferPngText } from './client-icons.js';
import Underpost from '../index.js';
dotenv.config();
// Static Site Generation (SSG)
const buildAcmeChallengePath = (acmeChallengeFullPath = '') => {
fs.mkdirSync(acmeChallengeFullPath, {
recursive: true,
});
fs.writeFileSync(`${acmeChallengeFullPath}/.gitkeep`, '', 'utf8');
};
const fullBuild = async ({
path,
logger,
client,
db,
dists,
rootClientPath,
acmeChallengeFullPath,
publicClientId,
iconsBuild,
metadata,
}) => {
logger.warn('Full build', rootClientPath);
buildAcmeChallengePath(acmeChallengeFullPath);
if (publicClientId && publicClientId.startsWith('html-website-templates')) {
if (!fs.existsSync(`/home/dd/html-website-templates/`))
shellExec(`cd /home/dd && git clone https://github.com/designmodo/html-website-templates.git`);
if (!fs.existsSync(`${rootClientPath}/index.php`)) {
fs.copySync(`/home/dd/html-website-templates/${publicClientId.split('-publicClientId-')[1]}`, rootClientPath);
shellExec(`cd ${rootClientPath} && git init && git add . && git commit -m "Base template implementation"`);
// git remote add origin git@github.com:<username>/<repo>.git
fs.writeFileSync(`${rootClientPath}/.git/.htaccess`, `Deny from all`, 'utf8');
}
return;
}
fs.removeSync(rootClientPath);
if (fs.existsSync(`./src/client/public/${publicClientId}`)) {
if (iconsBuild) {
const defaultBaseIconFolderPath = `src/client/public/${publicClientId}/assets/logo`;
if (!fs.existsSync(defaultBaseIconFolderPath)) fs.mkdirSync(defaultBaseIconFolderPath, { recursive: true });
const defaultBaseIconPath = `${defaultBaseIconFolderPath}/base-icon.png`;
if (!fs.existsSync(defaultBaseIconPath))
await buildTextImg(metadata.title, { debugFilename: defaultBaseIconPath });
if (!fs.existsSync(`./src/client/public/${publicClientId}/site.webmanifest`))
await buildIcons({ publicClientId, metadata });
}
fs.copySync(
`./src/client/public/${publicClientId}`,
rootClientPath /* {
filter: function (name) {
console.log(name);
return true;
},
} */,
);
} else if (fs.existsSync(`./engine-private/src/client/public/${publicClientId}`)) {
switch (publicClientId) {
case 'mysql_test':
if (db) {
fs.copySync(`./engine-private/src/client/public/${publicClientId}`, rootClientPath);
fs.writeFileSync(
`${rootClientPath}/index.php`,
fs
.readFileSync(`${rootClientPath}/index.php`, 'utf8')
.replace('test_servername', 'localhost')
.replace('test_username', db.user)
.replace('test_password', db.password)
.replace('test_dbname', db.name),
'utf8',
);
} else logger.error('not provided db config');
break;
default:
break;
}
}
if (dists)
for (const dist of dists) {
if ('folder' in dist) {
if (fs.statSync(dist.folder).isDirectory()) {
fs.mkdirSync(`${rootClientPath}${dist.public_folder}`, { recursive: true });
fs.copySync(dist.folder, `${rootClientPath}${dist.public_folder}`);
} else {
const folder = dist.public_folder.split('/');
folder.pop();
fs.mkdirSync(`${rootClientPath}${folder.join('/')}`, { recursive: true });
fs.copyFileSync(dist.folder, `${rootClientPath}${dist.public_folder}`);
}
}
if ('styles' in dist) {
fs.mkdirSync(`${rootClientPath}${dist.public_styles_folder}`, { recursive: true });
fs.copySync(dist.styles, `${rootClientPath}${dist.public_styles_folder}`);
}
}
};
const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }) => {
const logger = loggerFactory(import.meta);
const confClient = JSON.parse(fs.readFileSync(`./conf/conf.client.json`, 'utf8'));
const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
const confSSR = JSON.parse(fs.readFileSync(`./conf/conf.ssr.json`, 'utf8'));
const packageData = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
const acmeChallengePath = `/.well-known/acme-challenge`;
const publicPath = `./public`;
// { srcBuildPath, publicBuildPath }
const enableLiveRebuild =
options && options.liveClientBuildPaths && options.liveClientBuildPaths.length > 0 ? true : false;
let currentPort = parseInt(process.env.PORT) + 1;
for (const host of Object.keys(confServer)) {
const paths = orderArrayFromAttrInt(Object.keys(confServer[host]), 'length', 'asc');
for (const path of paths) {
if (
options &&
options.instances &&
options.instances.length > 0 &&
!options.instances.find((i) => i.path === path && i.host === host)
)
continue;
const {
runtime,
client,
directory,
disabledRebuild,
minifyBuild,
db,
redirect,
apis,
iconsBuild,
docsBuild,
apiBaseProxyPath,
apiBaseHost,
ttiLoadTimeLimit,
singleReplica,
offlineBuild,
} = confServer[host][path];
if (singleReplica) continue;
if (!confClient[client]) confClient[client] = {};
const { components, dists, views, services, metadata, publicRef } = confClient[client];
let backgroundImage;
if (metadata) {
backgroundImage = metadata.backgroundImage;
if (metadata.thumbnail) metadata.thumbnail = `${path === '/' ? path : `${path}/`}${metadata.thumbnail}`;
}
const rootClientPath = directory ? directory : `${publicPath}/${host}${path}`;
const port = newInstance(currentPort);
const publicClientId = publicRef ? publicRef : client;
const fullBuildEnabled = !process.argv.includes('l') && !confServer[host][path].liteBuild && !enableLiveRebuild;
// const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : `http://localhost:${port}`;
const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
// ''; // process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
currentPort++;
const acmeChallengeFullPath = directory
? `${directory}${acmeChallengePath}`
: `${publicPath}/${host}${acmeChallengePath}`;
if (!enableLiveRebuild) buildAcmeChallengePath(acmeChallengeFullPath);
if (redirect || disabledRebuild) continue;
if (fullBuildEnabled)
await fullBuild({
path,
logger,
client,
db,
dists,
rootClientPath,
acmeChallengeFullPath,
publicClientId,
iconsBuild,
metadata,
});
if (components)
for (const module of Object.keys(components)) {
if (!fs.existsSync(`${rootClientPath}/components/${module}`))
fs.mkdirSync(`${rootClientPath}/components/${module}`, { recursive: true });
for (const component of components[module]) {
const jsSrcPath = `./src/client/components/${module}/${component}.js`;
const jsPublicPath = `${rootClientPath}/components/${module}/${component}.js`;
if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
const jsSrc = componentFormatted(
await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
module,
dists,
path,
'components',
baseHost,
);
fs.writeFileSync(
jsPublicPath,
minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
'utf8',
);
}
}
if (services) {
for (const module of services) {
if (!fs.existsSync(`${rootClientPath}/services/${module}`))
fs.mkdirSync(`${rootClientPath}/services/${module}`, { recursive: true });
if (fs.existsSync(`./src/client/services/${module}/${module}.service.js`)) {
const jsSrcPath = `./src/client/services/${module}/${module}.service.js`;
const jsPublicPath = `${rootClientPath}/services/${module}/${module}.service.js`;
if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
let jsSrc = componentFormatted(
await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
module,
dists,
path,
'services',
baseHost,
);
fs.writeFileSync(
jsPublicPath,
minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
'utf8',
);
}
}
for (const module of services) {
if (fs.existsSync(`./src/client/services/${module}/${module}.management.js`)) {
const jsSrcPath = `./src/client/services/${module}/${module}.management.js`;
const jsPublicPath = `${rootClientPath}/services/${module}/${module}.management.js`;
if (enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath)) continue;
const jsSrc = componentFormatted(
await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')),
module,
dists,
path,
'services',
baseHost,
);
fs.writeFileSync(
jsPublicPath,
minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
'utf8',
);
}
}
}
const buildId = `${client}.index`;
const siteMapLinks = [];
const ssrPath = path === '/' ? path : `${path}/`;
const Render = await ssrFactory();
if (views) {
const jsSrcPath = fs.existsSync(`./src/client/sw/${publicClientId}.sw.js`)
? `./src/client/sw/${publicClientId}.sw.js`
: `./src/client/sw/default.sw.js`;
const jsPublicPath = `${rootClientPath}/sw.js`;
if (!(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath))) {
const jsSrc = viewFormatted(await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')), dists, path, baseHost);
fs.writeFileSync(
jsPublicPath,
minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
'utf8',
);
}
if (
!(
enableLiveRebuild &&
!options.liveClientBuildPaths.find(
(p) => p.srcBuildPath.startsWith(`./src/client/ssr`) || p.srcBuildPath.slice(-9) === '.index.js',
)
)
)
for (const view of views) {
const buildPath = `${
rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
}${view.path === '/' ? view.path : `${view.path}/`}`;
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
logger.info('View build', buildPath);
const jsSrc = viewFormatted(
await srcFormatted(fs.readFileSync(`./src/client/${view.client}.index.js`, 'utf8')),
dists,
path,
baseHost,
);
fs.writeFileSync(
`${buildPath}${buildId}.js`,
minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
'utf8',
);
// const title = `${metadata && metadata.title ? metadata.title : cap(client)}${
// view.title ? ` | ${view.title}` : view.path !== '/' ? ` | ${titleFormatted(view.path)}` : ''
// }`;
const title = `${
view.title ? `${view.title} | ` : view.path !== '/' ? `${titleFormatted(view.path)} | ` : ''
}${metadata && metadata.title ? metadata.title : cap(client)}`;
const canonicalURL = `https://${host}${path}${
view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
}`;
let ssrHeadComponents = ``;
let ssrBodyComponents = ``;
if ('ssr' in view) {
// https://metatags.io/
if (process.env.NODE_ENV === 'production' && !confSSR[view.ssr].head.includes('Production'))
confSSR[view.ssr].head.unshift('Production');
for (const ssrHeadComponent of confSSR[view.ssr].head) {
const SrrComponent = await ssrFactory(`./src/client/ssr/head/${ssrHeadComponent}.js`);
switch (ssrHeadComponent) {
case 'Pwa':
const validPwaBuild =
metadata &&
fs.existsSync(`./src/client/public/${publicClientId}/browserconfig.xml`) &&
fs.existsSync(`./src/client/public/${publicClientId}/site.webmanifest`);
if (validPwaBuild) {
// build webmanifest
const webmanifestJson = JSON.parse(
fs.readFileSync(`./src/client/public/${publicClientId}/site.webmanifest`, 'utf8'),
);
if (metadata.title) {
webmanifestJson.name = metadata.title;
webmanifestJson.short_name = metadata.title;
}
if (metadata.description) {
webmanifestJson.description = metadata.description;
}
if (metadata.themeColor) {
webmanifestJson.theme_color = metadata.themeColor;
webmanifestJson.background_color = metadata.themeColor;
}
fs.writeFileSync(
`${buildPath}site.webmanifest`,
JSON.stringify(webmanifestJson, null, 4).replaceAll(`: "/`, `: "${ssrPath}`),
'utf8',
);
// build browserconfig
fs.writeFileSync(
`${buildPath}browserconfig.xml`,
fs
.readFileSync(`./src/client/public/${publicClientId}/browserconfig.xml`, 'utf8')
.replaceAll(
`<TileColor></TileColor>`,
metadata.themeColor
? `<TileColor>${metadata.themeColor}</TileColor>`
: `<TileColor>#e0e0e0</TileColor>`,
)
.replaceAll(`src="/`, `src="${ssrPath}`),
'utf8',
);
// Android play store example:
//
// "related_applications": [
// {
// "platform": "play",
// "url": "https://play.google.com/store/apps/details?id=cheeaun.hackerweb"
// }
// ],
// "prefer_related_applications": true
}
if (validPwaBuild) ssrHeadComponents += SrrComponent({ title, ssrPath, canonicalURL, ...metadata });
break;
case 'Seo':
if (metadata) {
ssrHeadComponents += SrrComponent({ title, ssrPath, canonicalURL, ...metadata });
}
break;
case 'Microdata':
if (
fs.existsSync(`./src/client/public/${publicClientId}/microdata.json`) // &&
// path === '/' &&
// view.path === '/'
) {
const microdata = JSON.parse(
fs.readFileSync(`./src/client/public/${publicClientId}/microdata.json`, 'utf8'),
);
ssrHeadComponents += SrrComponent({ microdata });
}
break;
default:
ssrHeadComponents += SrrComponent({ ssrPath, host, path });
break;
}
}
for (const ssrBodyComponent of confSSR[view.ssr].body) {
const SrrComponent = await ssrFactory(`./src/client/ssr/body/${ssrBodyComponent}.js`);
switch (ssrBodyComponent) {
case 'UnderpostDefaultSplashScreen':
case 'CyberiaDefaultSplashScreen':
case 'NexodevSplashScreen':
case 'DefaultSplashScreen':
if (backgroundImage) {
ssrHeadComponents += SrrComponent({
backgroundImage: (path === '/' ? path : `${path}/`) + backgroundImage,
});
// `data:image/${backgroundImage.split('.').pop()};base64,${fs
// .readFileSync()
// .toString('base64')}`,
break;
} else {
ssrHeadComponents += SrrComponent({ metadata });
break;
const bufferBackgroundImage = await getBufferPngText({
text: ' ',
textColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
size: '100x100',
bgColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
});
ssrHeadComponents += SrrComponent({
backgroundImage: `data:image/png;base64,${bufferBackgroundImage.toString('base64')}`,
});
}
case 'CyberiaSplashScreenLore': {
ssrBodyComponents += SrrComponent({
ssrPath,
host,
path,
ttiLoadTimeLimit,
});
break;
}
default:
ssrBodyComponents += SrrComponent({
ssrPath,
host,
path,
ttiLoadTimeLimit,
version: Underpost.version,
});
break;
}
}
}
/** @type {import('sitemap').SitemapItem} */
const siteMapLink = {
url: `${path === '/' ? '' : path}${view.path}`,
changefreq: 'daily',
priority: 0.8,
};
siteMapLinks.push(siteMapLink);
const htmlSrc = Render({
title,
buildId,
ssrPath,
ssrHeadComponents,
ssrBodyComponents,
renderPayload: {
apiBaseProxyPath,
apiBaseHost,
apiBasePath: process.env.BASE_API,
version: Underpost.version,
},
renderApi: {
JSONweb,
},
});
fs.writeFileSync(
`${buildPath}index.html`,
minifyBuild || process.env.NODE_ENV === 'production'
? await minify(htmlSrc, {
minifyCSS: true,
minifyJS: true,
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
})
: htmlSrc,
'utf8',
);
}
}
if (!enableLiveRebuild && siteMapLinks.length > 0) {
const xslUrl = fs.existsSync(`${rootClientPath}/sitemap`)
? `${path === '/' ? '' : path}/sitemap.xsl`
: undefined;
// Create a stream to write to
/** @type {import('sitemap').SitemapStreamOptions} */
const sitemapOptions = { hostname: `https://${host}`, xslUrl };
const siteMapStream = new SitemapStream(sitemapOptions);
let siteMapSrc = await new Promise((resolve) =>
streamToPromise(Readable.from(siteMapLinks).pipe(siteMapStream)).then((data) => resolve(data.toString())),
);
// Return a promise that resolves with your XML string
fs.writeFileSync(`${rootClientPath}/sitemap.xml`, siteMapSrc, 'utf8');
if (xslUrl)
fs.writeFileSync(
`${rootClientPath}/sitemap.xsl`,
fs.readFileSync(`${rootClientPath}/sitemap`, 'utf8').replaceAll('{{web-url}}', `https://${host}${path}`),
'utf8',
);
fs.writeFileSync(
`${rootClientPath}/robots.txt`,
`User-agent: *
Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
'utf8',
);
}
if (!enableLiveRebuild && !process.argv.includes('l') && !process.argv.includes('deploy') && docsBuild) {
// https://www.pullrequest.com/blog/leveraging-jsdoc-for-better-code-documentation-in-javascript/
// https://jsdoc.app/about-configuring-jsdoc
// https://jsdoc.app/ Block tags
const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
jsDocsConfig.opts.theme_opts.title = metadata && metadata.title ? metadata.title : undefined;
jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? path : `${path}/favicon.ico`}`;
fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
shellExec(`npm run docs`, { silent: true });
// coverage
if (!fs.existsSync(`./coverage`)) {
shellExec(`npm test`);
}
const coverageBuildPath = `${jsDocsConfig.opts.destination}/coverage`;
fs.mkdirSync(coverageBuildPath, { recursive: true });
fs.copySync(`./coverage`, coverageBuildPath);
// uml
// shellExec(`node bin/deploy uml ${host} ${path}`);
// https://swagger-autogen.github.io/docs/
const basePath = path === '/' ? `${process.env.BASE_API}` : `/${process.env.BASE_API}`;
const doc = {
info: {
version: packageData.version, // by default: '1.0.0'
title: metadata?.title ? `${metadata.title}` : 'REST API', // by default: 'REST API'
description: metadata?.description ? metadata.description : '', // by default: ''
},
servers: [
{
url:
process.env.NODE_ENV === 'development'
? `http://localhost:${port}${path}${basePath}`
: `https://${host}${path}${basePath}`, // by default: 'http://localhost:3000'
description: `${process.env.NODE_ENV} server`, // by default: ''
},
],
tags: [
// by default: empty Array
{
name: 'user', // Tag name
description: 'User API operations', // Tag description
},
],
components: {
schemas: {
userRequest: {
username: 'user123',
password: 'Password123',
email: 'user@example.com',
},
userResponse: {
status: 'success',
data: {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
user: {
_id: '66c377f57f99e5969b81de89',
email: 'user@example.com',
emailConfirmed: false,
username: 'user123',
role: 'user',
profileImageId: '66c377f57f99e5969b81de87',
},
},
},
userUpdateResponse: {
status: 'success',
data: {
_id: '66c377f57f99e5969b81de89',
email: 'user@example.com',
emailConfirmed: false,
username: 'user123222',
role: 'user',
profileImageId: '66c377f57f99e5969b81de87',
},
},
userGetResponse: {
status: 'success',
data: {
_id: '66c377f57f99e5969b81de89',
email: 'user@example.com',
emailConfirmed: false,
username: 'user123222',
role: 'user',
profileImageId: '66c377f57f99e5969b81de87',
},
},
userLogInRequest: {
email: 'user@example.com',
password: 'Password123',
},
userBadRequestResponse: {
status: 'error',
message: 'Bad request. Please check your inputs, and try again',
},
},
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
},
},
},
};
// plantuml
logger.info('copy plantuml', `${rootClientPath}/docs/plantuml`);
fs.copySync(`./src/client/public/default/plantuml`, `${rootClientPath}/docs/plantuml`);
logger.warn('build swagger api docs', doc.info);
const outputFile = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
const routes = [];
for (const api of apis) {
if (['user'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
}
/* NOTE: If you are using the express Router, you must pass in the 'routes' only the
root file where the route starts, such as index.js, app.js, routes.js, etc ... */
await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
const htmlFiles = await fs.readdir(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}`);
for (const htmlFile of htmlFiles) {
if (htmlFile.match('.html')) {
fs.writeFileSync(
`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`,
fs
.readFileSync(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`, 'utf8')
.replaceAll('Tutorials', 'References'),
'utf8',
);
}
}
}
if (client) {
let PRE_CACHED_RESOURCES = [];
if (views && offlineBuild && fs.existsSync(`${rootClientPath}/sw.js`)) {
PRE_CACHED_RESOURCES = await fs.readdir(rootClientPath, { recursive: true });
PRE_CACHED_RESOURCES = views
.map((view) => `${path === '/' ? '' : path}${view.path}`)
.concat(
PRE_CACHED_RESOURCES.map((p) => `/${p}`).filter(
(p) => p[1] !== '.' && !fs.statSync(`${rootClientPath}${p}`).isDirectory(),
),
);
}
for (const pageType of ['offline', 'pages']) {
if (confSSR[getCapVariableName(client)] && confSSR[getCapVariableName(client)][pageType]) {
for (const page of confSSR[getCapVariableName(client)][pageType]) {
const SsrComponent = await ssrFactory(`./src/client/ssr/${pageType}/${page.client}.js`);
const htmlSrc = Render({
title: page.title,
ssrPath,
ssrHeadComponents: '',
ssrBodyComponents: SsrComponent(),
renderPayload: {
apiBaseProxyPath,
apiBaseHost,
apiBasePath: process.env.BASE_API,
version: Underpost.version,
},
renderApi: {
JSONweb,
},
});
const buildPath = `${
rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
}${page.path === '/' ? page.path : `${page.path}/`}`;
PRE_CACHED_RESOURCES.push(`${path === '/' ? '' : path}${page.path === '/' ? '' : page.path}/index.html`);
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
const buildHtmlPath = `${buildPath}index.html`;
logger.info('ssr page build', buildHtmlPath);
fs.writeFileSync(
buildHtmlPath,
minifyBuild || process.env.NODE_ENV === 'production'
? await minify(htmlSrc, {
minifyCSS: true,
minifyJS: true,
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
})
: htmlSrc,
'utf8',
);
}
}
}
{
const renderPayload = {
PRE_CACHED_RESOURCES: uniqueArray(PRE_CACHED_RESOURCES),
PROXY_PATH: path,
};
fs.writeFileSync(
`${rootClientPath}/sw.js`,
`self.renderPayload = ${JSONweb(renderPayload)};
${fs.readFileSync(`${rootClientPath}/sw.js`, 'utf8')}`,
'utf8',
);
}
}
if (!enableLiveRebuild && process.argv.includes('zip')) {
logger.warn('build zip', rootClientPath);
if (!fs.existsSync('./build')) fs.mkdirSync('./build');
const zip = new AdmZip();
const files = await fs.readdir(rootClientPath, { recursive: true });
for (const relativePath of files) {
const filePath = dir.resolve(`${rootClientPath}/${relativePath}`);
if (!fs.lstatSync(filePath).isDirectory()) {
const folder = dir.relative(`public/${host}${path}`, dir.dirname(filePath));
zip.addLocalFile(filePath, folder);
}
}
const buildId = `${host}-${path.replaceAll('/', '')}`;
logger.warn('write zip', `./build/${buildId}.zip`);
zip.writeZip(`./build/${buildId}.zip`);
}
}
}
};
export { buildClient };