@@ -9,8 +9,15 @@ const collator = new Intl.Collator('en', { numeric: true }); // for sorting SemV
99
1010// Initialization work done only once.
1111wasm . init ( ) ;
12- const syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */ =
13- SyncGet . startWorker ( ) ;
12+ // Lazily start the worker until needed.
13+ // This is important for the tests, which never exit otherwise.
14+ let syncGetWorker_ /*: void | typeof SyncGet.SyncGetWorker */ = undefined ;
15+ function syncGetWorker ( ) /*: typeof SyncGet.SyncGetWorker */ {
16+ if ( syncGetWorker_ === undefined ) {
17+ syncGetWorker_ = SyncGet . startWorker ( ) ;
18+ }
19+ return syncGetWorker_ ;
20+ }
1421
1522// Cache of existing versions according to the package website.
1623class OnlineVersionsCache {
@@ -55,7 +62,7 @@ class OnlineVersionsCache {
5562
5663 // Complete cache with a remote call to the package server.
5764 const remoteUrl = remotePackagesUrl + '/since/' + ( versionsCount - 1 ) ; // -1 to check if no package was deleted.
58- const newVersions = JSON . parse ( syncGetWorker . get ( remoteUrl ) ) ;
65+ const newVersions = JSON . parse ( syncGetWorker ( ) . get ( remoteUrl ) ) ;
5966 if ( newVersions . length === 0 ) {
6067 // Reload from scratch since it means at least one package was deleted from the registry.
6168 this . map = onlineVersionsFromScratch ( cachePath , remotePackagesUrl ) ;
@@ -104,10 +111,13 @@ class OnlineAvailableVersionLister {
104111 this . onlineCache = onlineCache ;
105112 }
106113
107- list ( pkg /*: string */ ) /*: Array<string> */ {
114+ list (
115+ pkg /*: string */ ,
116+ pinnedVersion /*: void | string */
117+ ) /*: Array<string> */ {
108118 const memoVersions = this . memoCache . get ( pkg ) ;
109119 if ( memoVersions !== undefined ) {
110- return memoVersions ;
120+ return prioritizePinnedIndirectVersion ( memoVersions , pinnedVersion ) ;
111121 }
112122 const offlineVersions = readVersionsInElmHomeAndSort ( pkg ) ;
113123 const allVersionsSet = new Set ( this . onlineCache . getVersions ( pkg ) ) ;
@@ -117,24 +127,27 @@ class OnlineAvailableVersionLister {
117127 }
118128 const allVersions = [ ...allVersionsSet ] . sort ( flippedSemverCompare ) ;
119129 this . memoCache . set ( pkg , allVersions ) ;
120- return allVersions ;
130+ return prioritizePinnedIndirectVersion ( allVersions , pinnedVersion ) ;
121131 }
122132}
123133
124134class OfflineAvailableVersionLister {
125135 // Memoization cache to avoid doing the same work twice in list.
126136 cache /*: Map<string, Array<string>> */ = new Map ( ) ;
127137
128- list ( pkg /*: string */ ) /*: Array<string> */ {
138+ list (
139+ pkg /*: string */ ,
140+ pinnedVersion /*: void | string */
141+ ) /*: Array<string> */ {
129142 const memoVersions = this . cache . get ( pkg ) ;
130143 if ( memoVersions !== undefined ) {
131- return memoVersions ;
144+ return prioritizePinnedIndirectVersion ( memoVersions , pinnedVersion ) ;
132145 }
133146
134147 const offlineVersions = readVersionsInElmHomeAndSort ( pkg ) ;
135148
136149 this . cache . set ( pkg , offlineVersions ) ;
137- return offlineVersions ;
150+ return prioritizePinnedIndirectVersion ( offlineVersions , pinnedVersion ) ;
138151 }
139152}
140153
@@ -162,13 +175,21 @@ class DependencyProvider {
162175 extra /*: { [string]: string } */
163176 ) /*: string */ {
164177 const lister = new OfflineAvailableVersionLister ( ) ;
178+ const dependencies = JSON . parse ( elmJson ) . dependencies ;
179+ const indirectDeps =
180+ dependencies === undefined ? undefined : dependencies . indirect ;
181+
165182 try {
166183 return wasm . solve_deps (
167184 elmJson ,
168185 useTest ,
169186 extra ,
170187 fetchElmJsonOffline ,
171- ( pkg ) => lister . list ( pkg )
188+ ( pkg ) =>
189+ lister . list (
190+ pkg ,
191+ indirectDeps === undefined ? undefined : indirectDeps [ pkg ]
192+ )
172193 ) ;
173194 } catch ( errorMessage ) {
174195 throw new Error ( errorMessage ) ;
@@ -182,13 +203,21 @@ class DependencyProvider {
182203 extra /*: { [string]: string } */
183204 ) /*: string */ {
184205 const lister = new OnlineAvailableVersionLister ( this . cache ) ;
206+ const dependencies = JSON . parse ( elmJson ) . dependencies ;
207+ const indirectDeps =
208+ dependencies === undefined ? undefined : dependencies . indirect ;
209+
185210 try {
186211 return wasm . solve_deps (
187212 elmJson ,
188213 useTest ,
189214 extra ,
190215 fetchElmJsonOnline ,
191- ( pkg ) => lister . list ( pkg )
216+ ( pkg ) =>
217+ lister . list (
218+ pkg ,
219+ indirectDeps === undefined ? undefined : indirectDeps [ pkg ]
220+ )
192221 ) ;
193222 } catch ( errorMessage ) {
194223 throw new Error ( errorMessage ) ;
@@ -208,7 +237,7 @@ function fetchElmJsonOnline(
208237 // or because there was an error parsing `pkg` and `version`.
209238 // In such case, this will throw again with `cacheElmJsonPath()` so it's fine.
210239 const remoteUrl = remoteElmJsonUrl ( pkg , version ) ;
211- const elmJson = syncGetWorker . get ( remoteUrl ) ;
240+ const elmJson = syncGetWorker ( ) . get ( remoteUrl ) ;
212241 const cachePath = cacheElmJsonPath ( pkg , version ) ;
213242 const parentDir = path . dirname ( cachePath ) ;
214243 fs . mkdirSync ( parentDir , { recursive : true } ) ;
@@ -239,7 +268,7 @@ function onlineVersionsFromScratch(
239268 cachePath /*: string */ ,
240269 remotePackagesUrl /*: string */
241270) /*: Map<string, Array<string>> */ {
242- const onlineVersionsJson = syncGetWorker . get ( remotePackagesUrl ) ;
271+ const onlineVersionsJson = syncGetWorker ( ) . get ( remotePackagesUrl ) ;
243272 fs . writeFileSync ( cachePath , onlineVersionsJson ) ;
244273 const onlineVersions = JSON . parse ( onlineVersionsJson ) ;
245274 try {
@@ -253,6 +282,42 @@ function onlineVersionsFromScratch(
253282
254283// Helper functions ##################################################
255284
285+ /**
286+ * Enforces respecting pinned indirect dependencies.
287+ *
288+ * When Elm apps have pinned indirect versions, e.g.:
289+ *
290+ * "indirect": {
291+ * "elm/virtual-dom": "1.0.3"
292+ * }
293+ *
294+ * We must prioritize these versions for the wasm dependency solver.
295+ *
296+ * Otherwise the wasm solver will take liberties that will result in
297+ * tests running with dependency versions distinct from those used by
298+ * the real live application.
299+ *
300+ * Assumes versions is sorted descending (newest -> oldest).
301+ */
302+ function prioritizePinnedIndirectVersion (
303+ versions /*: Array<string> */ ,
304+ pinnedVersion /*: void | string */
305+ ) /*: Array<string> */ {
306+ if ( pinnedVersion === undefined || ! versions . includes ( pinnedVersion ) ) {
307+ return versions ;
308+ }
309+
310+ // the pinned version and any newer version, in ascending order
311+ const desirableVersions = versions
312+ . filter ( ( v ) => v >= pinnedVersion )
313+ . reverse ( ) ;
314+
315+ // older versions, in descending order
316+ const olderVersions = versions . filter ( ( v ) => v < pinnedVersion ) ;
317+
318+ return desirableVersions . concat ( olderVersions ) ;
319+ }
320+
256321/* Compares two versions so that newer versions appear first when sorting with this function. */
257322function flippedSemverCompare ( a /*: string */ , b /*: string */ ) /*: number */ {
258323 return collator . compare ( b , a ) ;
@@ -340,4 +405,7 @@ function splitPkgVersion(str /*: string */) /*: {
340405 return { pkg : parts [ 0 ] , version : parts [ 1 ] } ;
341406}
342407
343- module . exports = DependencyProvider ;
408+ module . exports = {
409+ DependencyProvider,
410+ prioritizePinnedIndirectVersion,
411+ } ;
0 commit comments