@metricinsights/pp-dev 0.3.3 → 0.5.0

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/dist/cjs/cli.js CHANGED
@@ -4,21 +4,26 @@ var path = require('path');
4
4
  var fs = require('fs');
5
5
  var node_perf_hooks = require('node:perf_hooks');
6
6
  var cac = require('cac');
7
- var picocolors = require('picocolors');
8
- var index = require('./index-ab672d81.js');
7
+ var index = require('./index-44d9499e.js');
8
+ var plugin = require('./plugin-34b9bc89.js');
9
9
  var vite = require('vite');
10
10
  var url = require('url');
11
- var plugin = require('./plugin-24dd0a43.js');
12
11
  var express = require('express');
13
12
  var winston = require('winston');
14
- require('vite-plugin-zip-pack');
15
- require('esbuild');
13
+ var dirCompare = require('dir-compare');
14
+ var DiffMatchPatch = require('diff-match-patch');
15
+ var isbinaryfile = require('isbinaryfile');
16
+ var os = require('os');
17
+ var crypto = require('crypto');
18
+ var extractZip = require('extract-zip');
16
19
  require('ejs');
20
+ require('esbuild');
17
21
  require('http-proxy-middleware');
22
+ require('picocolors');
18
23
  require('axios');
19
24
  require('jsdom');
25
+ require('https');
20
26
  require('memory-cache');
21
- require('crypto');
22
27
  require('process');
23
28
  require('child_process');
24
29
  require('console');
@@ -45,8 +50,10 @@ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
45
50
  var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
46
51
  var express__namespace = /*#__PURE__*/_interopNamespaceDefault(express);
47
52
  var winston__namespace = /*#__PURE__*/_interopNamespaceDefault(winston);
53
+ var dirCompare__namespace = /*#__PURE__*/_interopNamespaceDefault(dirCompare);
54
+ var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
55
+ var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
48
56
 
49
- const colors$2 = picocolors.createColors();
50
57
  function isDefined(value) {
51
58
  return value != null;
52
59
  }
@@ -55,8 +62,9 @@ function bindShortcuts(server, opts) {
55
62
  return;
56
63
  }
57
64
  server._shortcutsOptions = opts;
65
+ const logger = plugin.createLogger();
58
66
  if (opts.print) {
59
- server.config.logger.info(colors$2.dim(colors$2.green(' ➜')) + colors$2.dim(' press ') + colors$2.bold('h') + colors$2.dim(' to show help'));
67
+ logger.info(plugin.colors.dim(plugin.colors.green(' ➜')) + plugin.colors.dim(' press ') + plugin.colors.bold('h') + plugin.colors.dim(' to show help'));
60
68
  }
61
69
  const shortcuts = (opts.customShortcuts ?? []).filter(isDefined).concat(BASE_SHORTCUTS);
62
70
  let actionRunning = false;
@@ -70,10 +78,10 @@ function bindShortcuts(server, opts) {
70
78
  return;
71
79
  }
72
80
  if (input === 'h') {
73
- server.config.logger.info([
81
+ logger.info([
74
82
  '',
75
- colors$2.bold(' Shortcuts'),
76
- ...shortcuts.map((shortcut) => colors$2.dim(' press ') + colors$2.bold(shortcut.key) + colors$2.dim(` to ${shortcut.description}`)),
83
+ plugin.colors.bold(' Shortcuts'),
84
+ ...shortcuts.map((shortcut) => plugin.colors.dim(' press ') + plugin.colors.bold(shortcut.key) + plugin.colors.dim(` to ${shortcut.description}`)),
77
85
  ].join('\n'));
78
86
  }
79
87
  const shortcut = shortcuts.find((shortcut) => shortcut.key === input);
@@ -139,7 +147,6 @@ const BASE_SHORTCUTS = [
139
147
  },
140
148
  ];
141
149
 
142
- const colors$1 = picocolors.createColors();
143
150
  function createDevServer(logLevel = 'info') {
144
151
  const server = express__namespace.default();
145
152
  const logger = winston__namespace.createLogger({
@@ -159,24 +166,492 @@ function createDevServer(logLevel = 'info') {
159
166
  if (!listener) {
160
167
  throw new Error('Server is not listening');
161
168
  }
162
- const colorUrl = (url) => colors$1.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors$1.bold(port)}/`));
169
+ const colorUrl = (url) => plugin.colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${plugin.colors.bold(port)}/`));
163
170
  const address = listener.address();
164
171
  if (address && typeof address === 'object') {
165
172
  if (address.address === '::') {
166
173
  const url$1 = new url.URL(base || '', `http://localhost:${address.port}`);
167
- logger.info(` ${colors$1.green('➜')} ${colors$1.bold('Local')}: ${colorUrl(url$1.toString())}`);
174
+ logger.info(` ${plugin.colors.green('➜')} ${plugin.colors.bold('Local')}: ${colorUrl(url$1.toString())}`);
168
175
  }
169
176
  else {
170
177
  const url$1 = new url.URL(base || '', `http://[${address.address}]:${address.port}`);
171
- logger.info(` ${colors$1.green('➜')} ${colors$1.bold('Local')}: ${colorUrl(url$1.toString())}`);
178
+ logger.info(` ${plugin.colors.green('➜')} ${plugin.colors.bold('Local')}: ${colorUrl(url$1.toString())}`);
172
179
  }
173
180
  }
174
181
  };
175
182
  return server;
176
183
  }
177
184
 
185
+ const changelogTemplate = /* HTML */ `<!DOCTYPE html>
186
+ <html lang="en">
187
+ <head>
188
+ <meta charset="UTF-8" />
189
+ <title>Changelog Diff</title>
190
+ <style>
191
+ tr,
192
+ td {
193
+ padding: 0;
194
+ }
195
+ .diff-file {
196
+ margin-top: 20px;
197
+ border: 1px solid #e1e4e8;
198
+ border-radius: 6px;
199
+ }
200
+ .diff-file-title {
201
+ padding: 10px 20px;
202
+ background-color: #f6f8fa;
203
+ border-bottom: 1px solid #e1e4e8;
204
+ border-radius: 6px 6px 0 0;
205
+ font-weight: bold;
206
+ }
207
+ .diff-file-title .renamed {
208
+ font-weight: normal;
209
+ }
210
+ .diff-file-title .renamed .from {
211
+ color: #cb2431;
212
+ }
213
+ .diff-file-title .renamed .to {
214
+ color: #22863a;
215
+ }
216
+ .diff-file-title .added {
217
+ color: #22863a;
218
+ }
219
+ .diff-file-title .deleted {
220
+ color: #cb2431;
221
+ }
222
+ .diff-file-content {
223
+ }
224
+ .diff-table {
225
+ tab-size: 8;
226
+ width: 100%;
227
+ border-collapse: separate;
228
+ border-spacing: 0;
229
+ }
230
+ .blob-num {
231
+ position: relative;
232
+ color: #1f2328;
233
+ width: 1%;
234
+ min-width: 50px;
235
+ padding: 0 10px;
236
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
237
+ font-size: 12px;
238
+ line-height: 20px;
239
+ text-align: right;
240
+ white-space: nowrap;
241
+ vertical-align: top;
242
+ cursor: pointer;
243
+ -webkit-user-select: none;
244
+ user-select: none;
245
+ }
246
+ .blob-num.addition {
247
+ background-color: #ccffd8;
248
+ border-color: #1f883e;
249
+ }
250
+ .blob-num.deletion {
251
+ background-color: #ffd7d5;
252
+ border-color: #cf222e;
253
+ }
254
+ .blob-num::before {
255
+ content: attr(data-line-number);
256
+ }
257
+ .blob-code {
258
+ position: relative;
259
+ padding: 0 10px 0 22px;
260
+ vertical-align: top;
261
+ color: #1f2329;
262
+ }
263
+ .blob-code.code-addition {
264
+ background-color: #e6ffec;
265
+ }
266
+ .blob-code.code-deletion {
267
+ background-color: #ffebe9;
268
+ }
269
+ .blob-code.skip,
270
+ .blob-code.message {
271
+ text-align: center;
272
+ }
273
+ .blob-code.skip .blob-code-inner,
274
+ .blob-code.message .blob-code-inner {
275
+ font-weight: bold;
276
+ color: #6a737d;
277
+ padding: 10px 0;
278
+ }
279
+ .blob-code-inner {
280
+ display: table-cell;
281
+ overflow: visible;
282
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
283
+ font-size: 12px;
284
+ word-wrap: anywhere;
285
+ white-space: pre-wrap;
286
+ }
287
+ .blob-code-inner::before {
288
+ content: attr(data-code-prefix);
289
+ position: absolute;
290
+ top: 1px;
291
+ left: 8px;
292
+ padding-right: 8px;
293
+ }
294
+ </style>
295
+ </head>
296
+ <body>
297
+ <h1>Changelog Diff</h1>
298
+
299
+ %FILES%
300
+ </body>
301
+ </html>`;
302
+ const LINE_ADDED = 1;
303
+ const LINE_REMOVED = -1;
304
+ const LINE_CONTEXT = 0;
305
+ /**
306
+ * Escape HTML sequence
307
+ * @param str
308
+ */
309
+ function escapeHTMLSequence(str) {
310
+ return str.replace(/[\u00A0-\u9999<>&]/g, (i) => '&#' + i.charCodeAt(0) + ';');
311
+ }
312
+ const fileNameWithHashRegEx = /(.+)(-[a-f0-9]{6,20})(\.[a-z0-9]+)$/i;
313
+ /**
314
+ * Changelog generator class
315
+ * @class
316
+ * @classdesc
317
+ * Generates a changelog file based on the differences between two asset folders or zip files
318
+ * @example
319
+ * const changelogGenerator = new ChangelogGenerator({
320
+ * previousAssetsPath: './dist-zip/previous',
321
+ * currentAssetsPath: './dist-zip/current',
322
+ * destinationPath: './dist-zip',
323
+ * changelogFilename: 'CHANGELOG.html',
324
+ * });
325
+ * changelogGenerator.generateChangelog();
326
+ */
327
+ class ChangelogGenerator {
328
+ oldAssetsPath;
329
+ newAssetsPath;
330
+ destinationPath;
331
+ changelogFilename;
332
+ changelogTemplate = changelogTemplate;
333
+ diffFileTemplateHandler;
334
+ diffLineTemplateHandler;
335
+ contextLines = 3;
336
+ logger;
337
+ /**
338
+ * Changelog generator class constructor
339
+ * @param opts
340
+ */
341
+ constructor(opts) {
342
+ const { oldAssetsPath, newAssetsPath, destinationPath, changelogTemplate, diffFileTemplateHandler, diffLineTemplateHandler, changelogFilename, contextLines, } = opts;
343
+ this.logger = plugin.createLogger();
344
+ if (!oldAssetsPath || !newAssetsPath || !destinationPath) {
345
+ throw new Error('Previous assets path, current assets path and destination path are required');
346
+ }
347
+ if (oldAssetsPath === newAssetsPath) {
348
+ throw new Error('Previous and current assets paths must be different');
349
+ }
350
+ if (!this.isExists(oldAssetsPath)) {
351
+ throw new Error(`Previous assets path ${oldAssetsPath} does not exist`);
352
+ }
353
+ if (!this.isExists(newAssetsPath)) {
354
+ throw new Error(`Current assets path ${newAssetsPath} does not exist`);
355
+ }
356
+ if (this.isZipFile(oldAssetsPath)) {
357
+ const unzipDestinationPath = path__namespace.resolve(os__namespace.tmpdir(), crypto__namespace.createHash('md5').update(oldAssetsPath).digest('hex'));
358
+ this.oldAssetsPath = this.unzipFile(oldAssetsPath, unzipDestinationPath)
359
+ .then(() => {
360
+ return this.normalizeAssetFolderPath(unzipDestinationPath);
361
+ })
362
+ .then((path) => {
363
+ if (this.isEmptyFolder(path)) {
364
+ throw new Error(`Previous assets path ${path} is empty`);
365
+ }
366
+ return path;
367
+ });
368
+ }
369
+ else if (this.isFolder(oldAssetsPath)) {
370
+ const path = this.normalizeAssetFolderPath(oldAssetsPath);
371
+ if (this.isEmptyFolder(path)) {
372
+ throw new Error(`Previous assets path ${path} is empty`);
373
+ }
374
+ this.oldAssetsPath = Promise.resolve(path);
375
+ }
376
+ else {
377
+ throw new Error(`Invalid previous assets path ${oldAssetsPath}. It must be a folder or a zip file`);
378
+ }
379
+ if (this.isZipFile(newAssetsPath)) {
380
+ const unzipDestinationPath = path__namespace.resolve(os__namespace.tmpdir(), crypto__namespace.createHash('md5').update(newAssetsPath).digest('hex'));
381
+ this.newAssetsPath = this.unzipFile(newAssetsPath, unzipDestinationPath)
382
+ .then(() => {
383
+ return this.normalizeAssetFolderPath(unzipDestinationPath);
384
+ })
385
+ .then((path) => {
386
+ if (this.isEmptyFolder(path)) {
387
+ throw new Error(`Current assets path ${path} is empty`);
388
+ }
389
+ return path;
390
+ });
391
+ }
392
+ else if (this.isFolder(newAssetsPath)) {
393
+ const path = this.normalizeAssetFolderPath(newAssetsPath);
394
+ if (this.isEmptyFolder(path)) {
395
+ throw new Error(`Current assets path ${newAssetsPath} is empty`);
396
+ }
397
+ this.newAssetsPath = Promise.resolve(path);
398
+ }
399
+ else {
400
+ throw new Error(`Invalid current assets path ${newAssetsPath}. It must be a folder or a zip file`);
401
+ }
402
+ this.destinationPath = destinationPath;
403
+ this.mkdirpSync(this.destinationPath);
404
+ if (changelogFilename) {
405
+ this.changelogFilename = changelogFilename;
406
+ }
407
+ else {
408
+ this.changelogFilename = 'CHANGELOG.html';
409
+ }
410
+ if (changelogTemplate) {
411
+ if (this.templateIsValid(changelogTemplate)) {
412
+ this.changelogTemplate = changelogTemplate;
413
+ }
414
+ else {
415
+ this.logger.warn(plugin.colors.yellow('Invalid changelog template, using default'));
416
+ }
417
+ }
418
+ if (typeof diffFileTemplateHandler === 'function') {
419
+ this.diffFileTemplateHandler = diffFileTemplateHandler;
420
+ }
421
+ if (typeof diffLineTemplateHandler === 'function') {
422
+ this.diffLineTemplateHandler = diffLineTemplateHandler;
423
+ }
424
+ if (contextLines) {
425
+ this.contextLines = contextLines;
426
+ }
427
+ }
428
+ templateIsValid(template) {
429
+ return template.includes('%FILES%');
430
+ }
431
+ isExists(assetPath) {
432
+ return fs__namespace.existsSync(assetPath);
433
+ }
434
+ isZipFile(assetPath) {
435
+ return assetPath.endsWith('.zip');
436
+ }
437
+ isFolder(assetPath) {
438
+ return fs__namespace.lstatSync(assetPath).isDirectory();
439
+ }
440
+ isEmptyFolder(assetPath) {
441
+ return fs__namespace.readdirSync(assetPath, { withFileTypes: true }).length === 0;
442
+ }
443
+ mkdirpSync(dir) {
444
+ if (!fs__namespace.existsSync(dir)) {
445
+ fs__namespace.mkdirSync(dir, { recursive: true });
446
+ }
447
+ }
448
+ async unzipFile(assetPath, destinationPath) {
449
+ fs__namespace.rmSync(destinationPath, { force: true, recursive: true });
450
+ return extractZip(assetPath, { dir: destinationPath });
451
+ }
452
+ normalizeAssetFolderPath(assetPath) {
453
+ const folderContent = fs__namespace.readdirSync(assetPath);
454
+ if (folderContent.length === 1 && fs__namespace.lstatSync(path__namespace.join(assetPath, folderContent[0])).isDirectory()) {
455
+ return this.normalizeAssetFolderPath(path__namespace.join(assetPath, folderContent[0]));
456
+ }
457
+ return assetPath;
458
+ }
459
+ pathToPosix(path) {
460
+ return path.replace(/\\/g, '/');
461
+ }
462
+ diffTableTemplate(diffLinesHTML) {
463
+ return /* HTML */ `<table class="diff-table">
464
+ <tbody>
465
+ ${diffLinesHTML}
466
+ </tbody>
467
+ </table>`;
468
+ }
469
+ getDiffTableHTML(diffLines) {
470
+ const diffLinesHTML = diffLines
471
+ .filter((value, index, array) => {
472
+ if (value.lineType === LINE_CONTEXT) {
473
+ return ((index >= 0 && index < this.contextLines) ||
474
+ (index > array.length - (this.contextLines + 1) && index <= array.length - 1) ||
475
+ array
476
+ .slice(index - this.contextLines, index + this.contextLines + 1)
477
+ .some((line) => line.lineType !== LINE_CONTEXT));
478
+ }
479
+ return true;
480
+ })
481
+ .map((line, index, array) => {
482
+ if (index > 0) {
483
+ const prevLine = array[index - 1];
484
+ if (line.lineNumber - prevLine.lineNumber > 1) {
485
+ return [{ lineNumber: -1, lineContent: '', lineType: LINE_CONTEXT }, line];
486
+ }
487
+ }
488
+ return [line];
489
+ })
490
+ .flat()
491
+ .map((line) => this.diffLineHTML(line))
492
+ .join('');
493
+ return this.diffTableTemplate(diffLinesHTML);
494
+ }
495
+ diffLineMessageTemplate(message) {
496
+ return /* HTML */ `<tr>
497
+ <td class="blob-num"></td>
498
+ <td class="blob-num"></td>
499
+ <td class="blob-code message">
500
+ <span class="blob-code-inner">${message}</span>
501
+ </td>
502
+ </tr>`;
503
+ }
504
+ diffLineSkipTemplate() {
505
+ return /* HTML */ `<tr>
506
+ <td class="blob-num"></td>
507
+ <td class="blob-num"></td>
508
+ <td class="blob-code skip">
509
+ <span class="blob-code-inner">Skip</span>
510
+ </td>
511
+ </tr>`;
512
+ }
513
+ diffLineTemplate(diffLine) {
514
+ const { lineNumber, lineContent, lineType } = diffLine;
515
+ const numClass = lineType === LINE_ADDED ? 'addition' : lineType === LINE_REMOVED ? 'deletion' : '';
516
+ const codeClass = lineType === LINE_ADDED ? 'code-addition' : lineType === LINE_REMOVED ? 'code-deletion' : '';
517
+ const prefix = lineType === LINE_ADDED ? '+' : lineType === LINE_REMOVED ? '-' : ' ';
518
+ return /* HTML */ `<tr>
519
+ <td
520
+ class="blob-num ${numClass}${numClass === 'addition' ? ' empty' : ''}"
521
+ ${numClass !== 'addition' ? ` data-line-number="${lineNumber}"` : ''}
522
+ ></td>
523
+ <td
524
+ class="blob-num ${numClass}${numClass === 'deletion' ? ' empty' : ''}"
525
+ ${numClass !== 'deletion' ? ` data-line-number="${lineNumber}"` : ''}
526
+ ></td>
527
+ <td class="blob-code ${codeClass}">
528
+ <span class="blob-code-inner" data-code-prefix="${prefix}">${escapeHTMLSequence(lineContent ?? '')}</span>
529
+ </td>
530
+ </tr>`;
531
+ }
532
+ diffLineHTML(diffLine) {
533
+ if (this.diffLineTemplateHandler) {
534
+ return this.diffLineTemplateHandler(diffLine);
535
+ }
536
+ const { lineNumber } = diffLine;
537
+ if (lineNumber === -1) {
538
+ return this.diffLineSkipTemplate();
539
+ }
540
+ return this.diffLineTemplate(diffLine);
541
+ }
542
+ diffFileTemplate(filename, htmlDiff) {
543
+ if (this.diffFileTemplateHandler) {
544
+ return this.diffFileTemplateHandler(filename, htmlDiff);
545
+ }
546
+ return /* HTML */ `<div class="diff-file">
547
+ <div class="diff-file-title">${filename}</div>
548
+ <div class="diff-file-content">${htmlDiff}</div>
549
+ </div>`;
550
+ }
551
+ async generateAssetFoldersDiff() {
552
+ this.logger.info(plugin.colors.blue(`Comparing asset folders ${await this.oldAssetsPath} and ${await this.newAssetsPath}`));
553
+ const dirDiff = await dirCompare__namespace.compare(await this.oldAssetsPath, await this.newAssetsPath, {
554
+ compareContent: true,
555
+ skipSymlinks: true,
556
+ compareSize: true,
557
+ compareDate: false,
558
+ compareNameHandler: (name1, name2) => {
559
+ if (fileNameWithHashRegEx.test(name1)) {
560
+ name1 = name1.replace(fileNameWithHashRegEx, '$1$3');
561
+ }
562
+ if (fileNameWithHashRegEx.test(name2)) {
563
+ name2 = name2.replace(fileNameWithHashRegEx, '$1$3');
564
+ }
565
+ if (name1.localeCompare(name2) === 0) {
566
+ return 0;
567
+ }
568
+ return name1.localeCompare(name2) > 0 ? 1 : -1;
569
+ },
570
+ });
571
+ return dirDiff.diffSet?.filter((d) => d.state !== 'equal' || d.name1 !== d.name2) || [];
572
+ }
573
+ async generateAssetFilesDiff(oldFileString, newFileString) {
574
+ const dmp = new DiffMatchPatch();
575
+ const linesChars = dmp.diff_linesToChars_(oldFileString, newFileString);
576
+ const linesDiff = dmp.diff_main(linesChars.chars1, linesChars.chars2, false);
577
+ dmp.diff_charsToLines_(linesDiff, linesChars.lineArray);
578
+ let lineNumber = 0;
579
+ return linesDiff
580
+ .map((diffLine) => {
581
+ const [lineType, lineContent] = diffLine;
582
+ const linesCount = lineContent.endsWith('\n')
583
+ ? lineContent.split('\n').length - 1
584
+ : lineContent.split('\n').length;
585
+ const lines = lineContent
586
+ .split('\n')
587
+ .map((line, index) => {
588
+ return { lineContent: line, lineNumber: lineNumber + index + 1, lineType };
589
+ })
590
+ .slice(0, linesCount);
591
+ if (lineType !== LINE_REMOVED) {
592
+ lineNumber += linesCount;
593
+ }
594
+ return lines;
595
+ })
596
+ .flat();
597
+ }
598
+ /**
599
+ * Generate diff for a file
600
+ * @param difference
601
+ * @private
602
+ */
603
+ async generateFilesDiff(difference) {
604
+ if (difference.state === 'equal') {
605
+ const filepath1 = this.pathToPosix(path__namespace.join('.', difference.relativePath, difference.name1 ?? ''));
606
+ const filepath2 = this.pathToPosix(path__namespace.join('.', difference.relativePath, difference.name2 ?? ''));
607
+ return this.diffFileTemplate(
608
+ /* HTML */ `<span class="renamed"
609
+ >Renamed <span class="from">${filepath1}</span> -> <span class="to">${filepath2}</span></span
610
+ >`, this.diffTableTemplate(this.diffLineMessageTemplate('No changes')));
611
+ }
612
+ const assetFile1Path = difference.path1 && difference.name1 ? path__namespace.join(difference.path1, difference.name1) : null;
613
+ const assetFile2Path = difference.path2 && difference.name2 ? path__namespace.join(difference.path2, difference.name2) : null;
614
+ const filepath = this.pathToPosix(path__namespace.join('.', difference.relativePath, (difference.name1 || difference.name2) ?? ''));
615
+ if (difference.state === 'left' && assetFile1Path) {
616
+ return this.diffFileTemplate(
617
+ /* HTML */ `<span class="removed">Removed ${filepath}</span>`, this.diffTableTemplate(this.diffLineMessageTemplate('File removed')));
618
+ }
619
+ if (difference.state === 'right' && assetFile2Path) {
620
+ return this.diffFileTemplate(
621
+ /* HTML */ `<span class="added">Added ${filepath}</span>`, this.diffTableTemplate(this.diffLineMessageTemplate('File added')));
622
+ }
623
+ if (difference.state === 'distinct' && assetFile1Path && assetFile2Path) {
624
+ const filepath1 = this.pathToPosix(path__namespace.join('.', difference.relativePath, difference.name1 ?? ''));
625
+ const filepath2 = this.pathToPosix(path__namespace.join('.', difference.relativePath, difference.name2 ?? ''));
626
+ const filenameTitle = difference.name1 !== difference.name2
627
+ ? /* HTML */ `<span class="renamed"
628
+ >Renamed <span class="from">${filepath1}</span> -> <span class="to">${filepath2}</span></span
629
+ >`
630
+ : filepath;
631
+ return this.diffFileTemplate(filenameTitle, (await isbinaryfile.isBinaryFile(assetFile1Path)) || (await isbinaryfile.isBinaryFile(assetFile2Path))
632
+ ? this.diffTableTemplate(this.diffLineMessageTemplate('Binary file'))
633
+ : this.getDiffTableHTML(await this.generateAssetFilesDiff(fs__namespace.readFileSync(assetFile1Path, 'utf-8'), fs__namespace.readFileSync(assetFile2Path, 'utf-8'))));
634
+ }
635
+ return '';
636
+ }
637
+ /**
638
+ * Generate changelog file based on the differences between two asset folders or zip files
639
+ * and write it to the destination folder
640
+ */
641
+ async generateChangelog() {
642
+ this.logger.info(plugin.colors.green('Generating changelog'));
643
+ const diffSet = await this.generateAssetFoldersDiff();
644
+ const htmlDiff = (await Promise.all(diffSet.map((difference) => this.generateFilesDiff(difference)))).join('');
645
+ this.logger.info(plugin.colors.green('Writing changelog file'));
646
+ const templateArray = this.changelogTemplate.split('%FILES%');
647
+ templateArray.splice(1, 0, htmlDiff);
648
+ const changelogHTML = templateArray.join('');
649
+ fs__namespace.writeFileSync(path__namespace.join(this.destinationPath, this.changelogFilename), changelogHTML);
650
+ this.logger.info(plugin.colors.green(`Changelog file written to ${path__namespace.join(this.destinationPath, this.changelogFilename)}`));
651
+ }
652
+ }
653
+
178
654
  const cli = cac.cac('pp-dev');
179
- const colors = picocolors.createColors();
180
655
  let profileSession = global.__pp_dev_profile_session;
181
656
  let profileCount = 0;
182
657
  const stopProfiler = (log) => {
@@ -189,7 +664,7 @@ const stopProfiler = (log) => {
189
664
  if (!err) {
190
665
  const outPath = path__namespace.resolve(`./pp-dev-profile-${profileCount++}.cpuprofile`);
191
666
  fs__namespace.writeFileSync(outPath, JSON.stringify(profile));
192
- log(colors.yellow(`CPU profile written to ${colors.white(colors.dim(outPath))}`));
667
+ log(plugin.colors.yellow(`CPU profile written to ${plugin.colors.white(plugin.colors.dim(outPath))}`));
193
668
  profileSession = undefined;
194
669
  res();
195
670
  }
@@ -267,19 +742,21 @@ cli
267
742
  clearScreen: options.clearScreen,
268
743
  optimizeDeps: { force: options.force },
269
744
  server: cleanOptions(options),
745
+ customLogger: plugin.createLogger(options.logLevel),
270
746
  }, true));
747
+ if (!server.config.base || server.config.base === '/') {
748
+ throw new Error('base cannot be equal to "/" or empty string');
749
+ }
271
750
  if (!server.httpServer) {
272
751
  throw new Error('HTTP server not available');
273
752
  }
274
753
  await server.listen();
275
- const info = server.config.logger.info;
754
+ const logger = plugin.createLogger(options.logLevel);
276
755
  const ppDevStartTime = global.__pp_dev_start_time ?? false;
277
756
  const startupDurationString = ppDevStartTime
278
- ? colors.dim(`ready in ${colors.reset(colors.bold(Math.ceil(node_perf_hooks.performance.now() - ppDevStartTime)))} ms`)
757
+ ? plugin.colors.dim(`ready in ${plugin.colors.reset(plugin.colors.bold(Math.ceil(node_perf_hooks.performance.now() - ppDevStartTime)))} ms`)
279
758
  : '';
280
- info(`\n ${colors.green(`${colors.bold('PP-DEV')} v${index.VERSION}`)} ${startupDurationString}\n`, {
281
- clear: !server.config.logger.hasWarned,
282
- });
759
+ logger.info(`\n ${plugin.colors.green(`${plugin.colors.bold('PP-DEV')} v${index.VERSION}`)} ${startupDurationString}\n`);
283
760
  server.printUrls();
284
761
  bindShortcuts(server, {
285
762
  print: true,
@@ -289,7 +766,7 @@ cli
289
766
  description: 'start/stop the profiler',
290
767
  async action(server) {
291
768
  if (profileSession) {
292
- await stopProfiler(server.config.logger.info);
769
+ await stopProfiler(logger.info);
293
770
  }
294
771
  else {
295
772
  const inspector = await import('node:inspector').then((r) => r.default);
@@ -298,7 +775,7 @@ cli
298
775
  profileSession.connect();
299
776
  profileSession.post('Profiler.enable', () => {
300
777
  profileSession?.post('Profiler.start', () => {
301
- server.config.logger.info('Profiler started');
778
+ logger.info('Profiler started');
302
779
  res();
303
780
  });
304
781
  });
@@ -321,8 +798,8 @@ cli
321
798
  });
322
799
  }
323
800
  catch (e) {
324
- const logger = vite.createLogger(options.logLevel);
325
- logger.error(colors.red(`error when starting dev server:\n${e.stack}`), {
801
+ const logger = plugin.createLogger(options.logLevel);
802
+ logger.error(plugin.colors.red(`error when starting dev server:\n${e.stack}`), {
326
803
  error: e,
327
804
  });
328
805
  stopProfiler(logger.info);
@@ -354,10 +831,13 @@ cli
354
831
  if (!base.endsWith('/')) {
355
832
  base += '/';
356
833
  }
834
+ if (base === '/') {
835
+ throw new Error('basePath cannot be equal to "/" or empty string');
836
+ }
357
837
  const baseWithoutTrailingSlash = base.substring(0, base.lastIndexOf('/'));
358
838
  const templateName = nextServer.nextConfig.serverRuntimeConfig.templateName;
359
839
  const ppDevConfig = nextServer.nextConfig.serverRuntimeConfig.ppDevConfig;
360
- const { backendBaseURL, portalPageId, templateLess = true, enableProxyCache = true, miHudLess = true, proxyCacheTTL = 10 * 60 * 1000, } = ppDevConfig;
840
+ const { backendBaseURL, portalPageId, templateLess = true, enableProxyCache = true, miHudLess = true, proxyCacheTTL = 10 * 60 * 1000, disableSSLValidation = false, } = ppDevConfig;
361
841
  server.use(plugin.initPPRedirect(base, templateName));
362
842
  if (backendBaseURL) {
363
843
  const baseUrlHost = new URL(backendBaseURL).host;
@@ -369,6 +849,7 @@ cli
369
849
  },
370
850
  portalPageId,
371
851
  templateLess,
852
+ disableSSLValidation,
372
853
  });
373
854
  if (enableProxyCache) {
374
855
  let ttl = +proxyCacheTTL;
@@ -381,6 +862,7 @@ cli
381
862
  devServer: server,
382
863
  baseURL: backendBaseURL,
383
864
  proxyIgnore: ['/@vite', '/@metricinsights', '/@', baseWithoutTrailingSlash],
865
+ disableSSLValidation,
384
866
  }));
385
867
  const isIndexRegExp = new RegExp(`^((${base})|/)$`);
386
868
  // Get portal page variables from the backend (also, redirect magic)
@@ -408,12 +890,12 @@ cli
408
890
  }));
409
891
  }
410
892
  });
411
- const info = server.config.logger.info;
893
+ const logger = plugin.createLogger();
412
894
  const ppDevStartTime = global.__pp_dev_start_time ?? false;
413
895
  const startupDurationString = ppDevStartTime
414
- ? colors.dim(`ready in ${colors.reset(colors.bold(Math.ceil(node_perf_hooks.performance.now() - ppDevStartTime)))} ms`)
896
+ ? plugin.colors.dim(`ready in ${plugin.colors.reset(plugin.colors.bold(Math.ceil(node_perf_hooks.performance.now() - ppDevStartTime)))} ms`)
415
897
  : '';
416
- info(`\n ${colors.green(`${colors.bold('PP-DEV')} v${index.VERSION}`)} ${startupDurationString}\n`, {
898
+ logger.info(`\n ${plugin.colors.green(`${plugin.colors.bold('PP-DEV')} v${index.VERSION}`)} ${startupDurationString}\n`, {
417
899
  clear: true,
418
900
  });
419
901
  server.printUrls(base);
@@ -456,8 +938,8 @@ cli
456
938
  // });
457
939
  }
458
940
  catch (e) {
459
- const logger = vite.createLogger(options.logLevel);
460
- logger.error(colors.red(`error when starting dev server:\n${e.stack}`), {
941
+ const logger = plugin.createLogger(options.logLevel);
942
+ logger.error(plugin.colors.red(`error when starting dev server:\n${e.stack}`), {
461
943
  error: e,
462
944
  });
463
945
  stopProfiler(logger.info);
@@ -479,6 +961,7 @@ cli
479
961
  .option('--force', `[boolean] force the optimizer to ignore the cache and re-bundle (experimental)`)
480
962
  .option('--emptyOutDir', `[boolean] force empty outDir when it's outside of root`)
481
963
  .option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`)
964
+ .option('--changelog [assetsFile]', `[boolean | string] generate changelog between assetsFile and current build (default: false)`)
482
965
  .action(async (root, options) => {
483
966
  filterDuplicateOptions(options);
484
967
  const buildOptions = cleanOptions(options);
@@ -489,7 +972,7 @@ cli
489
972
  const { plugins, ...fileConfig } = configFromFile.config;
490
973
  config = vite.mergeConfig(config, fileConfig);
491
974
  }
492
- await vite.build(vite.mergeConfig(config, {
975
+ const buildConfig = vite.mergeConfig(config, {
493
976
  root,
494
977
  base: options.base,
495
978
  mode: options.mode,
@@ -498,16 +981,90 @@ cli
498
981
  clearScreen: options.clearScreen,
499
982
  optimizeDeps: { force: options.force },
500
983
  build: buildOptions,
501
- }, true));
984
+ }, true);
985
+ await vite.build(buildConfig);
986
+ if (buildOptions.changelog) {
987
+ const executionRoot = root || process.cwd();
988
+ const outDir = buildConfig.build?.outDir || 'dist';
989
+ let oldAssetsPath = '';
990
+ if (typeof buildOptions.changelog === 'string') {
991
+ oldAssetsPath = path__namespace.resolve(executionRoot, buildOptions.changelog);
992
+ }
993
+ else {
994
+ const backupsDirPath = path__namespace.resolve(executionRoot, buildConfig.ppDevConfig?.syncBackupsDir || 'backups');
995
+ if (!fs__namespace.existsSync(backupsDirPath)) {
996
+ plugin.createLogger(options.logLevel).warn(plugin.colors.yellow(`backups directory not found, skipping changelog generation`));
997
+ return;
998
+ }
999
+ const backups = fs__namespace.readdirSync(backupsDirPath, { withFileTypes: true });
1000
+ if (!backups.length) {
1001
+ plugin.createLogger(options.logLevel).warn(plugin.colors.yellow(`no backups found, skipping changelog generation`));
1002
+ return;
1003
+ }
1004
+ const latestBackup = backups
1005
+ .filter((value) => {
1006
+ return value.isFile() && value.name.endsWith('.zip');
1007
+ })
1008
+ .reduce((latest, current) => {
1009
+ const latestTime = fs__namespace.statSync(path__namespace.resolve(backupsDirPath, latest.name)).mtimeMs;
1010
+ const currentTime = fs__namespace.statSync(path__namespace.resolve(backupsDirPath, current.name)).mtimeMs;
1011
+ return latestTime > currentTime ? latest : current;
1012
+ }, backups[0]).name;
1013
+ oldAssetsPath = path__namespace.resolve(backupsDirPath, latestBackup);
1014
+ }
1015
+ const currentAssetFilePath = path__namespace.resolve(executionRoot, outDir);
1016
+ let changelogDestination = 'dist-zip';
1017
+ if (buildConfig.ppDevConfig) {
1018
+ if (buildConfig.ppDevConfig.distZip === false) {
1019
+ changelogDestination = buildConfig.build?.outDir || 'dist';
1020
+ }
1021
+ else if (typeof buildConfig.ppDevConfig.distZip === 'object' &&
1022
+ typeof buildConfig.ppDevConfig.distZip.outDir === 'string') {
1023
+ changelogDestination = buildConfig.ppDevConfig.distZip.outDir;
1024
+ }
1025
+ }
1026
+ const changelogGenerator = new ChangelogGenerator({
1027
+ oldAssetsPath,
1028
+ newAssetsPath: currentAssetFilePath,
1029
+ destinationPath: path__namespace.resolve(executionRoot, changelogDestination),
1030
+ });
1031
+ await changelogGenerator.generateChangelog();
1032
+ }
502
1033
  }
503
1034
  catch (e) {
504
- vite.createLogger(options.logLevel).error(colors.red(`error during build:\n${e.stack}`), { error: e });
1035
+ plugin.createLogger(options.logLevel).error(plugin.colors.red(`error during build:\n${e.stack}`), { error: e });
505
1036
  process.exit(1);
506
1037
  }
507
1038
  finally {
508
- stopProfiler((message) => vite.createLogger(options.logLevel).info(message));
1039
+ stopProfiler((message) => plugin.createLogger(options.logLevel).info(message));
509
1040
  }
510
1041
  });
1042
+ // changelog
1043
+ cli
1044
+ .command('changelog [oldAssetPath] [newAssetPath]', 'generate changelog between two assets files/folders')
1045
+ .option('--oldAssetsPath <oldAssetsPath>', `[string] path to the old assets zip file or folder`)
1046
+ .option('--newAssetsPath <newAssetsPath>', `[string] path to the new assets zip file or folder`)
1047
+ .option('--destination <destination>', `[string] destination folder for the changelog (default: .)`)
1048
+ .option('--filename <filename>', `[string] filename for the changelog (default: CHANGELOG.html)`)
1049
+ .action(async (oldAssetPath, newAssetPath, options) => {
1050
+ filterDuplicateOptions(options);
1051
+ const { oldAssetsPath: oldPath = oldAssetPath, newAssetsPath: newPath = newAssetPath, destination = '.', filename = 'CHANGELOG.html', logLevel, } = options;
1052
+ const root = process.cwd();
1053
+ if (!oldPath || !newPath) {
1054
+ plugin.createLogger(logLevel).error(plugin.colors.red(`error during changelog generation: oldAssetPath and newAssetPath are required`));
1055
+ process.exit(1);
1056
+ }
1057
+ const fullOldPath = path__namespace.resolve(root, oldPath);
1058
+ const fullNewPath = path__namespace.resolve(root, newPath);
1059
+ const fullDestination = path__namespace.resolve(root, destination);
1060
+ const changelogGenerator = new ChangelogGenerator({
1061
+ oldAssetsPath: fullOldPath,
1062
+ newAssetsPath: fullNewPath,
1063
+ destinationPath: fullDestination,
1064
+ changelogFilename: filename,
1065
+ });
1066
+ await changelogGenerator.generateChangelog();
1067
+ });
511
1068
  // optimize
512
1069
  cli
513
1070
  .command('optimize [root]', 'pre-bundle dependencies')
@@ -531,7 +1088,7 @@ cli
531
1088
  await vite.optimizeDeps(optimizeConfig, options.force, true);
532
1089
  }
533
1090
  catch (e) {
534
- vite.createLogger(options.logLevel).error(colors.red(`error when optimizing deps:\n${e.stack}`), { error: e });
1091
+ plugin.createLogger(options.logLevel).error(plugin.colors.red(`error when optimizing deps:\n${e.stack}`), { error: e });
535
1092
  process.exit(1);
536
1093
  }
537
1094
  });
@@ -572,13 +1129,13 @@ cli
572
1129
  server.printUrls();
573
1130
  }
574
1131
  catch (e) {
575
- vite.createLogger(options.logLevel).error(colors.red(`error when starting preview server:\n${e.stack}`), {
1132
+ plugin.createLogger(options.logLevel).error(plugin.colors.red(`error when starting preview server:\n${e.stack}`), {
576
1133
  error: e,
577
1134
  });
578
1135
  process.exit(1);
579
1136
  }
580
1137
  finally {
581
- stopProfiler((message) => vite.createLogger(options.logLevel).info(message));
1138
+ stopProfiler((message) => plugin.createLogger(options.logLevel).info(message));
582
1139
  }
583
1140
  });
584
1141
  cli.help();