-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
149 lines (146 loc) · 3.95 KB
/
index.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
146
147
148
149
const fs = require('fs')
const path = require('path')
const stream = require('stream')
const ftp = require('basic-ftp')
const glob = require('./lib/glob')
const client = new ftp.Client()
module.exports = async (opts) => {
try {
// state
const stateFile = `.ftpup${opts.scope ? `~${opts.scope}` : ''}`
const state = {
local: new Map,
}
// open connection
if (opts.test) {
console.log('> TEST MODE')
}
await client.access({
port: opts.port,
host: opts.host,
user: opts.username,
password: opts.password,
secure: opts.secure === true,
secureOptions: {
rejectUnauthorized: opts.allowUnauthorized !== true,
},
})
console.log('> connected')
// directories
const localDir = path.join(process.cwd(), opts.localDir || '.')
const remoteDir = path.join('/', opts.remoteDir || '.')
// remote state
if (!opts.fresh) {
try {
await client.cd(remoteDir)
const ws = new WS
await client.downloadTo(ws, stateFile)
state.remote = new Map(ws.getJSON())
} catch (_err) {}
}
if (!state.remote) {
state.remote = new Map()
}
state.toRemove = new Set(state.remote.keys())
// upload files
const files = await glob({
cwd: localDir,
exclude: opts.exclude,
})
for (const file of files) {
const ff = path.join(localDir, file)
const st = fs.lstatSync(ff)
state.local.set(file, st.mtimeMs)
state.toRemove.delete(file)
if (st.isFile()) {
if (st.mtimeMs <= state.remote.get(file)) continue
console.log('+', 'F', file)
if (opts.test) continue
const dir = path.dirname(file)
if (dir !== state.dir) {
await client.cd(path.join(remoteDir, dir))
state.dir = dir
}
await client.uploadFrom(ff, path.basename(file))
} else if (st.isDirectory()) {
if (state.remote.has(file)) continue
console.log('+', 'D', file)
if (opts.test) continue
await client.cd(remoteDir)
await client.ensureDir(file)
state.dir = file
}
}
// remove files
if (!opts.test) {
await client.cd(remoteDir)
}
for (const file of state.toRemove) {
console.log('-', 'F', file)
if (!opts.test) {
await client.remove(file).catch(err => {
if (err.code === 550) return
throw err
})
}
}
// remove directories
if (!opts.test) {
await client.cd(remoteDir)
}
const toRemove = new Set(
Array.from(state.toRemove)
.sort((a, b) => b.split('/').length - a.split('/').length)
)
for (const dir of toRemove) {
if (opts.test || dir === '.') continue
if (state.local.has(dir)) continue
const list = await client.list(dir).catch(err => {
if (err.code === 450 || err.code === 550) return
throw err
})
if (list === undefined || list.length) continue
console.log('-', 'D', dir)
await client.removeDir(path.join(remoteDir, dir))
toRemove.add(dir)
}
// purge directory
for (const dir of opts.purge) {
console.log('-', '*', path.join(dir, '*'))
if (!opts.test) {
await client.cd(path.join(remoteDir, dir)).then(() => (
client.clearWorkingDir()
)).catch(err => {
if (err.code === 550) return
throw err
})
}
}
if (!opts.test) {
await client.cd(remoteDir)
const rs = new stream.Readable
rs.push(JSON.stringify(Array.from(state.local)))
rs.push(null)
await client.uploadFrom(rs, stateFile)
}
// close connection
await client.close()
console.log('> done')
} catch (err) {
await client.close()
throw err
}
}
class WS extends stream.Writable {
str = ''
_write(chunk, encoding, cb) {
this.str += chunk.toString()
cb()
}
getString() {
return this.str
}
getJSON() {
return JSON.parse(this.str)
}
}