create-file.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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. import { invariant } from 'foxts/guard';
  9. export async function fileEqual(linesA: string[], source: AsyncIterable<string> | Iterable<string>): Promise<boolean> {
  10. if (linesA.length === 0) {
  11. return false;
  12. }
  13. const aLen = linesA.length;
  14. const maxIndexA = aLen - 1;
  15. let index = -1;
  16. const iterator = Symbol.asyncIterator in source
  17. ? source[Symbol.asyncIterator]()
  18. : (
  19. Symbol.iterator in source
  20. ? source[Symbol.iterator]()
  21. : null
  22. );
  23. invariant(iterator, 'source must be iterable or async iterable');
  24. let result = await iterator.next();
  25. let lineB: string = result.value;
  26. while (!result.done) {
  27. index++;
  28. // b become bigger
  29. if (index === aLen) {
  30. return false;
  31. }
  32. const lineA = linesA[index];
  33. const aFirstChar = lineA.charCodeAt(0);
  34. if (aFirstChar !== lineB.charCodeAt(0)) {
  35. return false;
  36. }
  37. // Now both line has the same first char
  38. // We only need to compare one of them
  39. if (
  40. aFirstChar === 35 // #
  41. || aFirstChar === 33 // !
  42. ) {
  43. // eslint-disable-next-line no-await-in-loop -- sequential
  44. result = await iterator.next();
  45. lineB = result.value;
  46. continue;
  47. }
  48. if (lineA !== lineB) {
  49. return false;
  50. }
  51. // eslint-disable-next-line no-await-in-loop -- sequential
  52. result = await iterator.next();
  53. lineB = result.value;
  54. }
  55. // b is not smaller than a
  56. return index === maxIndexA;
  57. }
  58. export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
  59. const isEqual = await span.traceChildAsync(`compare ${filePath}`, async () => {
  60. if (fs.existsSync(filePath)) {
  61. return fileEqual(linesA, readFileByLine(filePath));
  62. }
  63. console.log(`${filePath} does not exists, writing...`);
  64. return false;
  65. });
  66. if (isEqual) {
  67. console.log(picocolors.gray(picocolors.dim(`same content, bail out writing: ${filePath}`)));
  68. return;
  69. }
  70. await span.traceChildAsync(`writing ${filePath}`, async () => {
  71. const linesALen = linesA.length;
  72. // The default highwater mark is normally 16384,
  73. // So we make sure direct write to file if the content is
  74. // most likely less than 500 lines
  75. if (linesALen < 500) {
  76. return writeFile(filePath, fastStringArrayJoin(linesA, '\n') + '\n');
  77. }
  78. const writeStream = fs.createWriteStream(filePath);
  79. for (let i = 0; i < linesALen; i++) {
  80. const p = asyncWriteToStream(writeStream, linesA[i] + '\n');
  81. // eslint-disable-next-line no-await-in-loop -- stream high water mark
  82. if (p) await p;
  83. }
  84. writeStream.end();
  85. writeStream.close();
  86. });
  87. }