-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
operate.js
145 lines (137 loc) · 4.66 KB
/
operate.js
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const { spawn } = require('child_process')
const path = require('path')
const { randomBytes } = require('crypto')
const del = require('del')
const logger = require('pino')({ name: 'dat-ssg-operate' })
const { mkdir, writeFile, rename: _rename, access } = require('fs').promises
const { datPush, datInit, datKey } = require('./dat.js')
const { checkAbort, isAborted, listenAbort, AbortError } = require('./abort.js')
async function fileExists (pth, { signal } = {}) {
checkAbort(signal)
try {
await access(pth)
return true
} catch (_) {}
return false
}
async function rename (oldPath, newPath, { signal } = {}) {
checkAbort(signal)
return _rename(oldPath, newPath)
}
async function safeReplace (target, newPath, { signal, op } = {}) {
const backup = `${target}_${randomBytes(8).toString('hex')}`
logger.info('Safely replacing %s with %s [backup=%s]', target, newPath, backup)
let state = 'start'
const rewind = async () => {
if (state !== 'none') {
if (op !== undefined && state === 'op') {
await op.rewind(backup, target)
}
await del(target)
await rename(backup, target)
}
}
try {
await rename(target, backup, { signal })
state = 'replaced'
await rename(newPath, target, { signal })
if (op !== undefined) {
state = 'op'
await op.run(backup, target)
}
} catch (err) {
logger.error('Error while replacing: %o', err)
try {
await rewind()
} catch (_) {}
throw err
}
await del(backup)
}
function operate (cwd, { signal } = {}) {
const exec = (cmd, args = [], opts = {}) => new Promise((resolve, reject) => {
const child = spawn(cmd, args, Object.assign({}, { cwd }, opts))
const stdout = []
child.stdout.on('data', data => stdout.push(data))
const stderr = []
child.stderr.on('data', data => stderr.push(data))
const execError = (error) => {
const out = Buffer.concat(stdout).toString()
const err = Buffer.concat(stderr).toString()
return Object.assign(
new Error(`Error while running ${cmd} ${args.join(' ')}\nMessage: ${error.message}\nOut: ${out}\nError: ${err}`),
{ code: error.code, exitCode: error.exitCode, original: error.message, out, err }
)
}
let close = err => {
close = () => {}
unlisten()
if (err) {
// Even if it is aborted another error may have occured
// while shutting down, lets not eat this error and actually show it
return reject(err)
}
if (isAborted(signal)) {
return reject(new AbortError())
}
resolve({
get stdout () {
return Buffer.concat(stdout)
},
get stderr () {
return Buffer.concat(stderr)
}
})
}
// child.stdout.pipe(process.stdout, { end: false })
// child.stderr.pipe(process.stderr, { end: false })
child.on('error', close)
child.on('exit', exitCode => {
if (exitCode !== 0 && exitCode !== null) {
return close(execError({ exitCode, code: 'exit-error', message: `Returned error code ${exitCode}` }))
}
close()
})
const unlisten = listenAbort(signal, abortError => {
logger.info('Killing process with SIGINT on abort signal.')
child.kill('SIGINT')
})
})
return {
dirname: cwd,
cd: (...folders) => operate(path.join(cwd, ...folders), { signal }),
exec,
git: (...args) => exec('git', args, { signal }),
exists: (...folders) => fileExists(path.join(cwd, ...folders)),
mkdir: (...folders) => mkdir(path.join(cwd, ...folders), { recursive: true }),
download: paths => exec('wget', [
'--recursive',
'--adjust-extension',
'-e', 'robots=off',
'--no-proxy',
'--no-cache',
'--no-check-certificate',
'--page-requisites',
'--html-extension',
'--convert-links',
'--restrict-file-names=windows',
'--no-verbose'
].concat(paths), { signal }),
write: async (folders, data, opts) => {
checkAbort(signal)
return writeFile(path.join(cwd, ...folders), data, opts)
},
move: async (folders, target) => rename(path.join(cwd, ...folders), path.join(target, ...folders)),
deployNetlify: ({ authToken, production }) =>
exec(
require.resolve('netlify-cli/bin/run'),
['deploy', '--dir', cwd, '--auth', authToken, '--json', production && '--prod'].filter(Boolean),
{ signal }
),
datPush: (timeout, ...folders) => datPush(signal, cwd, timeout, ...folders),
datKey: (...folders) => datKey(cwd, ...folders),
datInit: (...folders) => datInit(signal, cwd, ...folders),
safeReplaceWith: (target, op) => safeReplace(cwd, target, { signal, op })
}
}
module.exports = operate