underpost 3.1.2 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -42,7 +42,7 @@ class Underpost {
42
42
  * @type {String}
43
43
  * @memberof Underpost
44
44
  */
45
- static version = 'v3.1.2';
45
+ static version = 'v3.1.3';
46
46
 
47
47
  /**
48
48
  * Required Node.js major version
@@ -165,14 +165,7 @@ class ExpressService {
165
165
  });
166
166
  }
167
167
 
168
- // Swagger UI setup
169
- if (fs.existsSync(swaggerJsonPath)) {
170
- const swaggerDoc = JSON.parse(fs.readFileSync(swaggerJsonPath, 'utf8'));
171
- const swaggerUiOptions = await buildSwaggerUiOptions();
172
- app.use(swaggerPath, swaggerUi.serve, swaggerUi.setup(swaggerDoc, swaggerUiOptions));
173
- }
174
-
175
- // Security and CORS
168
+ // Security and CORS — must run before swagger-ui so CSP headers are included in swagger responses
176
169
  if (process.env.NODE_ENV === 'development' && useLocalSsl)
177
170
  origins = origins.map((origin) => origin.replace('http', 'https'));
178
171
 
@@ -180,6 +173,13 @@ class ExpressService {
180
173
  origin: origins,
181
174
  });
182
175
 
176
+ // Swagger UI setup (after security middleware so responses carry correct CSP headers)
177
+ if (fs.existsSync(swaggerJsonPath)) {
178
+ const swaggerDoc = JSON.parse(fs.readFileSync(swaggerJsonPath, 'utf8'));
179
+ const swaggerUiOptions = await buildSwaggerUiOptions();
180
+ app.use(swaggerPath, swaggerUi.serve, swaggerUi.setup(swaggerDoc, swaggerUiOptions));
181
+ }
182
+
183
183
  // Database and Valkey connections
184
184
  if (db && apis) await DataBaseProvider.load({ apis, host, path, db });
185
185
 
@@ -613,13 +613,13 @@ function applySecurity(app, opts = {}) {
613
613
  frameAncestors: frameAncestors,
614
614
  imgSrc: ["'self'", 'data:', httpDirective, 'https:', 'blob:'],
615
615
  objectSrc: ["'none'"],
616
- // script-src and script-src-elem include dynamic nonce
617
- scriptSrc: [
616
+ // script-src and script-src-elem: use 'unsafe-inline' for swagger (no nonce, otherwise
617
+ // the nonce causes 'unsafe-inline' to be ignored per CSP3 spec), nonce for everything else.
618
+ scriptSrc: ["'self'", (req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : `'nonce-${res.locals.nonce}'`)],
619
+ scriptSrcElem: [
618
620
  "'self'",
619
- (req, res) => `'nonce-${res.locals.nonce}'`,
620
- (req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : ''),
621
+ (req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : `'nonce-${res.locals.nonce}'`),
621
622
  ],
622
- scriptSrcElem: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
623
623
  // style-src: avoid 'unsafe-inline' when possible; if you must inline styles,
624
624
  // use a nonce for them too (or hash).
625
625
  styleSrc: [
@@ -627,6 +627,7 @@ function applySecurity(app, opts = {}) {
627
627
  httpDirective,
628
628
  (req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : `'nonce-${res.locals.nonce}'`),
629
629
  ],
630
+ styleSrcAttr: [(req, res) => (res.locals.isSwagger ? "'unsafe-inline'" : "'none'")],
630
631
  // deny plugins
631
632
  objectSrc: ["'none'"],
632
633
  },
@@ -332,56 +332,17 @@ const buildApiDocs = async ({
332
332
  * @param {string} options.path - The base path for the documentation
333
333
  * @param {Object} options.metadata - Metadata for the documentation
334
334
  * @param {string} options.publicClientId - Client ID used to resolve the tutorials/references directory
335
+ * @param {Object} options.docs - Documentation config from server conf
336
+ * @param {string} options.docs.jsJsonPath - Path to the JSDoc JSON config file
335
337
  */
336
- const buildJsDocs = async ({ host, path, metadata = {}, publicClientId }) => {
338
+ const buildJsDocs = async ({ host, path, metadata = {}, publicClientId, docs }) => {
337
339
  const logger = loggerFactory(import.meta);
338
340
 
339
- // Detect custom jsdoc.<deployId>.json by matching host against deploy server configs
340
- let customJsDocPath = '';
341
- const privateConfBase = `./engine-private/conf`;
342
- if (fs.existsSync(privateConfBase)) {
343
- for (const deployId of fs.readdirSync(privateConfBase)) {
344
- const candidatePath = `./jsdoc.${deployId}.json`;
345
- if (!fs.existsSync(candidatePath)) continue;
346
-
347
- // Check if this deployId's server config contains the current host
348
- const serverConfPath = `${privateConfBase}/${deployId}/conf.server.json`;
349
- if (fs.existsSync(serverConfPath)) {
350
- try {
351
- const serverConf = JSON.parse(fs.readFileSync(serverConfPath, 'utf8'));
352
- if (serverConf[host]) {
353
- customJsDocPath = candidatePath;
354
- logger.info('detected custom jsdoc config', { deployId, host, path: candidatePath });
355
- break;
356
- }
357
- } catch (e) {
358
- // skip invalid JSON
359
- }
360
- }
361
-
362
- // Fallback: also check dev server configs
363
- if (!customJsDocPath) {
364
- const devConfFiles = fs
365
- .readdirSync(`${privateConfBase}/${deployId}`)
366
- .filter((f) => f.match(/^conf\.server\.dev\..*\.json$/));
367
- for (const devFile of devConfFiles) {
368
- try {
369
- const devConf = JSON.parse(fs.readFileSync(`${privateConfBase}/${deployId}/${devFile}`, 'utf8'));
370
- if (devConf[host]) {
371
- customJsDocPath = candidatePath;
372
- logger.info('detected custom jsdoc config (dev)', { deployId, host, path: candidatePath });
373
- break;
374
- }
375
- } catch (e) {
376
- // skip invalid JSON
377
- }
378
- }
379
- }
380
- if (customJsDocPath) break;
381
- }
341
+ const jsDocSourcePath = docs.jsJsonPath;
342
+ if (!fs.existsSync(jsDocSourcePath)) {
343
+ logger.warn('jsdoc config not found, skipping', jsDocSourcePath);
344
+ return;
382
345
  }
383
-
384
- const jsDocSourcePath = customJsDocPath || `./jsdoc.json`;
385
346
  const jsDocsConfig = JSON.parse(fs.readFileSync(jsDocSourcePath, 'utf8'));
386
347
  logger.info('using jsdoc config', jsDocSourcePath);
387
348
 
@@ -391,22 +352,14 @@ const buildJsDocs = async ({ host, path, metadata = {}, publicClientId }) => {
391
352
 
392
353
  const tutorialsPath = `./src/client/public/${publicClientId}/docs/references`;
393
354
 
394
- // Auto-prepare hardhat references when jsdoc config includes hardhat source files
395
- const includesHardhat =
396
- jsDocsConfig.source &&
397
- Array.isArray(jsDocsConfig.source.include) &&
398
- jsDocsConfig.source.include.some((p) => p.includes('hardhat/'));
399
- if (includesHardhat && fs.existsSync(`./hardhat`)) {
355
+ if (Array.isArray(docs.references) && docs.references.length > 0) {
400
356
  fs.mkdirSync(tutorialsPath, { recursive: true });
401
- const hardhatReadmePath = `./hardhat/README.md`;
402
- const hardhatWhitePaperPath = `./hardhat/WHITE-PAPER.md`;
403
- if (fs.existsSync(hardhatReadmePath)) {
404
- fs.copySync(hardhatReadmePath, `${tutorialsPath}/Hardhat Module.md`);
405
- logger.info('copied hardhat README.md to tutorials references');
406
- }
407
- if (fs.existsSync(hardhatWhitePaperPath)) {
408
- fs.copySync(hardhatWhitePaperPath, `${tutorialsPath}/White Paper.md`);
409
- logger.info('copied hardhat WHITE-PAPER.md to tutorials references');
357
+ for (const refPath of docs.references) {
358
+ if (fs.existsSync(refPath)) {
359
+ const fileName = refPath.split('/').pop();
360
+ fs.copySync(refPath, `${tutorialsPath}/${fileName}`);
361
+ logger.info('copied reference to tutorials', refPath);
362
+ }
410
363
  }
411
364
  }
412
365
 
@@ -420,10 +373,10 @@ const buildJsDocs = async ({ host, path, metadata = {}, publicClientId }) => {
420
373
  delete jsDocsConfig.opts.tutorials;
421
374
  }
422
375
 
423
- fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
376
+ fs.writeFileSync(jsDocSourcePath, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
424
377
  logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
425
378
 
426
- shellExec(`npm run docs`, { silent: true });
379
+ shellExec(`npx jsdoc -c ${jsDocSourcePath}`, { silent: true });
427
380
  };
428
381
 
429
382
  /**
@@ -433,58 +386,37 @@ const buildJsDocs = async ({ host, path, metadata = {}, publicClientId }) => {
433
386
  * @param {Object} options - Coverage build options
434
387
  * @param {string} options.host - The hostname for the coverage
435
388
  * @param {string} options.path - The base path for the coverage
389
+ * @param {Object} options.docs - Documentation config from server conf
390
+ * @param {string} options.docs.coveragePath - Directory where to run npm run coverage
436
391
  */
437
- const buildCoverage = async ({ host, path }) => {
392
+ const buildCoverage = async ({ host, path, docs }) => {
438
393
  const logger = loggerFactory(import.meta);
439
- const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
440
-
441
- if (!fs.existsSync(`./coverage`)) {
442
- shellExec(`npm test`);
443
- }
444
-
445
- const coverageBuildPath = `${jsDocsConfig.opts.destination}coverage`;
446
- fs.mkdirSync(coverageBuildPath, { recursive: true });
447
- fs.copySync(`./coverage`, coverageBuildPath);
448
-
449
- logger.warn('build coverage', coverageBuildPath);
394
+ const jsDocSourcePath = docs.jsJsonPath;
395
+ const jsDocsConfig = JSON.parse(fs.readFileSync(jsDocSourcePath, 'utf8'));
396
+ const coveragePath = docs.coveragePath;
450
397
 
451
- // Include hardhat coverage for cyberia-related builds
452
- const hardhatCoveragePath = `./hardhat/coverage`;
453
- if (fs.existsSync(hardhatCoveragePath) && fs.readdirSync(hardhatCoveragePath).length > 0) {
454
- const hardhatCoverageBuildPath = `${jsDocsConfig.opts.destination}hardhat-coverage`;
455
- fs.mkdirSync(hardhatCoverageBuildPath, { recursive: true });
456
- fs.copySync(hardhatCoveragePath, hardhatCoverageBuildPath);
457
- logger.warn('build hardhat coverage', hardhatCoverageBuildPath);
458
- } else if (fs.existsSync(`./hardhat/package.json`)) {
459
- // Attempt to generate hardhat coverage if the hardhat project exists
460
- try {
461
- const hardhatPkg = JSON.parse(fs.readFileSync(`./hardhat/package.json`, 'utf8'));
462
- if (hardhatPkg.scripts && hardhatPkg.scripts.coverage) {
463
- logger.info('generating hardhat coverage report');
464
- shellExec(`cd ./hardhat && npx hardhat coverage`, { silent: true });
465
- if (fs.existsSync(hardhatCoveragePath) && fs.readdirSync(hardhatCoveragePath).length > 0) {
466
- const hardhatCoverageBuildPath = `${jsDocsConfig.opts.destination}hardhat-coverage`;
467
- fs.mkdirSync(hardhatCoverageBuildPath, { recursive: true });
468
- fs.copySync(hardhatCoveragePath, hardhatCoverageBuildPath);
469
- logger.warn('build hardhat coverage (generated)', hardhatCoverageBuildPath);
470
- }
398
+ const coverageOutputPath = `${coveragePath}/coverage`;
399
+ if (!fs.existsSync(coverageOutputPath)) {
400
+ const pkgPath = `${coveragePath}/package.json`;
401
+ if (fs.existsSync(pkgPath)) {
402
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
403
+ if (pkg.scripts && pkg.scripts.coverage) {
404
+ logger.info('generating coverage report', coveragePath);
405
+ shellExec(`cd ${coveragePath} && npm run coverage`, { silent: true });
406
+ } else if (pkg.scripts && pkg.scripts.test) {
407
+ logger.info('generating coverage via test', coveragePath);
408
+ shellExec(`cd ${coveragePath} && npm test`, { silent: true });
471
409
  }
472
- } catch (e) {
473
- logger.warn('hardhat coverage generation skipped', e.message);
474
410
  }
475
411
  }
476
412
 
477
- // Copy hardhat README and WHITE-PAPER as markdown docs
478
- const docsDestBase = jsDocsConfig.opts.destination;
479
- const hardhatReadmePath = `./hardhat/README.md`;
480
- const hardhatWhitePaperPath = `./hardhat/WHITE-PAPER.md`;
481
- if (fs.existsSync(hardhatReadmePath)) {
482
- fs.copySync(hardhatReadmePath, `${docsDestBase}hardhat-README.md`);
483
- logger.info('copied hardhat README.md to docs');
484
- }
485
- if (fs.existsSync(hardhatWhitePaperPath)) {
486
- fs.copySync(hardhatWhitePaperPath, `${docsDestBase}hardhat-WHITE-PAPER.md`);
487
- logger.info('copied hardhat WHITE-PAPER.md to docs');
413
+ if (fs.existsSync(coverageOutputPath) && fs.readdirSync(coverageOutputPath).length > 0) {
414
+ const coverageBuildPath = `${jsDocsConfig.opts.destination}coverage`;
415
+ fs.mkdirSync(coverageBuildPath, { recursive: true });
416
+ fs.copySync(coverageOutputPath, coverageBuildPath);
417
+ logger.warn('build coverage', coverageBuildPath);
418
+ } else {
419
+ logger.warn('no coverage output found, skipping', coverageOutputPath);
488
420
  }
489
421
  };
490
422
 
@@ -501,6 +433,7 @@ const buildCoverage = async ({ host, path }) => {
501
433
  * @param {string} options.publicClientId - Client ID for the public documentation
502
434
  * @param {string} options.rootClientPath - Root path for client files
503
435
  * @param {Object} options.packageData - Package.json data
436
+ * @param {Object} options.docs - Documentation config from server conf
504
437
  */
505
438
  const buildDocs = async ({
506
439
  host,
@@ -511,9 +444,10 @@ const buildDocs = async ({
511
444
  publicClientId,
512
445
  rootClientPath,
513
446
  packageData,
447
+ docs,
514
448
  }) => {
515
- await buildJsDocs({ host, path, metadata, publicClientId });
516
- await buildCoverage({ host, path });
449
+ await buildJsDocs({ host, path, metadata, publicClientId, docs });
450
+ await buildCoverage({ host, path, docs });
517
451
  await buildApiDocs({
518
452
  host,
519
453
  path,
@@ -230,16 +230,28 @@ const defaultSitemapXsl = `<?xml version="1.0" encoding="UTF-8"?>
230
230
  * @function buildClient
231
231
  * @memberof clientBuild
232
232
  * @param {Object} options - Options for the build process.
233
+ * @param {string} options.deployId - The deployment ID for which to build the client.
233
234
  * @param {Array} options.liveClientBuildPaths - List of paths to build incrementally.
234
235
  * @param {Array} options.instances - List of instances to build.
235
236
  * @param {boolean} options.buildZip - Whether to create zip files of the builds.
237
+ * @param {boolean} options.fullBuild - Whether to perform a full build.
238
+ * @param {boolean} options.iconsBuild - Whether to build icons.
236
239
  * @returns {Promise<void>} - Promise that resolves when the build is complete.
237
240
  * @throws {Error} - If the build fails.
238
241
  * @memberof clientBuild
239
242
  */
240
- const buildClient = async (options = { liveClientBuildPaths: [], instances: [], buildZip: false }) => {
243
+ const buildClient = async (
244
+ options = {
245
+ deployId: '',
246
+ liveClientBuildPaths: [],
247
+ instances: [],
248
+ buildZip: false,
249
+ fullBuild: false,
250
+ iconsBuild: false,
251
+ },
252
+ ) => {
241
253
  const logger = loggerFactory(import.meta);
242
- const deployId = process.env.DEPLOY_ID;
254
+ const deployId = options.deployId || process.env.DEPLOY_ID;
243
255
  const confClient = readConfJson(deployId, 'client');
244
256
  const confServer = readConfJson(deployId, 'server', { loadReplicas: true });
245
257
  const confSSR = readConfJson(deployId, 'ssr');
@@ -373,17 +385,14 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
373
385
  client,
374
386
  directory,
375
387
  disabledRebuild,
376
- minifyBuild,
377
388
  db,
378
389
  redirect,
379
390
  apis,
380
- iconsBuild,
381
- docsBuild,
382
391
  apiBaseProxyPath,
383
392
  apiBaseHost,
384
393
  ttiLoadTimeLimit,
385
394
  singleReplica,
386
- offlineBuild,
395
+ docs,
387
396
  } = confServer[host][path];
388
397
  if (singleReplica) continue;
389
398
  if (!confClient[client]) confClient[client] = {};
@@ -397,9 +406,10 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
397
406
  const rootClientPath = directory ? directory : `${publicPath}/${host}${path}`;
398
407
  const port = newInstance(currentPort);
399
408
  const publicClientId = publicRef ? publicRef : client;
400
- const fullBuildEnabled = !confServer[host][path].liteBuild && !enableLiveRebuild;
409
+ const fullBuildEnabled = options.fullBuild && !enableLiveRebuild;
401
410
  // const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : `http://localhost:${port}`;
402
411
  const baseHost = process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
412
+ const minifyBuild = process.env.NODE_ENV === 'production';
403
413
  // ''; // process.env.NODE_ENV === 'production' ? `https://${host}` : ``;
404
414
  currentPort++;
405
415
 
@@ -421,7 +431,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
421
431
  rootClientPath,
422
432
  acmeChallengeFullPath,
423
433
  publicClientId,
424
- iconsBuild,
434
+ iconsBuild: options.iconsBuild,
425
435
  metadata,
426
436
  publicCopyNonExistingFiles,
427
437
  });
@@ -445,11 +455,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
445
455
  'components',
446
456
  baseHost,
447
457
  );
448
- fs.writeFileSync(
449
- jsPublicPath,
450
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
451
- 'utf8',
452
- );
458
+ fs.writeFileSync(jsPublicPath, minifyBuild ? UglifyJS.minify(jsSrc).code : jsSrc, 'utf8');
453
459
  }
454
460
  }
455
461
 
@@ -471,11 +477,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
471
477
  'services',
472
478
  baseHost,
473
479
  );
474
- fs.writeFileSync(
475
- jsPublicPath,
476
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
477
- 'utf8',
478
- );
480
+ fs.writeFileSync(jsPublicPath, minifyBuild ? UglifyJS.minify(jsSrc).code : jsSrc, 'utf8');
479
481
  }
480
482
  }
481
483
 
@@ -493,11 +495,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
493
495
  'services',
494
496
  baseHost,
495
497
  );
496
- fs.writeFileSync(
497
- jsPublicPath,
498
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
499
- 'utf8',
500
- );
498
+ fs.writeFileSync(jsPublicPath, minifyBuild ? UglifyJS.minify(jsSrc).code : jsSrc, 'utf8');
501
499
  }
502
500
  }
503
501
  }
@@ -517,11 +515,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
517
515
  if (!(enableLiveRebuild && !options.liveClientBuildPaths.find((p) => p.srcBuildPath === jsSrcPath))) {
518
516
  const jsSrc = viewFormatted(await srcFormatted(fs.readFileSync(jsSrcPath, 'utf8')), dists, path, baseHost);
519
517
 
520
- fs.writeFileSync(
521
- jsPublicPath,
522
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
523
- 'utf8',
524
- );
518
+ fs.writeFileSync(jsPublicPath, minifyBuild ? UglifyJS.minify(jsSrc).code : jsSrc, 'utf8');
525
519
  }
526
520
 
527
521
  if (
@@ -548,11 +542,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
548
542
  baseHost,
549
543
  );
550
544
 
551
- fs.writeFileSync(
552
- `${buildPath}${buildId}.js`,
553
- minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
554
- 'utf8',
555
- );
545
+ fs.writeFileSync(`${buildPath}${buildId}.js`, minifyBuild ? UglifyJS.minify(jsSrc).code : jsSrc, 'utf8');
556
546
  const title = metadata.title ? metadata.title : title;
557
547
 
558
548
  const canonicalURL = `https://${host}${path}${
@@ -689,7 +679,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [],
689
679
 
690
680
  fs.writeFileSync(
691
681
  `${buildPath}index.html`,
692
- minifyBuild || process.env.NODE_ENV === 'production'
682
+ minifyBuild
693
683
  ? await minify(htmlSrc, {
694
684
  minifyCSS: true,
695
685
  minifyJS: true,
@@ -739,7 +729,7 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
739
729
  );
740
730
  }
741
731
 
742
- if (fullBuildEnabled && !enableLiveRebuild && docsBuild) {
732
+ if (fullBuildEnabled && docs) {
743
733
  await buildDocs({
744
734
  host,
745
735
  path,
@@ -749,13 +739,14 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
749
739
  publicClientId,
750
740
  rootClientPath,
751
741
  packageData,
742
+ docs,
752
743
  });
753
744
  }
754
745
 
755
746
  if (client) {
756
747
  let PRE_CACHED_RESOURCES = [];
757
748
 
758
- if (views && offlineBuild && fs.existsSync(`${rootClientPath}/sw.js`)) {
749
+ if (views && fs.existsSync(`${rootClientPath}/sw.js`)) {
759
750
  PRE_CACHED_RESOURCES = await fs.readdir(rootClientPath, { recursive: true });
760
751
  PRE_CACHED_RESOURCES = views
761
752
  .map((view) => `${path === '/' ? '' : path}${view.path}`)
@@ -774,7 +765,7 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
774
765
  const htmlSrc = Render({
775
766
  title: page.title,
776
767
  ssrPath,
777
- ssrHeadComponents: '',
768
+ ssrHeadComponents: '<base target="_top">',
778
769
  ssrBodyComponents: SsrComponent(),
779
770
  renderPayload: {
780
771
  apiBaseProxyPath,
@@ -802,7 +793,7 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
802
793
 
803
794
  fs.writeFileSync(
804
795
  buildHtmlPath,
805
- minifyBuild || process.env.NODE_ENV === 'production'
796
+ minifyBuild
806
797
  ? await minify(htmlSrc, {
807
798
  minifyCSS: true,
808
799
  minifyJS: true,
@@ -1275,18 +1275,6 @@ const awaitDeployMonitor = async (init = false, deltaMs = 1000) => {
1275
1275
  if (Underpost.env.get('await-deploy')) return await awaitDeployMonitor();
1276
1276
  };
1277
1277
 
1278
- /**
1279
- * @method getCronBackUpFolder
1280
- * @description Gets the cron back up folder.
1281
- * @param {string} host - The host.
1282
- * @param {string} path - The path.
1283
- * @returns {string} - The cron back up folder.
1284
- * @memberof ServerConfBuilder
1285
- */
1286
- const getCronBackUpFolder = (host = '', path = '') => {
1287
- return `${host}${path.replace(/\\/g, '/').replace(`/`, '-')}`;
1288
- };
1289
-
1290
1278
  /**
1291
1279
  * @method mergeFile
1292
1280
  * @description Merges the file.
@@ -1499,8 +1487,25 @@ const buildCliDoc = (program, oldVersion, newVersion) => {
1499
1487
  md = md.replaceAll(oldVersion, newVersion);
1500
1488
  fs.writeFileSync(`./src/client/public/nexodev/docs/references/Command Line Interface.md`, md, 'utf8');
1501
1489
  fs.writeFileSync(`./CLI-HELP.md`, md, 'utf8');
1502
- const readme = fs.readFileSync(`./README.md`, 'utf8');
1503
- fs.writeFileSync('./README.md', readme.replaceAll(oldVersion, newVersion), 'utf8');
1490
+
1491
+ // Update README.md: replace version and CLI index section between comment tags
1492
+ let readme = fs.readFileSync(`./README.md`, 'utf8');
1493
+ readme = readme.replaceAll(oldVersion, newVersion);
1494
+ const cliStartTag = '<!-- cli-index-start -->';
1495
+ const cliEndTag = '<!-- cli-index-end -->';
1496
+ const startIdx = readme.indexOf(cliStartTag);
1497
+ const endIdx = readme.indexOf(cliEndTag);
1498
+ if (startIdx !== -1 && endIdx !== -1) {
1499
+ readme =
1500
+ readme.substring(0, startIdx) +
1501
+ cliStartTag +
1502
+ '\n' +
1503
+ baseOptions +
1504
+ '\n' +
1505
+ cliEndTag +
1506
+ readme.substring(endIdx + cliEndTag.length);
1507
+ }
1508
+ fs.writeFileSync('./README.md', readme, 'utf8');
1504
1509
  };
1505
1510
 
1506
1511
  /**
@@ -1743,7 +1748,6 @@ export {
1743
1748
  getDataDeploy,
1744
1749
  validateTemplatePath,
1745
1750
  buildReplicaId,
1746
- getCronBackUpFolder,
1747
1751
  mergeFile,
1748
1752
  getPathsSSR,
1749
1753
  buildKindPorts,
@@ -147,18 +147,25 @@ class UnderpostStartUp {
147
147
  * @param {string} deployId - The ID of the deployment.
148
148
  * @param {string} env - The environment of the deployment.
149
149
  * @param {Object} options - Options for the build.
150
+ * @param {boolean} options.skipPullBase - Whether to skip pulling the base code and use the current workspace code directly.
150
151
  * @param {boolean} options.underpostQuicklyInstall - Whether to use underpost quickly install.
151
152
  * @memberof UnderpostStartUp
152
153
  */
153
- async build(deployId = 'dd-default', env = 'development', options = { underpostQuicklyInstall: false }) {
154
+ async build(
155
+ deployId = 'dd-default',
156
+ env = 'development',
157
+ options = { underpostQuicklyInstall: false, skipPullBase: false },
158
+ ) {
154
159
  const buildBasePath = `/home/dd`;
155
160
  const repoName = `engine-${deployId.split('-')[1]}`;
156
- shellExec(`cd ${buildBasePath} && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}`);
157
- shellExec(`mkdir -p ${buildBasePath}/engine`);
158
- shellExec(`cd ${buildBasePath} && sudo cp -a ./${repoName}/. ./engine`);
159
- shellExec(`cd ${buildBasePath} && sudo rm -rf ./${repoName}`);
160
- shellExec(`cd ${buildBasePath}/engine && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}-private`);
161
- shellExec(`cd ${buildBasePath}/engine && sudo mv ./${repoName}-private ./engine-private`);
161
+ if (!options.skipPullBase) {
162
+ shellExec(`cd ${buildBasePath} && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}`);
163
+ shellExec(`mkdir -p ${buildBasePath}/engine`);
164
+ shellExec(`cd ${buildBasePath} && sudo cp -a ./${repoName}/. ./engine`);
165
+ shellExec(`cd ${buildBasePath} && sudo rm -rf ./${repoName}`);
166
+ shellExec(`cd ${buildBasePath}/engine && underpost clone ${process.env.GITHUB_USERNAME}/${repoName}-private`);
167
+ shellExec(`cd ${buildBasePath}/engine && sudo mv ./${repoName}-private ./engine-private`);
168
+ }
162
169
  shellCd(`${buildBasePath}/engine`);
163
170
  shellExec(options?.underpostQuicklyInstall ? `underpost install` : `npm install`);
164
171
  shellExec(`node bin env ${deployId} ${env}`);
@@ -167,7 +174,7 @@ class UnderpostStartUp {
167
174
  for (const itcScript of itcScripts)
168
175
  if (itcScript.match(deployId)) shellExec(`node ./engine-private/itc-scripts/${itcScript}`);
169
176
  }
170
- await Underpost.repo.client(deployId);
177
+ shellExec(`node bin client ${deployId}`);
171
178
  },
172
179
  /**
173
180
  * Runs a deployment.