create-file.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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. if (lineA.length === 0) {
  34. if (lineB.length === 0) {
  35. // eslint-disable-next-line no-await-in-loop -- sequential
  36. result = await iterator.next();
  37. lineB = result.value;
  38. continue;
  39. }
  40. return false;
  41. }
  42. const aFirstChar = lineA.charCodeAt(0);
  43. if (aFirstChar !== lineB.charCodeAt(0)) {
  44. return false;
  45. }
  46. // Now both line has the same first char
  47. // We only need to compare one of them
  48. if (
  49. aFirstChar === 35 // #
  50. || aFirstChar === 33 // !
  51. ) {
  52. // eslint-disable-next-line no-await-in-loop -- sequential
  53. result = await iterator.next();
  54. lineB = result.value;
  55. continue;
  56. }
  57. if (lineA !== lineB) {
  58. return false;
  59. }
  60. // eslint-disable-next-line no-await-in-loop -- sequential
  61. result = await iterator.next();
  62. lineB = result.value;
  63. }
  64. // b is not smaller than a
  65. return index === maxIndexA;
  66. }
  67. export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
  68. const isEqual = await span.traceChildAsync(`compare ${filePath}`, async () => {
  69. if (fs.existsSync(filePath)) {
  70. return fileEqual(linesA, readFileByLine(filePath));
  71. }
  72. console.log(`${filePath} does not exists, writing...`);
  73. return false;
  74. });
  75. if (isEqual) {
  76. console.log(picocolors.gray(picocolors.dim(`same content, bail out writing: ${filePath}`)));
  77. return;
  78. }
  79. return span.traceChildAsync<void>(`writing ${filePath}`, async () => {
  80. const linesALen = linesA.length;
  81. // The default highwater mark is normally 16384,
  82. // So we make sure direct write to file if the content is
  83. // most likely less than 500 lines
  84. if (linesALen < 500) {
  85. return writeFile(filePath, fastStringArrayJoin(linesA, '\n') + '\n');
  86. }
  87. const writeStream = fs.createWriteStream(filePath);
  88. for (let i = 0; i < linesALen; i++) {
  89. const p = asyncWriteToStream(writeStream, linesA[i] + '\n');
  90. // eslint-disable-next-line no-await-in-loop -- stream high water mark
  91. if (p) await p;
  92. }
  93. writeStream.end();
  94. writeStream.close();
  95. });
  96. }