create-file.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { asyncWriteToStream } from 'foxts/async-write-to-stream';
  2. import { fastStringArrayJoin } from 'foxts/fast-string-array-join';
  3. import fs from 'node:fs';
  4. import picocolors from 'picocolors';
  5. import type { Span } from '../trace';
  6. import { readFileByLine } from './fetch-text-by-line';
  7. import { writeFile } from './misc';
  8. export async function fileEqual(linesA: string[], source: AsyncIterable<string> | Iterable<string>): Promise<boolean> {
  9. if (linesA.length === 0) {
  10. return false;
  11. }
  12. const linesABound = linesA.length - 1;
  13. let index = -1;
  14. let aLen = 0;
  15. let bLen = 0;
  16. for await (const lineB of source) {
  17. index++;
  18. if (index > linesABound) {
  19. return (index === linesA.length && lineB.length === 0);
  20. }
  21. const lineA = linesA[index];
  22. aLen = lineA.length;
  23. bLen = lineB.length;
  24. if (aLen === 0) {
  25. if (bLen === 0) {
  26. // both lines are empty, check next line
  27. continue;
  28. }
  29. // lineA is empty but lineB is not
  30. return false;
  31. }
  32. // now lineA can not be empty
  33. if (bLen === 0) {
  34. // lineB is empty but lineA is not
  35. return false;
  36. }
  37. // now both lines can not be empty
  38. const firstCharA = lineA.charCodeAt(0);
  39. const firstCharB = lineB.charCodeAt(0);
  40. if (firstCharA !== firstCharB) {
  41. return false;
  42. }
  43. // now firstCharA is equal to firstCharB, we only need to check the first char
  44. if (firstCharA === 35 /* # */) {
  45. continue;
  46. }
  47. // adguard conf
  48. if (firstCharA === 33 /* ! */) {
  49. continue;
  50. }
  51. if (
  52. firstCharA === 47 /* / */
  53. && lineA[1] === '/' && lineB[1] === '/'
  54. && lineA[3] === '#' && lineB[3] === '#'
  55. ) {
  56. continue;
  57. }
  58. if (aLen !== bLen) {
  59. return false;
  60. }
  61. if (lineA !== lineB) {
  62. return false;
  63. }
  64. }
  65. // The file becomes larger
  66. return !(index < linesABound);
  67. }
  68. export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
  69. const linesALen = linesA.length;
  70. const isEqual = await span.traceChildAsync(`compare ${filePath}`, async () => {
  71. if (fs.existsSync(filePath)) {
  72. return fileEqual(linesA, readFileByLine(filePath));
  73. }
  74. console.log(`${filePath} does not exists, writing...`);
  75. return false;
  76. });
  77. if (isEqual) {
  78. console.log(picocolors.gray(picocolors.dim(`same content, bail out writing: ${filePath}`)));
  79. return;
  80. }
  81. await span.traceChildAsync(`writing ${filePath}`, async () => {
  82. // The default highwater mark is normally 16384,
  83. // So we make sure direct write to file if the content is
  84. // most likely less than 500 lines
  85. if (linesALen < 500) {
  86. return writeFile(filePath, fastStringArrayJoin(linesA, '\n') + '\n');
  87. }
  88. const writeStream = fs.createWriteStream(filePath);
  89. for (let i = 0; i < linesALen; i++) {
  90. const p = asyncWriteToStream(writeStream, linesA[i] + '\n');
  91. // eslint-disable-next-line no-await-in-loop -- stream high water mark
  92. if (p) await p;
  93. }
  94. writeStream.end();
  95. });
  96. }