Skip to content

Commit 0487b67

Browse files
committed
Add additional handling for experimental tracing
1 parent cb7769a commit 0487b67

File tree

18 files changed

+1184
-169
lines changed

18 files changed

+1184
-169
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import crypto from 'crypto'
4+
import { getPageFromPath } from '../entries'
5+
import { Sema } from 'next/dist/compiled/async-sema'
6+
7+
export interface DetectedEntriesResult {
8+
app: string[]
9+
pages: string[]
10+
}
11+
12+
let _hasShuttle: undefined | boolean = undefined
13+
export async function hasShuttle(shuttleDir: string) {
14+
if (typeof _hasShuttle === 'boolean') {
15+
return _hasShuttle
16+
}
17+
_hasShuttle = await fs.promises
18+
.access(path.join(shuttleDir, 'server'))
19+
.then(() => true)
20+
.catch(() => false)
21+
22+
return _hasShuttle
23+
}
24+
25+
export async function detectChangedEntries({
26+
appPaths,
27+
pagesPaths,
28+
pageExtensions,
29+
distDir,
30+
shuttleDir,
31+
}: {
32+
appPaths?: string[]
33+
pagesPaths?: string[]
34+
pageExtensions: string[]
35+
distDir: string
36+
shuttleDir: string
37+
}): Promise<{
38+
changed: DetectedEntriesResult
39+
unchanged: DetectedEntriesResult
40+
}> {
41+
const changedEntries: {
42+
app: string[]
43+
pages: string[]
44+
} = {
45+
app: [],
46+
pages: [],
47+
}
48+
const unchangedEntries: typeof changedEntries = {
49+
app: [],
50+
pages: [],
51+
}
52+
53+
if (!(await hasShuttle(shuttleDir))) {
54+
// no shuttle so consider everything changed
55+
console.log('no shuttle can not detect changes')
56+
return {
57+
changed: {
58+
pages: pagesPaths || [],
59+
app: appPaths || [],
60+
},
61+
unchanged: {
62+
pages: [],
63+
app: [],
64+
},
65+
}
66+
}
67+
68+
const hashCache = new Map<string, string>()
69+
70+
async function computeHash(p: string): Promise<string> {
71+
let hash = hashCache.get(p)
72+
if (hash) {
73+
return hash
74+
}
75+
return new Promise((resolve, reject) => {
76+
const hashInst = crypto.createHash('sha1')
77+
const stream = fs.createReadStream(p)
78+
stream.on('error', (err) => reject(err))
79+
stream.on('data', (chunk) => hashInst.update(chunk))
80+
stream.on('end', () => {
81+
const digest = hashInst.digest('hex')
82+
resolve(digest)
83+
hashCache.set(p, digest)
84+
})
85+
})
86+
}
87+
88+
const hashSema = new Sema(16)
89+
90+
async function detectChange({
91+
normalizedEntry,
92+
entry,
93+
type,
94+
}: {
95+
entry: string
96+
normalizedEntry: string
97+
type: keyof typeof changedEntries
98+
}) {
99+
const traceFile = path.join(
100+
shuttleDir,
101+
'server',
102+
type,
103+
`${normalizedEntry}.js.nft.json`
104+
)
105+
106+
const traceData:
107+
| false
108+
| {
109+
fileHashes: Record<string, string>
110+
} = JSON.parse(
111+
await fs.promises
112+
.readFile(traceFile, 'utf8')
113+
.catch(() => JSON.stringify(false))
114+
)
115+
let changed = false
116+
117+
if (traceData) {
118+
await Promise.all(
119+
Object.keys(traceData.fileHashes).map(async (file) => {
120+
if (changed) return
121+
try {
122+
await hashSema.acquire()
123+
const originalTraceFile = path.join(
124+
distDir,
125+
'server',
126+
type,
127+
path.relative(path.join(shuttleDir, 'server', type), traceFile)
128+
)
129+
const absoluteFile = path.join(
130+
path.dirname(originalTraceFile),
131+
file
132+
)
133+
134+
if (absoluteFile.startsWith(distDir)) {
135+
return
136+
}
137+
138+
const prevHash = traceData.fileHashes[file]
139+
const curHash = await computeHash(absoluteFile)
140+
141+
if (prevHash !== curHash) {
142+
console.error('detected change on', {
143+
prevHash,
144+
curHash,
145+
file,
146+
entry: normalizedEntry,
147+
})
148+
changed = true
149+
}
150+
} finally {
151+
hashSema.release()
152+
}
153+
})
154+
)
155+
} else {
156+
console.error('missing trace data', traceFile, normalizedEntry)
157+
changed = true
158+
}
159+
160+
if (changed || entry.match(/(_app|_document|_error)/)) {
161+
changedEntries[type].push(entry)
162+
} else {
163+
unchangedEntries[type].push(entry)
164+
}
165+
}
166+
167+
// collect page entries with default page extensions
168+
console.error(
169+
JSON.stringify(
170+
{
171+
appPaths,
172+
pagePaths: pagesPaths,
173+
},
174+
null,
175+
2
176+
)
177+
)
178+
// loop over entries and their dependency's hashes to find
179+
// which changed
180+
181+
// TODO: if _app or _document change it invalidates all pages
182+
for (const entry of pagesPaths || []) {
183+
let normalizedEntry = getPageFromPath(entry, pageExtensions)
184+
185+
if (normalizedEntry === '/') {
186+
normalizedEntry = '/index'
187+
}
188+
189+
await detectChange({ entry, normalizedEntry, type: 'pages' })
190+
}
191+
192+
for (const entry of appPaths || []) {
193+
const normalizedEntry = getPageFromPath(entry, pageExtensions)
194+
await detectChange({ entry, normalizedEntry, type: 'app' })
195+
}
196+
197+
console.error(
198+
'changed entries',
199+
JSON.stringify(
200+
{
201+
changedEntries,
202+
unchangedEntries,
203+
},
204+
null,
205+
2
206+
)
207+
)
208+
209+
return {
210+
changed: changedEntries,
211+
unchanged: unchangedEntries,
212+
}
213+
}

0 commit comments

Comments
 (0)