๐Ÿ“ฆ antongolub / git-glob-cp

๐Ÿ“„ index.js ยท 125 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125import { pipeline, Readable } from 'node:stream'
import { promisify } from 'node:util'
import { fs, tempy, $ as _$, fetch, copy as _copy } from 'zx-extra'
import * as tar from 'tar'
import { parse } from './parse.js'

export const copy = async (opts = {}, ...rest) => {
  // Legacy support for old signature
  if (typeof opts === 'string' || Array.isArray(opts)) {
    const [to, msg, ignoreFiles, cwd] = rest
    return copy({ from: opts, to, msg, ignoreFiles, cwd })
  }

  const { from, to, msg = 'chore: sync', ignoreFiles, cwd} = opts
  const { src, dst } = parseArgs(from, to, msg, ignoreFiles, cwd)

  if (dst.type === 'archive') throw new Error('archive as dest is not supported yet')

  if (src.type === 'archive') await unpackArchive(src)

  if (src.type === 'git') await gitFetch(src)

  if (dst.type === 'git') await gitFetch(dst, true)

  await _copy({
    baseFrom: src.base,
    from: src.pattern,
    baseTo: dst.base,
    to: dst.pattern,
    debug: _$.env.DEBUG ? console.log : () => {},
    ignoreFiles,
  })

  if (dst.type === 'git') await gitPush(dst, msg)
}

const parseArgs = (
  from,
  to,
  msg = 'chore: sync',
  ignoreFiles,
  cwd
) => {
  if (typeof from === 'object' && !Array.isArray(from) && from !== null) return parseArgs(from.from, from.to, from.msg, from.ignoreFiles, from.cwd)

  if (!from || !to) throw new Error('Both `from` and `to` arguments are required')

  const src = parse(from, {cwd, defaultPattern: '**/*'})
  const dst = parse(to, {cwd, defaultPattern: '.'})

  if (/[{}*,!]/.test(dst.pattern)) throw new Error('`dest` must not be a glob')

  return {src, dst, msg}
}

const unpackArchive = async (src) => {
  if (src.protocol !== 'local') src.file = await download(src.file)

  if (fs.statSync(src.file).isFile()) await tar.x({ file: src.file, cwd: src.base })
}

const gitFetch = async (src, nothrow) => {
  const $ = _$({
    cwd: src.base,
    verbose: !!_$.env.DEBUG,
  })

  try {
    await $`git clone --single-branch --branch ${src.branch} --depth 1 ${src.repo} .`
  } catch (e) {
    if (!nothrow) throw (e)
    console.warn(`ref ${src.branch} does not exist in ${src.repo}`)
    await $`git init`
    await $`git remote add origin ${src.repo}`
  }
}

const getGitConfig = async (name, cwd) => (await _$({nothrow: true, cwd})`git config ${name}`).stdout.trim()

const gitPush = async (dst, msg) => {
  const cwd = dst.base
  const $ = _$({
    cwd,
    verbose: !!_$.env.DEBUG,
  })

  const gitCommitterEmail = _$.env.GIT_COMMITTER_EMAIL || await getGitConfig('user.email', cwd) || 'semrel-extra-bot@hotmail.com'
  const gitCommitterName = _$.env.GIT_COMMITTER_NAME || await getGitConfig('user.name', cwd) || 'Semrel Extra Bot'

  try {
    await $`git config user.name ${gitCommitterName}`
    await $`git config user.email ${gitCommitterEmail}`
    await $`git add .`
    await $`git commit -m ${msg}`

  } catch {
    console.warn(`no changes to commit to ${dst.raw}`)
    return
  }

  await $`git push origin HEAD:refs/heads/${dst.branch}`
}

export const download = async (url, file = tempy.temporaryFile()) => {
  const res = await fetch(url)

  if (!res.ok)
    throw new Error(`Failed to fetch ${url}: ${res.statusText}`)

  const streamPipeline = promisify(pipeline)
  const fileStream = fs.createWriteStream(file)
  const body =
    typeof res.body.pipe === 'function'
      ? res.body
      : Readable.fromWeb(res.body)

  await streamPipeline(body, fileStream)

  return file
}

export const ggcp = copy

export const gitGlobCopy = copy