Skip to content

Commit c99b669

Browse files
authored
Merge pull request #163 from hanihusam/fix/content-refresh-checkpoint
fix: make content refresh checkpoint self-healing
2 parents f112665 + c020355 commit c99b669

2 files changed

Lines changed: 100 additions & 73 deletions

File tree

other/refresh-changed-content.cjs

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
// try to keep this dep-free so we don't have to install deps
2-
const { fetchJson, getChangedFiles, postRefreshCache } = require('./utils.cjs')
2+
const {
3+
fetchJson,
4+
getChangedFiles,
5+
getTrackedContentFiles,
6+
hostname,
7+
postRefreshCache,
8+
} = require('./utils.cjs')
39

410
const [currentCommitSha] = process.argv.slice(2)
511

@@ -15,54 +21,53 @@ async function safeFetchJson(url) {
1521
}
1622
}
1723

24+
async function getContentPathsToRefresh(deployedSha, compareSha) {
25+
const filenames = compareSha
26+
? (await getChangedFiles(deployedSha, compareSha)).map(
27+
({ filename }) => filename,
28+
)
29+
: getTrackedContentFiles()
30+
31+
return filenames
32+
.filter((filename) => filename.startsWith('contents/'))
33+
.map((filename) => filename.replace(/^contents\//, ''))
34+
}
35+
1836
async function go() {
37+
if (!currentCommitSha) throw new Error('The deployed commit SHA is required')
38+
1939
const shaInfo = await safeFetchJson(
20-
'https://hanihusam-com.fly.dev/refresh-commit-sha.json',
40+
`https://${hostname}/refresh-commit-sha.json`,
2141
)
22-
let compareSha = shaInfo?.sha
23-
if (!compareSha) {
24-
// Static build info is served from the build/client root, so it lives at
25-
// /build/info.json (not /build/client/build/info.json). Shape is
26-
// { buildTime, commit: { sha, ... } }.
27-
const buildInfo = await safeFetchJson(
28-
'https://hanihusam-com.fly.dev/build/info.json',
29-
)
30-
compareSha = buildInfo?.commit?.sha
31-
if (compareSha) {
32-
console.log(`No compare sha found, using build sha: ${compareSha}`)
33-
}
34-
}
35-
if (typeof compareSha !== 'string') {
36-
console.log('🤷‍♂️ No sha to compare to. Nothing to refresh.')
37-
return
42+
const compareSha = shaInfo?.sha
43+
const contentPaths = await getContentPathsToRefresh(
44+
currentCommitSha,
45+
compareSha,
46+
)
47+
48+
if (compareSha) {
49+
console.log(`Comparing deployed content with ${compareSha}.`)
50+
} else {
51+
console.log('No refresh checkpoint found. Refreshing all tracked content.')
3852
}
3953

40-
const changedFiles =
41-
(await getChangedFiles(currentCommitSha, compareSha)) ?? []
42-
const contentPaths = changedFiles
43-
.filter((f) => f.filename.startsWith('contents'))
44-
.map((f) => f.filename.replace(/^contents\//, ''))
45-
if (contentPaths.length) {
46-
console.log(`⚡️ Content changed. Requesting the cache be refreshed.`, {
47-
currentCommitSha,
48-
compareSha,
54+
const response = await postRefreshCache({
55+
postData: {
4956
contentPaths,
50-
})
57+
commitSha: currentCommitSha,
58+
},
59+
})
60+
console.log('Content refresh checkpoint updated.', {
61+
contentPaths,
62+
response,
63+
})
64+
}
5165

52-
try {
53-
const response = await postRefreshCache({
54-
postData: {
55-
contentPaths,
56-
commitSha: currentCommitSha,
57-
},
58-
})
59-
console.log(`Content change request finished.`, { response })
60-
} catch (error) {
61-
console.log(`Error`, { error })
62-
}
63-
} else {
64-
console.log('🆗 Not refreshing changed content because no content changed.')
65-
}
66+
if (require.main === module) {
67+
void go().catch((error) => {
68+
console.error('Content refresh failed.', error)
69+
process.exitCode = 1
70+
})
6671
}
6772

68-
void go()
73+
module.exports = { getContentPathsToRefresh, go }

other/utils.cjs

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// try to keep this dep-free so we don't have to install deps
2-
const { execSync } = require('child_process')
2+
const { execFileSync } = require('child_process')
33
const https = require('https')
44

5+
const hostname =
6+
process.env.GITHUB_REF_NAME === 'dev'
7+
? 'hanihusam-com-staging.fly.dev'
8+
: 'hanihusam.com'
9+
510
function fetchJson(url, { timeoutTime } = {}) {
611
return new Promise((resolve, reject) => {
712
const request = https
@@ -12,6 +17,10 @@ function fetchJson(url, { timeoutTime } = {}) {
1217
})
1318

1419
res.on('end', () => {
20+
if (res.statusCode && res.statusCode >= 400) {
21+
reject(new Error(`Request failed with status ${res.statusCode}`))
22+
return
23+
}
1524
try {
1625
resolve(JSON.parse(data))
1726
} catch (error) {
@@ -23,9 +32,9 @@ function fetchJson(url, { timeoutTime } = {}) {
2332
reject(e)
2433
})
2534
if (timeoutTime) {
26-
setTimeout(() => {
35+
request.setTimeout(timeoutTime, () => {
2736
request.destroy(new Error('Request timed out'))
28-
}, timeoutTime)
37+
})
2938
}
3039
})
3140
}
@@ -34,39 +43,39 @@ const changeTypes = {
3443
M: 'modified',
3544
A: 'added',
3645
D: 'deleted',
37-
R: 'moved',
3846
}
3947

4048
async function getChangedFiles(currentCommitSha, compareCommitSha) {
41-
try {
42-
const lineParser = /^(?<change>\w).*?\s+(?<filename>.+$)/
43-
const gitOutput = execSync(
44-
`git diff --name-status ${currentCommitSha} ${compareCommitSha}`,
45-
).toString()
46-
const changedFiles = gitOutput
47-
.split('\n')
48-
.map((line) => line.match(lineParser)?.groups)
49-
.filter(Boolean)
50-
const changes = []
51-
for (const { change, filename } of changedFiles) {
52-
const changeType = changeTypes[change]
53-
if (changeType) {
54-
changes.push({ changeType: changeTypes[change], filename })
55-
} else {
56-
console.error(`Unknown change type: ${change} ${filename}`)
57-
}
49+
const lineParser = /^(?<change>\w).*?\s+(?<filename>.+$)/
50+
const gitOutput = execFileSync('/usr/bin/git', [
51+
'diff',
52+
'--name-status',
53+
'--no-renames',
54+
currentCommitSha,
55+
compareCommitSha,
56+
]).toString()
57+
const changedFiles = gitOutput
58+
.split('\n')
59+
.map((line) => line.match(lineParser)?.groups)
60+
.filter(Boolean)
61+
const changes = []
62+
for (const { change, filename } of changedFiles) {
63+
const changeType = changeTypes[change]
64+
if (changeType) {
65+
changes.push({ changeType, filename })
66+
} else {
67+
throw new Error(`Unknown change type: ${change} ${filename}`)
5868
}
59-
return changes
60-
} catch (error) {
61-
console.error(`Something went wrong trying to get changed files.`, error)
62-
return null
6369
}
70+
return changes
6471
}
6572

66-
const hostname =
67-
process.env.GITHUB_REF_NAME === 'dev'
68-
? 'hanihusam-com.fly.dev'
69-
: 'hanihusam.com'
73+
function getTrackedContentFiles() {
74+
return execFileSync('/usr/bin/git', ['ls-files', 'contents'])
75+
.toString()
76+
.split('\n')
77+
.filter(Boolean)
78+
}
7079

7180
// try to keep this dep-free so we don't have to install deps
7281
async function postRefreshCache({
@@ -102,6 +111,10 @@ async function postRefreshCache({
102111
})
103112

104113
res.on('end', () => {
114+
if (res.statusCode && res.statusCode >= 400) {
115+
reject(new Error(`Refresh failed with status ${res.statusCode}`))
116+
return
117+
}
105118
try {
106119
resolve(JSON.parse(data))
107120
} catch {
@@ -110,6 +123,9 @@ async function postRefreshCache({
110123
})
111124
})
112125
.on('error', reject)
126+
req.setTimeout(30_000, () => {
127+
req.destroy(new Error('Refresh request timed out'))
128+
})
113129
req.write(postDataString)
114130
req.end()
115131
} catch (error) {
@@ -119,4 +135,10 @@ async function postRefreshCache({
119135
})
120136
}
121137

122-
module.exports = { fetchJson, getChangedFiles, postRefreshCache }
138+
module.exports = {
139+
fetchJson,
140+
getChangedFiles,
141+
getTrackedContentFiles,
142+
hostname,
143+
postRefreshCache,
144+
}

0 commit comments

Comments
 (0)