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