create-file.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  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 maxIndexA = linesA.length - 1;
  13. let index = -1;
  14. for await (const lineB of source) {
  15. index++;
  16. if (index > maxIndexA) {
  17. return false;
  18. }
  19. const lineA = linesA[index];
  20. const lineAIsComment = isCommentLine(lineA);
  21. const lineBIsComment = isCommentLine(lineB);
  22. if (lineAIsComment !== lineBIsComment) {
  23. return false;
  24. }
  25. // Now both line are either both comment or both not comment
  26. // We only need to compare one of them
  27. if (lineAIsComment) {
  28. continue;
  29. }
  30. if (lineA !== lineB) {
  31. return false;
  32. }
  33. }
  34. return index === maxIndexA;
  35. }
  36. export function isCommentLine(line: string): boolean {
  37. const firstChar = line.charCodeAt(0);
  38. return (
  39. firstChar === 35 // #
  40. || firstChar === 33 // !
  41. || (firstChar === 47 && line[1] === '/' && line[3] === '#') // //##
  42. );
  43. }
  44. export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
  45. const linesALen = linesA.length;
  46. const isEqual = await span.traceChildAsync(`compare ${filePath}`, async () => {
  47. if (fs.existsSync(filePath)) {
  48. return fileEqual(linesA, readFileByLine(filePath));
  49. }
  50. console.log(`${filePath} does not exists, writing...`);
  51. return false;
  52. });
  53. if (isEqual) {
  54. console.log(picocolors.gray(picocolors.dim(`same content, bail out writing: ${filePath}`)));
  55. return;
  56. }
  57. await span.traceChildAsync(`writing ${filePath}`, async () => {
  58. // The default highwater mark is normally 16384,
  59. // So we make sure direct write to file if the content is
  60. // most likely less than 500 lines
  61. if (linesALen < 500) {
  62. return writeFile(filePath, fastStringArrayJoin(linesA, '\n') + '\n');
  63. }
  64. const writeStream = fs.createWriteStream(filePath);
  65. for (let i = 0; i < linesALen; i++) {
  66. const p = asyncWriteToStream(writeStream, linesA[i] + '\n');
  67. // eslint-disable-next-line no-await-in-loop -- stream high water mark
  68. if (p) await p;
  69. }
  70. writeStream.end();
  71. });
  72. }