utils.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import fs from 'node:fs/promises'
  2. import { spawn } from 'node:child_process'
  3. import { envVars } from './config'
  4. import { downloadFile } from './download'
  5. export async function fileExistsAsync(filePath: string): Promise<boolean> {
  6. try {
  7. await fs.access(filePath)
  8. return true
  9. }
  10. catch {
  11. return false
  12. }
  13. }
  14. export async function renameWithOverwrite(oldPath: string, newPath: string): Promise<void> {
  15. try {
  16. try {
  17. await fs.access(newPath);
  18. await fs.unlink(newPath);
  19. console.log(`Файл по пути "${newPath}" был удален`);
  20. } catch (err) {
  21. if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
  22. throw err;
  23. }
  24. }
  25. await fs.rename(oldPath, newPath);
  26. } catch (err) {
  27. const error = err as NodeJS.ErrnoException;
  28. throw error;
  29. }
  30. }
  31. export async function removeDir(dir: string): Promise<void> {
  32. await fs.rm(dir, { recursive: true, force: true })
  33. }
  34. export async function createDir(dirPath: string): Promise<void> {
  35. await fs.mkdir(dirPath, { recursive: true })
  36. }
  37. export async function runCommand(
  38. command: string,
  39. args: string[] = [],
  40. options: {
  41. task?: { output?: string }
  42. env?: NodeJS.ProcessEnv
  43. disableOutput?: boolean
  44. maxOutputLines?: number
  45. } = { disableOutput: false },
  46. ): Promise<void> {
  47. if (!options.env) options.env = envVars
  48. const maxOutputLines = options.maxOutputLines ?? 10
  49. return new Promise((resolve, reject) => {
  50. const finalEnv = { ...envVars, ...(options.env || {}) }
  51. let outputLines: string[] = [`$ ${command} ${args.join(' ')}`]
  52. let remainingData = ''
  53. const child = spawn(command, args, { env: finalEnv, shell: true })
  54. const updateOutput = (data: Buffer) => {
  55. const text = data.toString()
  56. const fullText = remainingData + text
  57. const lines = fullText.split('\n')
  58. if (!fullText.endsWith('\n')) {
  59. remainingData = lines.pop() ?? ''
  60. }
  61. else {
  62. remainingData = ''
  63. }
  64. outputLines.push(...lines)
  65. if (outputLines.length > maxOutputLines) {
  66. outputLines = outputLines.slice(outputLines.length - maxOutputLines)
  67. }
  68. if (options.task) {
  69. options.task.output = outputLines.join('\n')
  70. }
  71. }
  72. if (!options.disableOutput) {
  73. child.stdout.on('data', updateOutput)
  74. child.stderr.on('data', updateOutput)
  75. }
  76. child.on('error', reject)
  77. child.on('close', (code) => {
  78. if (remainingData) {
  79. outputLines.push(remainingData)
  80. }
  81. if (outputLines.length > maxOutputLines) {
  82. outputLines = outputLines.slice(outputLines.length - maxOutputLines)
  83. }
  84. if (options.task) {
  85. options.task.output = outputLines.join('\n')
  86. }
  87. if (code === 0) {
  88. resolve()
  89. }
  90. else {
  91. reject(
  92. new Error(
  93. `Command "${command} ${args.join(' ')}" exited with code ${code}\nOutput:\n${outputLines.join('\n')}\nENV: ${JSON.stringify(options.env)}`,
  94. ),
  95. )
  96. }
  97. })
  98. })
  99. }
  100. export async function extractArchive(
  101. archiveName: string,
  102. extractCommand: string[],
  103. options: { task?: { output?: string } } = {},
  104. ) {
  105. // TODO: extract zip using library
  106. await runCommand('bash', ['-c', `"${extractCommand.join(' && ')}"`], options)
  107. }
  108. export async function ensureDownloadedAndExtracted(
  109. filePath: string,
  110. downloadUrl: string,
  111. archiveName: string,
  112. extractCommand: string[],
  113. options: { task?: { output?: string } } = {},
  114. ): Promise<void> {
  115. if (!(await fileExistsAsync(filePath))) {
  116. await downloadFile(options.task, downloadUrl, archiveName)
  117. await extractArchive(archiveName, extractCommand, options)
  118. }
  119. }