@rpcbase/cli 0.119.0 → 0.121.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import crypto from "crypto"
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import os from "os"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import { execSync } from "child_process"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const baseExcludeList = [
|
|
9
|
+
".husky/",
|
|
10
|
+
".github/",
|
|
11
|
+
".git/",
|
|
12
|
+
".wireit/",
|
|
13
|
+
"coverage/",
|
|
14
|
+
"node_modules/",
|
|
15
|
+
"infrastructure/data/",
|
|
16
|
+
".gitignore",
|
|
17
|
+
"*.css.map",
|
|
18
|
+
// "*.env*",
|
|
19
|
+
// "*.js.map",
|
|
20
|
+
"*.md",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const hashTree = (rootDir) => {
|
|
24
|
+
const fileHashes = {}
|
|
25
|
+
|
|
26
|
+
const walk = (dir) => {
|
|
27
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
28
|
+
const fullPath = path.join(dir, entry.name)
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
walk(fullPath)
|
|
31
|
+
} else {
|
|
32
|
+
const rel = path.relative(rootDir, fullPath)
|
|
33
|
+
const content = fs.readFileSync(fullPath)
|
|
34
|
+
const digest = crypto.createHash("sha256").update(content).digest("hex")
|
|
35
|
+
fileHashes[rel] = digest
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
walk(rootDir)
|
|
41
|
+
|
|
42
|
+
const aggregate = crypto.createHash("sha256")
|
|
43
|
+
Object.keys(fileHashes).sort().forEach((rel) => {
|
|
44
|
+
aggregate.update(rel)
|
|
45
|
+
aggregate.update("\0")
|
|
46
|
+
aggregate.update(fileHashes[rel])
|
|
47
|
+
aggregate.update("\0")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
treeHash: aggregate.digest("hex"),
|
|
52
|
+
fileHashes,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const buildRsyncCmd = ({ includeArgs, excludeArgs, keyPath, source, dest, remote }) => {
|
|
57
|
+
const parts = [
|
|
58
|
+
"rsync -az --delete --filter=':- .gitignore'",
|
|
59
|
+
includeArgs.join(" "),
|
|
60
|
+
excludeArgs,
|
|
61
|
+
remote ? `-e "ssh -i '${keyPath}' -o StrictHostKeyChecking=no"` : null,
|
|
62
|
+
source,
|
|
63
|
+
dest,
|
|
64
|
+
].filter(Boolean)
|
|
65
|
+
|
|
66
|
+
return parts.join(" ")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const detectComposeChanges = ({ keyPath, user, host, deployDir, log, extraExcludes = [] }) => {
|
|
70
|
+
const includeArgs = ["--include='*/'", "--include='*compose*.yml'", "--exclude='*'"]
|
|
71
|
+
const excludeArgs = baseExcludeList
|
|
72
|
+
.concat(extraExcludes)
|
|
73
|
+
.map((p) => `--exclude='${p}'`)
|
|
74
|
+
.join(" ")
|
|
75
|
+
|
|
76
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "rb-compose-"))
|
|
77
|
+
const remoteDir = path.join(tempRoot, "remote")
|
|
78
|
+
const localDir = path.join(tempRoot, "local")
|
|
79
|
+
fs.mkdirSync(remoteDir)
|
|
80
|
+
fs.mkdirSync(localDir)
|
|
81
|
+
|
|
82
|
+
const cleanup = () => {
|
|
83
|
+
try {
|
|
84
|
+
fs.rmSync(tempRoot, { recursive: true, force: true })
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.warn(`Cleanup failed for ${tempRoot}: ${err}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
// fetch remote compose files into temp dir
|
|
92
|
+
const remoteSyncCmd = buildRsyncCmd({
|
|
93
|
+
includeArgs,
|
|
94
|
+
excludeArgs,
|
|
95
|
+
keyPath,
|
|
96
|
+
source: `${user}@${host}:~/apps/${deployDir}/`,
|
|
97
|
+
dest: `${remoteDir}/`,
|
|
98
|
+
remote: true,
|
|
99
|
+
})
|
|
100
|
+
log(`Syncing remote compose files: ${remoteSyncCmd}`)
|
|
101
|
+
execSync(remoteSyncCmd, { stdio: "ignore" })
|
|
102
|
+
|
|
103
|
+
// copy local compose files into temp dir (same filters to keep parity)
|
|
104
|
+
const localSyncCmd = buildRsyncCmd({
|
|
105
|
+
includeArgs,
|
|
106
|
+
excludeArgs,
|
|
107
|
+
keyPath,
|
|
108
|
+
source: "./",
|
|
109
|
+
dest: `${localDir}/`,
|
|
110
|
+
remote: false,
|
|
111
|
+
})
|
|
112
|
+
log(`Syncing local compose files: ${localSyncCmd}`)
|
|
113
|
+
execSync(localSyncCmd, { stdio: "ignore" })
|
|
114
|
+
|
|
115
|
+
const remoteHashes = hashTree(remoteDir)
|
|
116
|
+
const localHashes = hashTree(localDir)
|
|
117
|
+
|
|
118
|
+
const remotePaths = new Set(Object.keys(remoteHashes.fileHashes))
|
|
119
|
+
const localPaths = new Set(Object.keys(localHashes.fileHashes))
|
|
120
|
+
|
|
121
|
+
const missingLocal = [...remotePaths].filter((p) => !localPaths.has(p))
|
|
122
|
+
const missingRemote = [...localPaths].filter((p) => !remotePaths.has(p))
|
|
123
|
+
const changed = [...remotePaths].filter(
|
|
124
|
+
(p) => localPaths.has(p) && remoteHashes.fileHashes[p] !== localHashes.fileHashes[p],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const hasChanges = (
|
|
128
|
+
remoteHashes.treeHash !== localHashes.treeHash ||
|
|
129
|
+
missingLocal.length > 0 ||
|
|
130
|
+
missingRemote.length > 0 ||
|
|
131
|
+
changed.length > 0
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const summary = [
|
|
135
|
+
`local tree: ${localHashes.treeHash}`,
|
|
136
|
+
`remote tree: ${remoteHashes.treeHash}`,
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
if (missingLocal.length) summary.push(`Only on remote: ${missingLocal.join(", ")}`)
|
|
140
|
+
if (missingRemote.length) summary.push(`Only on local: ${missingRemote.join(", ")}`)
|
|
141
|
+
if (changed.length) summary.push(`Content differs: ${changed.join(", ")}`)
|
|
142
|
+
if (!hasChanges) summary.push("Compose files are identical.")
|
|
143
|
+
|
|
144
|
+
return { hasChanges, output: summary.join("\n") }
|
|
145
|
+
} finally {
|
|
146
|
+
cleanup()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -1,38 +1,12 @@
|
|
|
1
1
|
import { execSync } from "child_process"
|
|
2
2
|
import fs from "fs"
|
|
3
|
-
import path from "path"
|
|
4
3
|
import os from "os"
|
|
4
|
+
import path from "path"
|
|
5
5
|
|
|
6
6
|
import validator from "validator"
|
|
7
7
|
|
|
8
|
-
import { purgeCloudflareCaches } from "./cloudflare
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const detectComposeChanges = ({ keyPath, user, host, deployDir, log }) => {
|
|
12
|
-
const includeArgs = ["--include='*/'", "--include='*compose*.yml'", "--exclude='*'"]
|
|
13
|
-
|
|
14
|
-
const rsyncCmd = [
|
|
15
|
-
"rsync -avzn --delete --itemize-changes",
|
|
16
|
-
includeArgs.join(" "),
|
|
17
|
-
`-e "ssh -i '${keyPath}' -o StrictHostKeyChecking=no"`,
|
|
18
|
-
".",
|
|
19
|
-
`${user}@${host}:~/apps/${deployDir}/`,
|
|
20
|
-
].join(" ")
|
|
21
|
-
|
|
22
|
-
log(`Checking compose files (dry-run): ${rsyncCmd}`)
|
|
23
|
-
|
|
24
|
-
const out = execSync(rsyncCmd, { stdio: "pipe" }).toString()
|
|
25
|
-
|
|
26
|
-
// rsync --itemize-changes prefixes change lines; any line that isn't a summary indicates a diff
|
|
27
|
-
const changeLines = out
|
|
28
|
-
.split("\n")
|
|
29
|
-
.map((line) => line.trim())
|
|
30
|
-
.filter((line) => line && !line.startsWith("sending ") && !line.startsWith("sent ") && !line.startsWith("total size"))
|
|
31
|
-
|
|
32
|
-
const hasChanges = changeLines.length > 0
|
|
33
|
-
|
|
34
|
-
return { hasChanges, output: out }
|
|
35
|
-
}
|
|
8
|
+
import { purgeCloudflareCaches } from "./purge-cloudflare.js"
|
|
9
|
+
import { baseExcludeList, detectComposeChanges } from "./detect-compose-changes.js"
|
|
36
10
|
|
|
37
11
|
|
|
38
12
|
export const deploy = async (argv) => {
|
|
@@ -114,28 +88,21 @@ export const deploy = async (argv) => {
|
|
|
114
88
|
console.log(`${user}@${host}`, res)
|
|
115
89
|
await ssh(`mkdir -p ~/apps/${deployDir}`)
|
|
116
90
|
|
|
117
|
-
const { hasChanges: infrastructureChanged, output: infrastructureDiff } = detectComposeChanges({
|
|
91
|
+
const { hasChanges: infrastructureChanged, output: infrastructureDiff } = detectComposeChanges({
|
|
92
|
+
keyPath,
|
|
93
|
+
user,
|
|
94
|
+
host,
|
|
95
|
+
deployDir,
|
|
96
|
+
log,
|
|
97
|
+
extraExcludes: argv.ignore || [],
|
|
98
|
+
})
|
|
118
99
|
|
|
119
100
|
if (infrastructureChanged) {
|
|
120
101
|
console.log("Infrastructure compose files differ between local and remote (info only; deploy proceeds).")
|
|
121
102
|
if (argv.verbose) console.log(infrastructureDiff)
|
|
122
103
|
}
|
|
123
104
|
|
|
124
|
-
const excludeList =
|
|
125
|
-
".husky/",
|
|
126
|
-
".github/",
|
|
127
|
-
".git/",
|
|
128
|
-
".wireit/",
|
|
129
|
-
"coverage/",
|
|
130
|
-
"node_modules/",
|
|
131
|
-
"infrastructure/data/",
|
|
132
|
-
".gitignore",
|
|
133
|
-
"*.css.map",
|
|
134
|
-
// "*.env*",
|
|
135
|
-
// "*.js.map",
|
|
136
|
-
"*.md",
|
|
137
|
-
].concat(argv.ignore)
|
|
138
|
-
|
|
105
|
+
const excludeList = baseExcludeList.concat(argv.ignore)
|
|
139
106
|
|
|
140
107
|
const excludeArgs = excludeList.map((p) => `--exclude='${p}'`).join(" ")
|
|
141
108
|
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { hideBin } from "yargs/helpers"
|
|
|
7
7
|
import dotenv from "dotenv"
|
|
8
8
|
import { expand } from "dotenv-expand"
|
|
9
9
|
|
|
10
|
-
import { deploy } from "./cmd-deploy.js"
|
|
10
|
+
import { deploy } from "./cmd-deploy/index.js"
|
|
11
11
|
import { ssh } from "./cmd-ssh.js"
|
|
12
12
|
import { waitFor } from "./cmd-wait-for/index.js"
|
|
13
13
|
|
|
File without changes
|