1
+ // @ts -check
1
2
import { makePromiseKit } from '@endo/promise-kit' ;
2
3
import { makeReadPowers } from '@endo/compartment-mapper/node-powers.js' ;
3
4
4
5
import bundleSource from './src/index.js' ;
5
6
6
7
const { Fail, quote : q } = assert ;
7
8
9
+ /**
10
+ * @typedef {(...args: unknown[]) => void } Logger A message logger.
11
+ */
8
12
9
13
/**
10
14
* @typedef {object } BundleMeta
@@ -14,22 +18,53 @@ const { Fail, quote: q } = assert;
14
18
* @property {Array<{ relativePath: string, mtime: string }> } contents
15
19
*/
16
20
21
+ /**
22
+ * @param {string } fileName
23
+ * @param {{
24
+ * fs: {
25
+ * promises: Pick<import('fs/promises'),'readFile' | 'stat'>
26
+ * },
27
+ * path: Pick<import('path'), 'resolve' | 'relative' | 'normalize'>,
28
+ * }} powers
29
+ */
17
30
export const makeFileReader = ( fileName , { fs, path } ) => {
18
31
const make = there => makeFileReader ( there , { fs, path } ) ;
32
+
33
+ // fs.promises.exists isn't implemented in Node.js apparently because it's pure
34
+ // sugar.
35
+ const exists = fn =>
36
+ fs . promises . stat ( fn ) . then (
37
+ ( ) => true ,
38
+ e => {
39
+ if ( e . code === 'ENOENT' ) {
40
+ return false ;
41
+ }
42
+ throw e ;
43
+ } ,
44
+ ) ;
45
+
19
46
return harden ( {
20
47
toString : ( ) => fileName ,
21
48
readText : ( ) => fs . promises . readFile ( fileName , 'utf-8' ) ,
22
49
neighbor : ref => make ( path . resolve ( fileName , ref ) ) ,
23
50
stat : ( ) => fs . promises . stat ( fileName ) ,
24
51
absolute : ( ) => path . normalize ( fileName ) ,
25
52
relative : there => path . relative ( fileName , there ) ,
26
- exists : ( ) => fs . existsSync ( fileName ) ,
53
+ exists : ( ) => exists ( fileName ) ,
27
54
} ) ;
28
55
} ;
29
56
30
57
/**
31
58
* @param {string } fileName
32
- * @param {{ fs: import('fs'), path: import('path') } } io
59
+ * @param {{
60
+ * fs: Pick<import('fs'), 'existsSync'> &
61
+ * { promises: Pick<
62
+ * import('fs/promises'),
63
+ * 'readFile' | 'stat' | 'writeFile' | 'mkdir' | 'rm'
64
+ * >,
65
+ * },
66
+ * path: Pick<import('path'), 'resolve' | 'relative' | 'normalize'>,
67
+ * }} io
33
68
*/
34
69
export const makeFileWriter = ( fileName , { fs, path } ) => {
35
70
const make = there => makeFileWriter ( there , { fs, path } ) ;
@@ -101,7 +136,13 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
101
136
if ( oops . code !== 'EEXIST' ) {
102
137
throw oops ;
103
138
}
104
- // The lock exists, so something is already writing the bundle.
139
+
140
+ // The lock exists, so something is already writing the bundle on our
141
+ // behalf.
142
+ //
143
+ // All we need to do is try validating the bundle, which will first wait
144
+ // for the lock to disappear before reading the freshly-written bundle.
145
+
105
146
// eslint-disable-next-line no-use-before-define
106
147
return validate ( targetName , rootPath ) ;
107
148
}
@@ -209,12 +250,16 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
209
250
/**
210
251
* @param {string } rootPath
211
252
* @param {string } targetName
212
- * @param {(...args: any[]) => void } [log]
253
+ * @param {Logger } [log]
213
254
* @returns {Promise<BundleMeta> }
214
255
*/
215
256
const validateOrAdd = async ( rootPath , targetName , log = defaultLog ) => {
216
257
let meta ;
217
- if ( wr . readOnly ( ) . neighbor ( toBundleMeta ( targetName ) ) . exists ( ) ) {
258
+ const metaExists = await wr
259
+ . readOnly ( )
260
+ . neighbor ( toBundleMeta ( targetName ) )
261
+ . exists ( ) ;
262
+ if ( metaExists ) {
218
263
try {
219
264
meta = await validate ( targetName , rootPath , log ) ;
220
265
const { bundleTime, contents } = meta ;
@@ -251,7 +296,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
251
296
/**
252
297
* @param {string } rootPath
253
298
* @param {string } [targetName]
254
- * @param {(...args: any[]) => void } [log]
299
+ * @param {Logger } [log]
255
300
*/
256
301
const load = async (
257
302
rootPath ,
0 commit comments