From 28c5a5abfb4cc745cf869af43de3b8a24aa5e761 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 23:06:05 +0200 Subject: [PATCH 01/98] Add --offline flag --- lib/flags.js | 5 +++++ lib/options.js | 1 + lib/project-json-files.js | 13 +++++++++---- lib/types/options.d.ts | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/flags.js b/lib/flags.js index 0f8c996c4..8b1bc9201 100644 --- a/lib/flags.js +++ b/lib/flags.js @@ -380,6 +380,11 @@ const flags = [ boolean: true, sections: null }, + { + name: 'offline', + boolean: true, + sections: null + }, gitHubAuthFlag, { name: 'namespace', diff --git a/lib/options.js b/lib/options.js index fe8cf324b..07f713baf 100644 --- a/lib/options.js +++ b/lib/options.js @@ -199,6 +199,7 @@ try re-running it with ${chalk.cyan('--elmjson ')}.`, packageJsonVersion: packageJson.version, localElmReviewSrc, forceBuild: args['force-build'], + offline: args.offline, report: args.report === 'json' || args.report === 'ndjson' ? 'json' : null, reportOnOneLine: args.report === 'ndjson', rulesFilter: listOfStrings(args.rules), diff --git a/lib/project-json-files.js b/lib/project-json-files.js index e083f87e6..4655fb713 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -35,15 +35,20 @@ function getElmJson(options, elmVersion, name, packageVersion) { packageVersion, 'elm.json' ); - return FS.readJsonFile(cacheLocation).catch(() => + return FS.readJsonFile(cacheLocation).catch((error) => { // Finally, try to download it from the packages website - readFromPackagesWebsite( + if (options.offline) { + // Unless we're in offline mode + throw error; + } + + return readFromPackagesWebsite( cacheLocation, name, packageVersion, 'elm.json' - ) - ); + ); + }); }) ); } diff --git a/lib/types/options.d.ts b/lib/types/options.d.ts index 301350612..3a7bebe2d 100644 --- a/lib/types/options.d.ts +++ b/lib/types/options.d.ts @@ -24,6 +24,7 @@ export type Options = { packageJsonVersion: string; localElmReviewSrc: string | undefined; forceBuild: boolean; + offline: boolean; report: ReportMode; reportOnOneLine: boolean; rulesFilter: string[]; From fdf48aae6a74b18e7a5010c3e8e9cc602d01965d Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 20:39:19 +0200 Subject: [PATCH 02/98] Add elm-solve-deps-wasm dependency --- package-lock.json | 12 +++++++----- package.json | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8797d6e22..6218beffa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "chalk": "^4.0.0", "chokidar": "^3.5.2", "cross-spawn": "^7.0.3", + "elm-solve-deps-wasm": "^1.0.2", "elm-tooling": "^1.14.1", "fastest-levenshtein": "^1.0.16", "find-up": "^4.1.0", @@ -2621,9 +2622,9 @@ "dev": true }, "node_modules/elm-solve-deps-wasm": { - "version": "1.0.1", - "dev": true, - "license": "MPL-2.0" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/elm-solve-deps-wasm/-/elm-solve-deps-wasm-1.0.2.tgz", + "integrity": "sha512-qnwo7RO9IO7jd9SLHvIy0rSOEIlc/tNMTE9Cras0kl+b161PVidW4FvXo0MtXU8GAKi/2s/HYvhcnpR/NNQ1zw==" }, "node_modules/elm-test": { "version": "0.19.1-revision10", @@ -8357,8 +8358,9 @@ "dev": true }, "elm-solve-deps-wasm": { - "version": "1.0.1", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/elm-solve-deps-wasm/-/elm-solve-deps-wasm-1.0.2.tgz", + "integrity": "sha512-qnwo7RO9IO7jd9SLHvIy0rSOEIlc/tNMTE9Cras0kl+b161PVidW4FvXo0MtXU8GAKi/2s/HYvhcnpR/NNQ1zw==" }, "elm-test": { "version": "0.19.1-revision10", diff --git a/package.json b/package.json index 492069a4e..ef48098c1 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "chalk": "^4.0.0", "chokidar": "^3.5.2", "cross-spawn": "^7.0.3", + "elm-solve-deps-wasm": "^1.0.2", "elm-tooling": "^1.14.1", "fastest-levenshtein": "^1.0.16", "find-up": "^4.1.0", From d706822c22587ed7a9e25d6ee2f12a1bf970a9ee Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 20:55:09 +0200 Subject: [PATCH 03/98] Fetch dependencies --- lib/dependency-provider-offline.js | 88 ++++++++++++++++++++++++++++++ lib/template-dependencies.js | 52 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 lib/dependency-provider-offline.js diff --git a/lib/dependency-provider-offline.js b/lib/dependency-provider-offline.js new file mode 100644 index 000000000..0c2298bcd --- /dev/null +++ b/lib/dependency-provider-offline.js @@ -0,0 +1,88 @@ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const process = require('process'); + +// fetchElmJson(pkg: &str, version: &str) -> String; +module.exports.fetchElmJson = function fetchElmJson(pkg, version) { + // console.log("Fetching: " + pkg + " @ " + version); + try { + return fs.readFileSync(homeElmJsonPath(pkg, version), 'utf8'); + } catch (_) { + try { + return fs.readFileSync(cacheElmJsonPath(pkg, version), 'utf8'); + } catch (_) { + const remoteUrl = remoteElmJsonUrl(pkg, version); + throw `Not doing a remote request to ${remoteUrl}. Please run at least once elm-test first.`; + } + } +}; + +// listAvailableVersions(pkg: &str) -> Vec; +module.exports.listAvailableVersions = function listAvailableVersions(pkg) { + // console.log("List versions of: " + pkg); + try { + return fs.readdirSync(homePkgPath(pkg)) + // Reverse order of subdirectories to have newest versions first. + .reverse(); + } catch (_) { + console.log(`Directory "${homePkgPath(pkg)} does not exist`); + console.log( + `Not doing a request to the package server to find out existing versions. Please run at least once elm-test first.` + ); + return []; + } + +}; + +// Helper functions ################################################## + +function remoteElmJsonUrl(pkg, version) { + return `https://package.elm-lang.org/packages/${pkg}/${version}/elm.json`; +} + +function cacheElmJsonPath(pkg, version) { + const parts = splitAuthorPkg(pkg); + return path.join( + elmHome(), + 'pubgrub', + 'elm_json_cache', + parts.author, + parts.pkg, + version, + 'elm.json' + ); +} + +function homeElmJsonPath(pkg, version) { + return path.join(homePkgPath(pkg), version, 'elm.json'); +} + +function homePkgPath(pkg) { + const parts = splitAuthorPkg(pkg); + return path.join(elmHome(), '0.19.1', 'packages', parts.author, parts.pkg); +} + +function splitAuthorPkg(pkgIdentifier) { + const parts = pkgIdentifier.split('/'); + return {author: parts[0], pkg: parts[1]}; +} + +function elmHome() { + const elmHomeEnv = process.env['ELM_HOME']; + return elmHomeEnv ? elmHomeEnv : defaultElmHome(); +} + +function defaultElmHome() { + return process.platform === 'win32' + ? defaultWindowsElmHome() + : defaultUnixElmHome(); +} + +function defaultUnixElmHome() { + return path.join(os.homedir(), '.elm'); +} + +function defaultWindowsElmHome() { + return path.join(process.env.APPDATA, 'elm'); +} diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 1ff989d82..484a360e0 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -3,11 +3,13 @@ */ const spawnAsync = require('cross-spawn'); +const elmSolveDeps = require("elm-solve-deps-wasm"); const Hash = require('./hash'); const Cache = require('./cache'); const FS = require('./fs-wrapper'); const ErrorMessage = require('./error-message'); const getExecutable = require('elm-tooling/getExecutable'); +const depsProvider = require("./dependency-provider-offline"); /** * @typedef { import("./types/options").Options } Options @@ -91,7 +93,57 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX +const elmJson = ` +{ + "type": "package", + "name": "ianmackenzie/elm-units-interval", + "summary": "Version of elm-interval based on elm-units", + "license": "MPL-2.0", + "version": "2.3.0", + "exposed-modules": [ + "Quantity.Interval", + "Temperature.Interval", + "Angle.Interval" + ], + "elm-version": "0.19.0 <= v < 0.20.0", + "dependencies": { + "elm/core": "1.0.0 <= v < 2.0.0", + "elm/random": "1.0.0 <= v < 2.0.0", + "ianmackenzie/elm-float-extra": "1.1.0 <= v < 2.0.0", + "ianmackenzie/elm-units": "2.7.0 <= v < 3.0.0" + }, + "test-dependencies": { + "elm-explorations/test": "1.1.0 <= v < 2.0.0" + } +}`; + +elmSolveDeps.init(); +let solution = elmSolveDeps.solve_deps( + elmJson, + false, + { + 'jfmengels/elm-review': '2.0.0 <= v < 3.0.0', + 'stil4m/elm-syntax': '7.1.0 <= v < 8.0.0', + 'elm/project-metadata-utils': '1.0.0 <= v < 2.0.0' + }, + depsProvider.fetchElmJson, + depsProvider.listAvailableVersions +); +console.log(JSON.parse(solution)); + + +process.exit(0); + async function addElmSyntax(options, pathToElmJson, elmSyntaxVersion) { + elmSolveDeps.init(); + elmSolveDeps.solve_deps( + elmJson, + false, + {}, + depsProvider.fetchElmJson, + depsProvider.listAvailableVersions + ); + return spawnElmJsonAsync( options, [ From 7b5261dcec972a9547ba1b24051f4dceef5f8d57 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 21:03:16 +0200 Subject: [PATCH 04/98] Cache listing of versions --- lib/dependency-provider-offline.js | 36 +++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/dependency-provider-offline.js b/lib/dependency-provider-offline.js index 0c2298bcd..673bd721c 100644 --- a/lib/dependency-provider-offline.js +++ b/lib/dependency-provider-offline.js @@ -3,9 +3,14 @@ const os = require('os'); const path = require('path'); const process = require('process'); -// fetchElmJson(pkg: &str, version: &str) -> String; -module.exports.fetchElmJson = function fetchElmJson(pkg, version) { - // console.log("Fetching: " + pkg + " @ " + version); +module.exports = { + fetchElmJson, + listAvailableVersions +}; + +// FetchElmJson(pkg: &str, version: &str) -> String; +function fetchElmJson(pkg, version) { + // Console.log("Fetching: " + pkg + " @ " + version); try { return fs.readFileSync(homeElmJsonPath(pkg, version), 'utf8'); } catch (_) { @@ -16,15 +21,21 @@ module.exports.fetchElmJson = function fetchElmJson(pkg, version) { throw `Not doing a remote request to ${remoteUrl}. Please run at least once elm-test first.`; } } -}; +} + +const listAvailableVersionsCache = new Map(); + +// ListAvailableVersions(pkg: &str) -> Vec; +function listAvailableVersions(pkg) { + if (listAvailableVersionsCache.has(pkg)) { + return listAvailableVersionsCache.get(pkg); + } -// listAvailableVersions(pkg: &str) -> Vec; -module.exports.listAvailableVersions = function listAvailableVersions(pkg) { - // console.log("List versions of: " + pkg); try { - return fs.readdirSync(homePkgPath(pkg)) - // Reverse order of subdirectories to have newest versions first. - .reverse(); + // Reverse order of subdirectories to have newest versions first. + const dependencies = fs.readdirSync(homePkgPath(pkg)).reverse(); + listAvailableVersionsCache.set(pkg, dependencies); + return dependencies; } catch (_) { console.log(`Directory "${homePkgPath(pkg)} does not exist`); console.log( @@ -32,8 +43,7 @@ module.exports.listAvailableVersions = function listAvailableVersions(pkg) { ); return []; } - -}; +} // Helper functions ################################################## @@ -69,7 +79,7 @@ function splitAuthorPkg(pkgIdentifier) { } function elmHome() { - const elmHomeEnv = process.env['ELM_HOME']; + const elmHomeEnv = process.env.ELM_HOME; return elmHomeEnv ? elmHomeEnv : defaultElmHome(); } From c412577abd905e122e47bada92d4b1b6454ab579 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 21:46:18 +0200 Subject: [PATCH 05/98] Add initElmSolveDeps --- lib/template-dependencies.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 484a360e0..47de86cdd 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -23,6 +23,14 @@ module.exports = { addElmSyntax }; +let elmSolveDepsWasInitialized = false; +function initElmSolveDeps() { + if (!elmSolveDepsWasInitialized) { + elmSolveDepsWasInitialized = true; + elmSolveDeps.init(); + } +} + // GET async function get(options, elmJsonDependencies, pathToElmJson) { From f4c514a4233ce0a37580d8cf3cebc575a2a06341 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 21:48:08 +0200 Subject: [PATCH 06/98] Delete tmp code --- lib/template-dependencies.js | 41 ------------------------------------ 1 file changed, 41 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 47de86cdd..1e64b4acf 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -101,47 +101,6 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX -const elmJson = ` -{ - "type": "package", - "name": "ianmackenzie/elm-units-interval", - "summary": "Version of elm-interval based on elm-units", - "license": "MPL-2.0", - "version": "2.3.0", - "exposed-modules": [ - "Quantity.Interval", - "Temperature.Interval", - "Angle.Interval" - ], - "elm-version": "0.19.0 <= v < 0.20.0", - "dependencies": { - "elm/core": "1.0.0 <= v < 2.0.0", - "elm/random": "1.0.0 <= v < 2.0.0", - "ianmackenzie/elm-float-extra": "1.1.0 <= v < 2.0.0", - "ianmackenzie/elm-units": "2.7.0 <= v < 3.0.0" - }, - "test-dependencies": { - "elm-explorations/test": "1.1.0 <= v < 2.0.0" - } -}`; - -elmSolveDeps.init(); -let solution = elmSolveDeps.solve_deps( - elmJson, - false, - { - 'jfmengels/elm-review': '2.0.0 <= v < 3.0.0', - 'stil4m/elm-syntax': '7.1.0 <= v < 8.0.0', - 'elm/project-metadata-utils': '1.0.0 <= v < 2.0.0' - }, - depsProvider.fetchElmJson, - depsProvider.listAvailableVersions -); -console.log(JSON.parse(solution)); - - -process.exit(0); - async function addElmSyntax(options, pathToElmJson, elmSyntaxVersion) { elmSolveDeps.init(); elmSolveDeps.solve_deps( From ac5d32196cb27aaf944bce92665707e69691a7dc Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 22:13:46 +0200 Subject: [PATCH 07/98] Delete tmp code --- lib/template-dependencies.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 1e64b4acf..fe17acbc4 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -102,15 +102,6 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX async function addElmSyntax(options, pathToElmJson, elmSyntaxVersion) { - elmSolveDeps.init(); - elmSolveDeps.solve_deps( - elmJson, - false, - {}, - depsProvider.fetchElmJson, - depsProvider.listAvailableVersions - ); - return spawnElmJsonAsync( options, [ From 674e018b1598b1e8019427eb650374adcf6cee3f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 22:27:31 +0200 Subject: [PATCH 08/98] Add elm-syntax dependency using elm-solve-deps-wasm --- lib/build.js | 3 -- lib/template-dependencies.js | 63 ++++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/lib/build.js b/lib/build.js index 1159c7c93..b77b481e0 100644 --- a/lib/build.js +++ b/lib/build.js @@ -589,7 +589,6 @@ https://github.com/jfmengels/node-elm-review/issues/new options, buildFolder, parseElmElmJson, - parseElmElmJsonPath, elmSyntaxVersion ), // Needed when the user has `"type": "module"` in their package.json. @@ -616,12 +615,10 @@ async function createParserElmJsonFile( options, buildFolder, parseElmElmJson, - parseElmElmJsonPath, elmSyntaxVersion ) { const dependencies = await TemplateDependencies.addElmSyntax( options, - parseElmElmJsonPath, elmSyntaxVersion ); diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index fe17acbc4..218da44f3 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -3,13 +3,13 @@ */ const spawnAsync = require('cross-spawn'); -const elmSolveDeps = require("elm-solve-deps-wasm"); +const elmSolveDeps = require('elm-solve-deps-wasm'); const Hash = require('./hash'); const Cache = require('./cache'); const FS = require('./fs-wrapper'); const ErrorMessage = require('./error-message'); const getExecutable = require('elm-tooling/getExecutable'); -const depsProvider = require("./dependency-provider-offline"); +const depsProvider = require('./dependency-provider-offline'); /** * @typedef { import("./types/options").Options } Options @@ -101,26 +101,49 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX -async function addElmSyntax(options, pathToElmJson, elmSyntaxVersion) { - return spawnElmJsonAsync( - options, - [ - 'solve', - '--extra', - `stil4m/elm-syntax@${elmSyntaxVersion}`, - '--', - pathToElmJson - ], - (error) => { - throw new ErrorMessage.CustomError( - 'CONFIGURATION COMPILATION ERROR', - `I encountered a problem when solving dependencies for creating the parser application: +async function addElmSyntax(options, elmSyntaxVersion) { + initElmSolveDeps(); -${formatElmJsonError(error, options)}`, - null - ); + try { + const dependencies = elmSolveDeps.solve_deps( + `{ + "type": "application", + "source-directories": [ + "src", + "../ast-codec/src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/core": "1.0.5", + "elm/json": "1.1.3" + }, + "indirect": {} + }, + "test-dependencies": { + "direct": {}, + "indirect": {} } - ).then(JSON.parse); +}`, + false, + { + 'stil4m/elm-syntax': `${elmSyntaxVersion} <= v < ${nextPatchVersion( + elmSyntaxVersion + )}` + }, + depsProvider.fetchElmJson, + depsProvider.listAvailableVersions + ); + return JSON.parse(dependencies); + } catch (error) { + console.log(error); + throw error; + } +} + +function nextPatchVersion(version) { + const [major, minor, patch] = version.split('.'); + return `${major}.${minor}.${parseInt(patch) + 1}`; } // ADD From 149900f0da60a810ca4c6376ee118175cad935a3 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 22:52:34 +0200 Subject: [PATCH 09/98] Move to separate module --- lib/build.js | 2 +- lib/dependency-solver.js | 34 ++++++++++++++++++++++++++++ lib/template-dependencies.js | 43 +++++++----------------------------- 3 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 lib/dependency-solver.js diff --git a/lib/build.js b/lib/build.js index b77b481e0..175efe98d 100644 --- a/lib/build.js +++ b/lib/build.js @@ -617,7 +617,7 @@ async function createParserElmJsonFile( parseElmElmJson, elmSyntaxVersion ) { - const dependencies = await TemplateDependencies.addElmSyntax( + const dependencies = TemplateDependencies.addElmSyntax( options, elmSyntaxVersion ); diff --git a/lib/dependency-solver.js b/lib/dependency-solver.js new file mode 100644 index 000000000..36bb09fa9 --- /dev/null +++ b/lib/dependency-solver.js @@ -0,0 +1,34 @@ +const elmSolveDeps = require('elm-solve-deps-wasm'); +const depsProvider = require('./dependency-provider-offline'); + +module.exports = { + addExact +}; + +function addExact(options, elmJson, dependency, version) { + initElmSolveDeps(); + + let dependencies; + + dependencies = elmSolveDeps.solve_deps( + elmJson, + false, + {[dependency]: `${version} <= v < ${nextPatchVersion(version)}`}, + depsProvider.fetchElmJson, + depsProvider.listAvailableVersions + ); + return JSON.parse(dependencies); +} + +let elmSolveDepsWasInitialized = false; +function initElmSolveDeps() { + if (!elmSolveDepsWasInitialized) { + elmSolveDepsWasInitialized = true; + elmSolveDeps.init(); + } +} + +function nextPatchVersion(version) { + const [major, minor, patch] = version.split('.'); + return `${major}.${minor}.${parseInt(patch, 10) + 1}`; +} diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 218da44f3..f39680a24 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -3,13 +3,12 @@ */ const spawnAsync = require('cross-spawn'); -const elmSolveDeps = require('elm-solve-deps-wasm'); const Hash = require('./hash'); const Cache = require('./cache'); const FS = require('./fs-wrapper'); const ErrorMessage = require('./error-message'); const getExecutable = require('elm-tooling/getExecutable'); -const depsProvider = require('./dependency-provider-offline'); +const dependencySolver = require('./dependency-solver'); /** * @typedef { import("./types/options").Options } Options @@ -23,14 +22,6 @@ module.exports = { addElmSyntax }; -let elmSolveDepsWasInitialized = false; -function initElmSolveDeps() { - if (!elmSolveDepsWasInitialized) { - elmSolveDepsWasInitialized = true; - elmSolveDeps.init(); - } -} - // GET async function get(options, elmJsonDependencies, pathToElmJson) { @@ -101,12 +92,10 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX -async function addElmSyntax(options, elmSyntaxVersion) { - initElmSolveDeps(); - - try { - const dependencies = elmSolveDeps.solve_deps( - `{ +function addElmSyntax(options, elmSyntaxVersion) { + return dependencySolver.addExact( + options, + `{ "type": "application", "source-directories": [ "src", @@ -125,25 +114,9 @@ async function addElmSyntax(options, elmSyntaxVersion) { "indirect": {} } }`, - false, - { - 'stil4m/elm-syntax': `${elmSyntaxVersion} <= v < ${nextPatchVersion( - elmSyntaxVersion - )}` - }, - depsProvider.fetchElmJson, - depsProvider.listAvailableVersions - ); - return JSON.parse(dependencies); - } catch (error) { - console.log(error); - throw error; - } -} - -function nextPatchVersion(version) { - const [major, minor, patch] = version.split('.'); - return `${major}.${minor}.${parseInt(patch) + 1}`; + 'stil4m/elm-syntax', + elmSyntaxVersion + ); } // ADD From 1d00c0da1645edc0a03167998b5dc9bfcab7b766 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 23:32:52 +0200 Subject: [PATCH 10/98] Add dependency provider --- lib/dependency-provider.js | 340 +++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 lib/dependency-provider.js diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js new file mode 100644 index 000000000..305674df5 --- /dev/null +++ b/lib/dependency-provider.js @@ -0,0 +1,340 @@ +const fs = require('fs'); +const path = require('path'); +const wasm = require('elm-solve-deps-wasm'); +const ElmHome = require('./ElmHome.js'); +const SyncGet = require('./SyncGet.js'); +const collator = new Intl.Collator('en', {numeric: true}); // for sorting SemVer strings + +// Initialization work done only once. +wasm.init(); +const syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */ = + SyncGet.startWorker(); + +// Cache of existing versions according to the package website. +class OnlineVersionsCache { + map /*: Map> */ = new Map(); + + update() { + const pubgrubHome = path.join(ElmHome.elmHome(), 'pubgrub'); + fs.mkdirSync(pubgrubHome, {recursive: true}); + const cachePath = path.join(pubgrubHome, 'versions_cache.json'); + const remotePackagesUrl = 'https://package.elm-lang.org/all-packages'; + if (this.map.size === 0) { + let cacheFile; + try { + // Read from disk existing versions which are already cached. + cacheFile = fs.readFileSync(cachePath, 'utf8'); + } catch (_) { + // The cache file does not exist so let's reset it. + this.map = onlineVersionsFromScratch(cachePath, remotePackagesUrl); + return; + } + try { + this.map = parseOnlineVersions(JSON.parse(cacheFile)); + } catch (error) { + throw new Error( + `Failed to parse the cache file ${cachePath}.\n${error.message}` + ); + } + } + this.updateWithRequestSince(cachePath, remotePackagesUrl); + } + + // Update the cache with a request to the package server. + updateWithRequestSince( + cachePath /*: string */, + remotePackagesUrl /*: string */ + ) /*: void */ { + // Count existing versions. + let versionsCount = 0; + for (const versions of this.map.values()) { + versionsCount += versions.length; + } + + // Complete cache with a remote call to the package server. + const remoteUrl = remotePackagesUrl + '/since/' + (versionsCount - 1); // -1 to check if no package was deleted. + const newVersions = JSON.parse(syncGetWorker.get(remoteUrl)); + if (newVersions.length === 0) { + // Reload from scratch since it means at least one package was deleted from the registry. + this.map = onlineVersionsFromScratch(cachePath, remotePackagesUrl); + return; + } + // Check that the last package in the list was already in cache + // since the list returned by the package server is sorted newest first. + const {pkg, version} = splitPkgVersion(newVersions.pop()); + const cachePkgVersions = this.map.get(pkg); + if ( + cachePkgVersions !== undefined && + cachePkgVersions[cachePkgVersions.length - 1] === version + ) { + // Insert (in reverse) newVersions into onlineVersionsCache map. + for (const pkgVersion of newVersions.reverse()) { + const {pkg, version} = splitPkgVersion(pkgVersion); + const versionsOfPkg = this.map.get(pkg); + if (versionsOfPkg === undefined) { + this.map.set(pkg, [version]); + } else { + versionsOfPkg.push(version); + } + } + // Save the updated onlineVersionsCache to disk. + const onlineVersions = Object.fromEntries(this.map.entries()); + fs.writeFileSync(cachePath, JSON.stringify(onlineVersions)); + } else { + // There was a problem and a package got deleted from the server. + this.map = onlineVersionsFromScratch(cachePath, remotePackagesUrl); + } + } + + getVersions(pkg /*: string */) /*: Array */ { + const versions = this.map.get(pkg); + return versions === undefined ? [] : versions; + } +} + +class OnlineAvailableVersionLister { + // Memoization cache to avoid doing the same work twice in list. + memoCache /*: Map> */ = new Map(); + onlineCache /*: OnlineVersionsCache */; + + constructor(onlineCache /*: OnlineVersionsCache */) { + onlineCache.update(); + this.onlineCache = onlineCache; + } + + list(pkg /*: string */) /*: Array */ { + const memoVersions = this.memoCache.get(pkg); + if (memoVersions !== undefined) { + return memoVersions; + } + const offlineVersions = readVersionsInElmHomeAndSort(pkg); + const allVersionsSet = new Set(this.onlineCache.getVersions(pkg)); + // Combine local and online versions. + for (const version of offlineVersions) { + allVersionsSet.add(version); + } + const allVersions = [...allVersionsSet].sort(flippedSemverCompare); + this.memoCache.set(pkg, allVersions); + return allVersions; + } +} + +class OfflineAvailableVersionLister { + // Memoization cache to avoid doing the same work twice in list. + cache /*: Map> */ = new Map(); + + list(pkg /*: string */) /*: Array */ { + const memoVersions = this.cache.get(pkg); + if (memoVersions !== undefined) { + return memoVersions; + } + + const offlineVersions = readVersionsInElmHomeAndSort(pkg); + + this.cache.set(pkg, offlineVersions); + return offlineVersions; + } +} + +function readVersionsInElmHomeAndSort(pkg /*: string */) /*: Array */ { + const pkgPath = ElmHome.packagePath(pkg); + let offlineVersions; + try { + offlineVersions = fs.readdirSync(pkgPath); + } catch (_) { + // The directory doesn't exist or we don't have permissions. + // It's fine to catch all cases and return an empty list. + offlineVersions = []; + } + + return offlineVersions.sort(flippedSemverCompare); +} + +class DependencyProvider { + cache /*: OnlineVersionsCache */ = new OnlineVersionsCache(); + + // Solve dependencies completely offline, without any http request. + solveOffline( + elmJson /*: string */, + useTest /*: boolean */, + extra /*: { [string]: string } */ + ) /*: string */ { + const lister = new OfflineAvailableVersionLister(); + try { + return wasm.solve_deps( + elmJson, + useTest, + extra, + fetchElmJsonOffline, + (pkg) => lister.list(pkg) + ); + } catch (errorMessage) { + throw new Error(errorMessage); + } + } + + // Solve dependencies with http requests when required. + solveOnline( + elmJson /*: string */, + useTest /*: boolean */, + extra /*: { [string]: string } */ + ) /*: string */ { + const lister = new OnlineAvailableVersionLister(this.cache); + try { + return wasm.solve_deps( + elmJson, + useTest, + extra, + fetchElmJsonOnline, + (pkg) => lister.list(pkg) + ); + } catch (errorMessage) { + throw new Error(errorMessage); + } + } +} + +function fetchElmJsonOnline( + pkg /*: string */, + version /*: string */ +) /*: string */ { + try { + return fetchElmJsonOffline(pkg, version); + } catch (_) { + // `fetchElmJsonOffline` can only fail in ways that are either expected + // (such as file does not exist or no permissions) + // or because there was an error parsing `pkg` and `version`. + // In such case, this will throw again with `cacheElmJsonPath()` so it's fine. + const remoteUrl = remoteElmJsonUrl(pkg, version); + const elmJson = syncGetWorker.get(remoteUrl); + const cachePath = cacheElmJsonPath(pkg, version); + const parentDir = path.dirname(cachePath); + fs.mkdirSync(parentDir, {recursive: true}); + fs.writeFileSync(cachePath, elmJson); + return elmJson; + } +} + +function fetchElmJsonOffline( + pkg /*: string */, + version /*: string */ +) /*: string */ { + try { + return fs.readFileSync(homeElmJsonPath(pkg, version), 'utf8'); + } catch (_) { + // The read can only fail if the elm.json file does not exist + // or if we don't have the permissions to read it so it's fine to catch all. + // Otherwise, it means that `homeElmJsonPath()` failed while processing `pkg` and `version`. + // In such case, again, it's fine to catch all since the next call to `cacheElmJsonPath()` + // will fail the same anyway. + return fs.readFileSync(cacheElmJsonPath(pkg, version), 'utf8'); + } +} + +// Reset the cache of existing versions from scratch +// with a request to the package server. +function onlineVersionsFromScratch( + cachePath /*: string */, + remotePackagesUrl /*: string */ +) /*: Map> */ { + const onlineVersionsJson = syncGetWorker.get(remotePackagesUrl); + fs.writeFileSync(cachePath, onlineVersionsJson); + const onlineVersions = JSON.parse(onlineVersionsJson); + try { + return parseOnlineVersions(onlineVersions); + } catch (error) { + throw new Error( + `Failed to parse the response from the request to ${remotePackagesUrl}.\n${error.message}` + ); + } +} + +// Helper functions ################################################## + +/* Compares two versions so that newer versions appear first when sorting with this function. */ +function flippedSemverCompare(a /*: string */, b /*: string */) /*: number */ { + return collator.compare(b, a); +} + +function parseOnlineVersions( + json /*: mixed */ +) /*: Map> */ { + if (typeof json !== 'object' || json === null || Array.isArray(json)) { + throw new Error( + `Expected an object, but got: ${ + json === null ? 'null' : Array.isArray(json) ? 'Array' : typeof json + }` + ); + } + + const result = new Map(); + + for (const [key, value] of Object.entries(json)) { + result.set(key, parseVersions(key, value)); + } + + return result; +} + +function parseVersions( + key /*: string */, + json /*: mixed */ +) /*: Array */ { + if (!Array.isArray(json)) { + throw new Error( + `Expected ${JSON.stringify(key)} to be an array, but got: ${typeof json}` + ); + } + + for (const [index, item] of json.entries()) { + if (typeof item !== 'string') { + throw new Error( + `Expected${JSON.stringify( + key + )}->${index} to be a string, but got: ${typeof item}` + ); + } + } + + return json; +} + +function remoteElmJsonUrl( + pkg /*: string */, + version /*: string */ +) /*: string */ { + return `https://package.elm-lang.org/packages/${pkg}/${version}/elm.json`; +} + +function cacheElmJsonPath( + pkg /*: string */, + version /*: string */ +) /*: string */ { + const parts = ElmHome.splitAuthorPkg(pkg); + return path.join( + ElmHome.elmHome(), + 'pubgrub', + 'elm_json_cache', + parts.author, + parts.pkg, + version, + 'elm.json' + ); +} + +function homeElmJsonPath( + pkg /*: string */, + version /*: string */ +) /*: string */ { + return path.join(ElmHome.packagePath(pkg), version, 'elm.json'); +} + +function splitPkgVersion(str /*: string */) /*: { + pkg: string, + version: string, +} */ { + const parts = str.split('@'); + return {pkg: parts[0], version: parts[1]}; +} + +module.exports = DependencyProvider; From aeca52ec4cdfdd9da2c4920600f88119e40c5800 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 23:33:21 +0200 Subject: [PATCH 11/98] Extract variable --- lib/template-dependencies.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index f39680a24..f9f3d8a31 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -93,9 +93,7 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX function addElmSyntax(options, elmSyntaxVersion) { - return dependencySolver.addExact( - options, - `{ + const elmJson = `{ "type": "application", "source-directories": [ "src", @@ -113,7 +111,10 @@ function addElmSyntax(options, elmSyntaxVersion) { "direct": {}, "indirect": {} } -}`, +}`; + return dependencySolver.addExact( + options, + elmJson, 'stil4m/elm-syntax', elmSyntaxVersion ); From 4481f1e0d960b6292150e62083e59d53f7223f8e Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 23:50:08 +0200 Subject: [PATCH 12/98] Expose elmReviewDependencyCache --- lib/project-json-files.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 4655fb713..5b4310afd 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -165,5 +165,6 @@ function elmReviewDependencyCache( module.exports = { getElmJson, getElmJsonFromElmHome, - getDocsJson + getDocsJson, + elmReviewDependencyCache }; From c5980202736796f198b22705f25eb4057e4215d0 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 12 Oct 2023 23:56:00 +0200 Subject: [PATCH 13/98] Add online solver --- lib/dependency-provider.js | 18 ++++++---------- lib/project-json-files.js | 2 +- lib/sync-get-worker.js | 36 +++++++++++++++++++++++++++++++ lib/sync-get.js | 41 ++++++++++++++++++++++++++++++++++++ lib/template-dependencies.js | 27 ++++++++++++++++++------ 5 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 lib/sync-get-worker.js create mode 100644 lib/sync-get.js diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 305674df5..67758aee4 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -1,8 +1,8 @@ const fs = require('fs'); const path = require('path'); const wasm = require('elm-solve-deps-wasm'); -const ElmHome = require('./ElmHome.js'); -const SyncGet = require('./SyncGet.js'); +const ProjectJsonFiles = require('./project-json-files'); +const SyncGet = require('./sync-get'); const collator = new Intl.Collator('en', {numeric: true}); // for sorting SemVer strings // Initialization work done only once. @@ -15,7 +15,7 @@ class OnlineVersionsCache { map /*: Map> */ = new Map(); update() { - const pubgrubHome = path.join(ElmHome.elmHome(), 'pubgrub'); + const pubgrubHome = path.join(ProjectJsonFiles.elmRoot, 'elm-review'); fs.mkdirSync(pubgrubHome, {recursive: true}); const cachePath = path.join(pubgrubHome, 'versions_cache.json'); const remotePackagesUrl = 'https://package.elm-lang.org/all-packages'; @@ -156,14 +156,13 @@ class DependencyProvider { // Solve dependencies completely offline, without any http request. solveOffline( elmJson /*: string */, - useTest /*: boolean */, extra /*: { [string]: string } */ ) /*: string */ { const lister = new OfflineAvailableVersionLister(); try { return wasm.solve_deps( elmJson, - useTest, + false, extra, fetchElmJsonOffline, (pkg) => lister.list(pkg) @@ -176,17 +175,12 @@ class DependencyProvider { // Solve dependencies with http requests when required. solveOnline( elmJson /*: string */, - useTest /*: boolean */, extra /*: { [string]: string } */ ) /*: string */ { const lister = new OnlineAvailableVersionLister(this.cache); try { - return wasm.solve_deps( - elmJson, - useTest, - extra, - fetchElmJsonOnline, - (pkg) => lister.list(pkg) + return wasm.solve_deps(elmJson, false, extra, fetchElmJsonOnline, (pkg) => + lister.list(pkg) ); } catch (errorMessage) { throw new Error(errorMessage); diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 5b4310afd..c6a123bce 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -166,5 +166,5 @@ module.exports = { getElmJson, getElmJsonFromElmHome, getDocsJson, - elmReviewDependencyCache + elmRoot }; diff --git a/lib/sync-get-worker.js b/lib/sync-get-worker.js new file mode 100644 index 000000000..14a25ffdf --- /dev/null +++ b/lib/sync-get-worker.js @@ -0,0 +1,36 @@ +// @flow + +// $FlowFixMe[cannot-resolve-module]: Flow doesn’t seem to know about the `worker_threads` module yet. +const {parentPort, workerData} = require('worker_threads'); +const https = require('https'); + +const {sharedLock, requestPort} = workerData; +const sharedLockArray = new Int32Array(sharedLock); + +parentPort.on('message', async (url) => { + try { + const response = await getBody(url); + requestPort.postMessage(response); + } catch (error) { + requestPort.postMessage({error}); + } + Atomics.notify(sharedLockArray, 0, Infinity); +}); + +async function getBody(url /*: string */) /*: Promise */ { + return new Promise(function (resolve, reject) { + https + .get(url, function (res) { + let body = ''; + res.on('data', function (chunk) { + body += chunk; + }); + res.on('end', function () { + resolve(body); + }); + }) + .on('error', function (err) { + reject(err); + }); + }); +} diff --git a/lib/sync-get.js b/lib/sync-get.js new file mode 100644 index 000000000..252a6c547 --- /dev/null +++ b/lib/sync-get.js @@ -0,0 +1,41 @@ +const path = require('path'); +const { + Worker, + MessageChannel, + receiveMessageOnPort +} = require('worker_threads'); + +// Start a worker thread and return a `syncGetWorker` +// capable of making sync requests until shut down. +function startWorker() /*: { + get: (string) => string, + shutDown: () => void, +} */ { + const {port1: localPort, port2: workerPort} = new MessageChannel(); + const sharedLock = new SharedArrayBuffer(4); + const sharedLockArray = new Int32Array(sharedLock); + const workerPath = path.resolve(__dirname, 'sync-get-worker.js'); + const worker = new Worker(workerPath, { + workerData: {sharedLock, requestPort: workerPort}, + transferList: [workerPort] + }); + function get(url) { + worker.postMessage(url); + Atomics.wait(sharedLockArray, 0, 0); // blocks until notified at index 0. + const response = receiveMessageOnPort(localPort); + if (response.message.error) { + throw response.message.error; + } else { + return response.message; + } + } + function shutDown() { + localPort.close(); + worker.terminate(); + } + return {get, shutDown}; +} + +module.exports = { + startWorker +}; diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index f9f3d8a31..cce8f682f 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -9,6 +9,7 @@ const FS = require('./fs-wrapper'); const ErrorMessage = require('./error-message'); const getExecutable = require('elm-tooling/getExecutable'); const dependencySolver = require('./dependency-solver'); +const DependencyProvider = require('./dependency-provider'); /** * @typedef { import("./types/options").Options } Options @@ -22,6 +23,8 @@ module.exports = { addElmSyntax }; +const dependencyProvider = new DependencyProvider(); + // GET async function get(options, elmJsonDependencies, pathToElmJson) { @@ -112,12 +115,24 @@ function addElmSyntax(options, elmSyntaxVersion) { "indirect": {} } }`; - return dependencySolver.addExact( - options, - elmJson, - 'stil4m/elm-syntax', - elmSyntaxVersion - ); + const extra = { + 'stil4m/elm-syntax': `${elmSyntaxVersion} <= v < ${nextPatchVersion( + elmSyntaxVersion + )}` + }; + try { + return dependencyProvider.solveOffline(elmJson, extra); + } catch (error) { + if (options.offline) { + throw error; + } + return dependencyProvider.solveOnline(elmJson, extra); + } +} + +function nextPatchVersion(version) { + const [major, minor, patch] = version.split('.'); + return `${major}.${minor}.${parseInt(patch, 10) + 1}`; } // ADD From 471bd0a7bc55f2bd8bb952649b2f2a9983e729ef Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:05:23 +0200 Subject: [PATCH 14/98] Add getElmJsonFromElmHomePath --- lib/project-json-files.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/project-json-files.js b/lib/project-json-files.js index c6a123bce..742153f73 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -62,17 +62,21 @@ function getElmJsonFromElmHome(elmVersion, name, packageVersion) { if (promise) { return promise; } + const elmJsonPath = getElmJsonFromElmHomePath(elmVersion, name, packageVersion); + promise = FS.readJsonFile(elmJsonPath); + elmJsonInElmHomePromises.set(key, promise); + return promise; +} +function getElmJsonFromElmHomePath(elmVersion, name, packageVersion) { const directory = path.join( - elmRoot, - elmVersion, - 'packages', - name, - packageVersion + elmRoot, + elmVersion, + 'packages', + name, + packageVersion ); - promise = FS.readJsonFile(path.join(directory, 'elm.json')); - elmJsonInElmHomePromises.set(key, promise); - return promise; + return path.join(directory, 'elm.json') } /** Get the docs.json file for a dependency. @@ -164,6 +168,7 @@ function elmReviewDependencyCache( module.exports = { getElmJson, + getElmJsonFromElmHomePath, getElmJsonFromElmHome, getDocsJson, elmRoot From 0e927192a1d94c567c2a7eaa2e352b94818cf374 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:12:34 +0200 Subject: [PATCH 15/98] Pass elm version to get elm home --- lib/build.js | 4 +++ lib/dependency-provider.js | 52 ++++++++++++++++++------------------ lib/project-json-files.js | 18 ++++++++----- lib/template-dependencies.js | 6 ++--- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/lib/build.js b/lib/build.js index 175efe98d..58ec2ea46 100644 --- a/lib/build.js +++ b/lib/build.js @@ -588,6 +588,7 @@ https://github.com/jfmengels/node-elm-review/issues/new createParserElmJsonFile( options, buildFolder, + reviewElmJson['elm-version'], parseElmElmJson, elmSyntaxVersion ), @@ -614,11 +615,13 @@ https://github.com/jfmengels/node-elm-review/issues/new async function createParserElmJsonFile( options, buildFolder, + elmVersion, parseElmElmJson, elmSyntaxVersion ) { const dependencies = TemplateDependencies.addElmSyntax( options, + elmVersion, elmSyntaxVersion ); @@ -628,6 +631,7 @@ async function createParserElmJsonFile( JSON.stringify( { ...parseElmElmJson, + "elm-version": elmVersion, dependencies, 'source-directories': parseElmElmJson['source-directories'].map((dir) => path.resolve(parseElmFolder, dir) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 67758aee4..7c8340d40 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -152,6 +152,11 @@ function readVersionsInElmHomeAndSort(pkg /*: string */) /*: Array */ { class DependencyProvider { cache /*: OnlineVersionsCache */ = new OnlineVersionsCache(); + elmVersion /*: string */; + + constructor(elmVersion /*: string */) { + this.elmVersion = elmVersion; + } // Solve dependencies completely offline, without any http request. solveOffline( @@ -164,7 +169,7 @@ class DependencyProvider { elmJson, false, extra, - fetchElmJsonOffline, + fetchElmJsonOffline(this.elmVersion), (pkg) => lister.list(pkg) ); } catch (errorMessage) { @@ -209,20 +214,22 @@ function fetchElmJsonOnline( } } -function fetchElmJsonOffline( - pkg /*: string */, - version /*: string */ -) /*: string */ { - try { - return fs.readFileSync(homeElmJsonPath(pkg, version), 'utf8'); - } catch (_) { - // The read can only fail if the elm.json file does not exist - // or if we don't have the permissions to read it so it's fine to catch all. - // Otherwise, it means that `homeElmJsonPath()` failed while processing `pkg` and `version`. - // In such case, again, it's fine to catch all since the next call to `cacheElmJsonPath()` - // will fail the same anyway. - return fs.readFileSync(cacheElmJsonPath(pkg, version), 'utf8'); - } +function fetchElmJsonOffline(elmVersion) { + return (pkg /*: string */, version /*: string */) /*: string */ => { + try { + return fs.readFileSync( + ProjectJsonFiles.getElmJsonFromElmHomePath(elmVersion, pkg, version), + 'utf8' + ); + } catch (_) { + // The read can only fail if the elm.json file does not exist + // or if we don't have the permissions to read it so it's fine to catch all. + // Otherwise, it means that `homeElmJsonPath()` failed while processing `pkg` and `version`. + // In such case, again, it's fine to catch all since the next call to `cacheElmJsonPath()` + // will fail the same anyway. + return fs.readFileSync(cacheElmJsonPath(pkg, version), 'utf8'); + } + }; } // Reset the cache of existing versions from scratch @@ -304,25 +311,18 @@ function cacheElmJsonPath( pkg /*: string */, version /*: string */ ) /*: string */ { - const parts = ElmHome.splitAuthorPkg(pkg); + const [author, pkgName] = pkg.split('/'); return path.join( - ElmHome.elmHome(), + ProjectJsonFiles.elmRoot, 'pubgrub', 'elm_json_cache', - parts.author, - parts.pkg, + author, + pkgName, version, 'elm.json' ); } -function homeElmJsonPath( - pkg /*: string */, - version /*: string */ -) /*: string */ { - return path.join(ElmHome.packagePath(pkg), version, 'elm.json'); -} - function splitPkgVersion(str /*: string */) /*: { pkg: string, version: string, diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 742153f73..cd8da3317 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -62,7 +62,11 @@ function getElmJsonFromElmHome(elmVersion, name, packageVersion) { if (promise) { return promise; } - const elmJsonPath = getElmJsonFromElmHomePath(elmVersion, name, packageVersion); + const elmJsonPath = getElmJsonFromElmHomePath( + elmVersion, + name, + packageVersion + ); promise = FS.readJsonFile(elmJsonPath); elmJsonInElmHomePromises.set(key, promise); return promise; @@ -70,13 +74,13 @@ function getElmJsonFromElmHome(elmVersion, name, packageVersion) { function getElmJsonFromElmHomePath(elmVersion, name, packageVersion) { const directory = path.join( - elmRoot, - elmVersion, - 'packages', - name, - packageVersion + elmRoot, + elmVersion, + 'packages', + name, + packageVersion ); - return path.join(directory, 'elm.json') + return path.join(directory, 'elm.json'); } /** Get the docs.json file for a dependency. diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index cce8f682f..7db2fc710 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -23,8 +23,6 @@ module.exports = { addElmSyntax }; -const dependencyProvider = new DependencyProvider(); - // GET async function get(options, elmJsonDependencies, pathToElmJson) { @@ -95,7 +93,8 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX -function addElmSyntax(options, elmSyntaxVersion) { +function addElmSyntax(options, elmVersion, elmSyntaxVersion) { + const dependencyProvider = new DependencyProvider(elmVersion); const elmJson = `{ "type": "application", "source-directories": [ @@ -120,6 +119,7 @@ function addElmSyntax(options, elmSyntaxVersion) { elmSyntaxVersion )}` }; + try { return dependencyProvider.solveOffline(elmJson, extra); } catch (error) { From 938ec5f065c35452ff159ac2e82b3b645b2554dd Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:14:19 +0200 Subject: [PATCH 16/98] Put cache under the version --- lib/build.js | 2 +- lib/dependency-provider.js | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/build.js b/lib/build.js index 58ec2ea46..cda3ba345 100644 --- a/lib/build.js +++ b/lib/build.js @@ -631,7 +631,7 @@ async function createParserElmJsonFile( JSON.stringify( { ...parseElmElmJson, - "elm-version": elmVersion, + 'elm-version': elmVersion, dependencies, 'source-directories': parseElmElmJson['source-directories'].map((dir) => path.resolve(parseElmFolder, dir) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 7c8340d40..cb34c602d 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -14,8 +14,13 @@ const syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */ = class OnlineVersionsCache { map /*: Map> */ = new Map(); - update() { - const pubgrubHome = path.join(ProjectJsonFiles.elmRoot, 'elm-review'); + update(elmVersion /*: string */) { + const pubgrubHome = path.join( + ProjectJsonFiles.elmRoot, + 'elm-review', + 'dependencies-cache', + elmVersion + ); fs.mkdirSync(pubgrubHome, {recursive: true}); const cachePath = path.join(pubgrubHome, 'versions_cache.json'); const remotePackagesUrl = 'https://package.elm-lang.org/all-packages'; @@ -97,8 +102,11 @@ class OnlineAvailableVersionLister { memoCache /*: Map> */ = new Map(); onlineCache /*: OnlineVersionsCache */; - constructor(onlineCache /*: OnlineVersionsCache */) { - onlineCache.update(); + constructor( + onlineCache /*: OnlineVersionsCache */, + elmVersion /*: string */ + ) { + onlineCache.update(elmVersion); this.onlineCache = onlineCache; } @@ -182,7 +190,10 @@ class DependencyProvider { elmJson /*: string */, extra /*: { [string]: string } */ ) /*: string */ { - const lister = new OnlineAvailableVersionLister(this.cache); + const lister = new OnlineAvailableVersionLister( + this.cache, + this.elmVersion + ); try { return wasm.solve_deps(elmJson, false, extra, fetchElmJsonOnline, (pkg) => lister.list(pkg) From d6b9d4f80f5e887332313ea92b7fc828026e9b72 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:20:11 +0200 Subject: [PATCH 17/98] Get elm home --- lib/dependency-provider.js | 16 ++++++++-------- lib/project-json-files.js | 21 ++++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index cb34c602d..c7dc2524b 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -110,12 +110,12 @@ class OnlineAvailableVersionLister { this.onlineCache = onlineCache; } - list(pkg /*: string */) /*: Array */ { + list(elmVersion/*: string */, pkg /*: string */) /*: Array */ { const memoVersions = this.memoCache.get(pkg); if (memoVersions !== undefined) { return memoVersions; } - const offlineVersions = readVersionsInElmHomeAndSort(pkg); + const offlineVersions = readVersionsInElmHomeAndSort(elmVersion, pkg); const allVersionsSet = new Set(this.onlineCache.getVersions(pkg)); // Combine local and online versions. for (const version of offlineVersions) { @@ -131,21 +131,21 @@ class OfflineAvailableVersionLister { // Memoization cache to avoid doing the same work twice in list. cache /*: Map> */ = new Map(); - list(pkg /*: string */) /*: Array */ { + list(elmVersion, pkg /*: string */) /*: Array */ { const memoVersions = this.cache.get(pkg); if (memoVersions !== undefined) { return memoVersions; } - const offlineVersions = readVersionsInElmHomeAndSort(pkg); + const offlineVersions = readVersionsInElmHomeAndSort(elmVersion, pkg); this.cache.set(pkg, offlineVersions); return offlineVersions; } } -function readVersionsInElmHomeAndSort(pkg /*: string */) /*: Array */ { - const pkgPath = ElmHome.packagePath(pkg); +function readVersionsInElmHomeAndSort(elmVersion /*: string*/, pkg /*: string */) /*: Array */ { + const pkgPath = ProjectJsonFiles.getPackagePathInElmHome(elmVersion, pkg); let offlineVersions; try { offlineVersions = fs.readdirSync(pkgPath); @@ -178,7 +178,7 @@ class DependencyProvider { false, extra, fetchElmJsonOffline(this.elmVersion), - (pkg) => lister.list(pkg) + (pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { throw new Error(errorMessage); @@ -196,7 +196,7 @@ class DependencyProvider { ); try { return wasm.solve_deps(elmJson, false, extra, fetchElmJsonOnline, (pkg) => - lister.list(pkg) + lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { throw new Error(errorMessage); diff --git a/lib/project-json-files.js b/lib/project-json-files.js index cd8da3317..115be34c2 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -73,14 +73,20 @@ function getElmJsonFromElmHome(elmVersion, name, packageVersion) { } function getElmJsonFromElmHomePath(elmVersion, name, packageVersion) { - const directory = path.join( - elmRoot, - elmVersion, - 'packages', - name, - packageVersion + return path.join( + getPackagePathInElmHome(elmVersion, name), + packageVersion, + 'elm.json' + ); +} + +function getPackagePathInElmHome(elmVersion, name) { + return path.join( + elmRoot, + elmVersion, + 'packages', + name ); - return path.join(directory, 'elm.json'); } /** Get the docs.json file for a dependency. @@ -172,6 +178,7 @@ function elmReviewDependencyCache( module.exports = { getElmJson, + getPackagePathInElmHome, getElmJsonFromElmHomePath, getElmJsonFromElmHome, getDocsJson, From bd168e1d1881e575c7c0587faecffbfc5eb742e2 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:27:03 +0200 Subject: [PATCH 18/98] Parse dependencies --- lib/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.js b/lib/build.js index cda3ba345..67874b515 100644 --- a/lib/build.js +++ b/lib/build.js @@ -632,7 +632,7 @@ async function createParserElmJsonFile( { ...parseElmElmJson, 'elm-version': elmVersion, - dependencies, + dependencies: JSON.parse(dependencies), 'source-directories': parseElmElmJson['source-directories'].map((dir) => path.resolve(parseElmFolder, dir) ) From 7de51dd05db0fff57d273b544123fba3bf96f9f9 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:29:20 +0200 Subject: [PATCH 19/98] Declare dependency solver once --- lib/template-dependencies.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 7db2fc710..1d3b637c5 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -11,6 +11,8 @@ const getExecutable = require('elm-tooling/getExecutable'); const dependencySolver = require('./dependency-solver'); const DependencyProvider = require('./dependency-provider'); +let dependencyProvider; + /** * @typedef { import("./types/options").Options } Options * @typedef { import("./types/template-dependencies").TemplateDependenciesError } TemplateDependenciesError @@ -94,7 +96,7 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX function addElmSyntax(options, elmVersion, elmSyntaxVersion) { - const dependencyProvider = new DependencyProvider(elmVersion); + dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); const elmJson = `{ "type": "application", "source-directories": [ From a86e70a8e17996c5613cf62dc3dfeac57afb481d Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:30:26 +0200 Subject: [PATCH 20/98] Format --- lib/dependency-provider.js | 7 +++++-- lib/project-json-files.js | 13 ++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index c7dc2524b..bad2e010e 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -110,7 +110,7 @@ class OnlineAvailableVersionLister { this.onlineCache = onlineCache; } - list(elmVersion/*: string */, pkg /*: string */) /*: Array */ { + list(elmVersion /*: string */, pkg /*: string */) /*: Array */ { const memoVersions = this.memoCache.get(pkg); if (memoVersions !== undefined) { return memoVersions; @@ -144,7 +144,10 @@ class OfflineAvailableVersionLister { } } -function readVersionsInElmHomeAndSort(elmVersion /*: string*/, pkg /*: string */) /*: Array */ { +function readVersionsInElmHomeAndSort( + elmVersion /*: string*/, + pkg /*: string */ +) /*: Array */ { const pkgPath = ProjectJsonFiles.getPackagePathInElmHome(elmVersion, pkg); let offlineVersions; try { diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 115be34c2..4978109bf 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -74,19 +74,14 @@ function getElmJsonFromElmHome(elmVersion, name, packageVersion) { function getElmJsonFromElmHomePath(elmVersion, name, packageVersion) { return path.join( - getPackagePathInElmHome(elmVersion, name), - packageVersion, - 'elm.json' + getPackagePathInElmHome(elmVersion, name), + packageVersion, + 'elm.json' ); } function getPackagePathInElmHome(elmVersion, name) { - return path.join( - elmRoot, - elmVersion, - 'packages', - name - ); + return path.join(elmRoot, elmVersion, 'packages', name); } /** Get the docs.json file for a dependency. From 905b60da5fcad416bd0b4c39eea0e79290f0dbef Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:32:24 +0200 Subject: [PATCH 21/98] Pass elmVersion around --- lib/build.js | 1 + lib/template-dependencies.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/build.js b/lib/build.js index 67874b515..3dad58337 100644 --- a/lib/build.js +++ b/lib/build.js @@ -315,6 +315,7 @@ async function createTemplateProject( const [dependencies, previousElmJson] = await Promise.all([ TemplateDependencies.get( options, + reviewElmJson['elm-version'], reviewElmJson.dependencies, reviewElmJsonPath ), diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 1d3b637c5..08bbb6609 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -27,17 +27,17 @@ module.exports = { // GET -async function get(options, elmJsonDependencies, pathToElmJson) { +async function get(options, elmVersion, elmJsonDependencies, pathToElmJson) { const dependencyHash = Hash.hash(JSON.stringify(elmJsonDependencies)); const cacheKey = `${dependencyHash}${ options.localElmReviewSrc ? '-local' : '' }`; return Cache.getOrCompute(options.dependenciesCachePath(), cacheKey, () => - computeDependencies(options, pathToElmJson) + computeDependencies(options, elmVersion, pathToElmJson) ); } -function computeDependencies(options, pathToElmJson) { +function computeDependencies(options, elmVersion, pathToElmJson) { return spawnElmJsonAsync( options, [ From 826f7907b69d42da206c635d7ab23319cc48b377 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:34:47 +0200 Subject: [PATCH 22/98] Extract solve function --- lib/template-dependencies.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 08bbb6609..2d025dc0c 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -96,7 +96,6 @@ function formatElmJsonError(error, options) { // ADD ELM-SYNTAX function addElmSyntax(options, elmVersion, elmSyntaxVersion) { - dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); const elmJson = `{ "type": "application", "source-directories": [ @@ -122,14 +121,20 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { )}` }; - try { - return dependencyProvider.solveOffline(elmJson, extra); - } catch (error) { - if (options.offline) { - throw error; + return solve(options, elmVersion, elmJson, extra); +} + +function solve(options, elmVersion, elmJson, extra) { + dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); + + try { + return dependencyProvider.solveOffline(elmJson, extra); + } catch (error) { + if (options.offline) { + throw error; + } + return dependencyProvider.solveOnline(elmJson, extra); } - return dependencyProvider.solveOnline(elmJson, extra); - } } function nextPatchVersion(version) { From a90eaa728d4df144acdc1220d5d8348a7e361293 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:40:00 +0200 Subject: [PATCH 23/98] Parse in solve --- lib/build.js | 2 +- lib/template-dependencies.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/build.js b/lib/build.js index 3dad58337..9557346ab 100644 --- a/lib/build.js +++ b/lib/build.js @@ -633,7 +633,7 @@ async function createParserElmJsonFile( { ...parseElmElmJson, 'elm-version': elmVersion, - dependencies: JSON.parse(dependencies), + dependencies, 'source-directories': parseElmElmJson['source-directories'].map((dir) => path.resolve(parseElmFolder, dir) ) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 2d025dc0c..64dfd6758 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -125,16 +125,16 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { } function solve(options, elmVersion, elmJson, extra) { - dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); + dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); - try { - return dependencyProvider.solveOffline(elmJson, extra); - } catch (error) { - if (options.offline) { - throw error; - } - return dependencyProvider.solveOnline(elmJson, extra); + try { + return JSON.parse(dependencyProvider.solveOffline(elmJson, extra)); + } catch (error) { + if (options.offline) { + throw error; } + return JSON.parse(dependencyProvider.solveOnline(elmJson, extra)); + } } function nextPatchVersion(version) { From 9ee9215b7e4d4bde0c595de14afd7d15c83e3f88 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:42:43 +0200 Subject: [PATCH 24/98] Solve using elm-solve-deps in TemplateDependencies.get --- lib/build.js | 17 ++---------- lib/template-dependencies.js | 52 ++++++++++++++---------------------- 2 files changed, 22 insertions(+), 47 deletions(-) diff --git a/lib/build.js b/lib/build.js index 9557346ab..084ee5b01 100644 --- a/lib/build.js +++ b/lib/build.js @@ -159,13 +159,7 @@ I can help set you up with an initial configuration if you run ${chalk.magenta(' const buildResult = await Promise.all([ getElmBinary(options), - createTemplateProject( - options, - reviewElmJsonPath, - userSrc, - buildFolder, - reviewElmJson - ) + createTemplateProject(options, userSrc, buildFolder, reviewElmJson) ]).then(([elmBinary]) => { Debug.log('Compiling review application'); Benchmark.start(options, 'Compile review project'); @@ -257,7 +251,6 @@ async function buildFromGitHubTemplate(options, template) { getElmBinary(options), createTemplateProject( options, - reviewElmJsonPath, buildFolder, path.join(buildFolder, 'project'), reviewElmJsonWithReplacedParentDirectories @@ -302,7 +295,6 @@ async function buildFromGitHubTemplate(options, template) { */ async function createTemplateProject( options, - reviewElmJsonPath, userSrc, projectFolder, reviewElmJson @@ -313,12 +305,7 @@ async function createTemplateProject( // Load review project's elm.json file contents const [dependencies, previousElmJson] = await Promise.all([ - TemplateDependencies.get( - options, - reviewElmJson['elm-version'], - reviewElmJson.dependencies, - reviewElmJsonPath - ), + TemplateDependencies.get(options, reviewElmJson), FS.readFile(elmJsonPath).catch(() => null) ]); const finalElmJson = updateSourceDirectories(options, userSrc, { diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 64dfd6758..3b5600d15 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -27,47 +27,35 @@ module.exports = { // GET -async function get(options, elmVersion, elmJsonDependencies, pathToElmJson) { - const dependencyHash = Hash.hash(JSON.stringify(elmJsonDependencies)); +async function get(options, elmJson) { + const dependencyHash = Hash.hash(JSON.stringify(elmJson.dependencies)); const cacheKey = `${dependencyHash}${ options.localElmReviewSrc ? '-local' : '' }`; return Cache.getOrCompute(options.dependenciesCachePath(), cacheKey, () => - computeDependencies(options, elmVersion, pathToElmJson) + computeDependencies(options, elmJson) ); } -function computeDependencies(options, elmVersion, pathToElmJson) { - return spawnElmJsonAsync( - options, - [ - 'solve', - '--extra', - 'elm/json@1', - 'stil4m/elm-syntax@7', - 'elm/project-metadata-utils@1', - '--', - pathToElmJson - ], - (error) => { - throw new ErrorMessage.CustomError( - 'CONFIGURATION COMPILATION ERROR', - `I encountered a problem when solving dependencies: +function computeDependencies(options, elmJson) { + const extra = { + 'elm/json': '1.0.0 <= v < 2.0.0', + 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', + 'elm/project-metadata-utils': '1.0.0 <= v < 2.0.0' + }; -${formatElmJsonError(error, options)}`, - null - ); - } - ) - .then(JSON.parse) - .then((dependencies) => { - if (options.localElmReviewSrc) { - delete dependencies.direct['jfmengels/elm-review']; - delete dependencies.indirect['jfmengels/elm-review']; - } + const dependencies = solve( + options, + elmJson['elm-version'], + JSON.stringify(elmJson), + extra + ); + if (options.localElmReviewSrc) { + delete dependencies.direct['jfmengels/elm-review']; + delete dependencies.indirect['jfmengels/elm-review']; + } - return dependencies; - }); + return dependencies; } function formatElmJsonError(error, options) { From 872aefde0196c4d56b2e750cd490e77c8b0fe14f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:45:16 +0200 Subject: [PATCH 25/98] Remove dependency solver --- lib/dependency-solver.js | 34 ---------------------------------- lib/template-dependencies.js | 1 - 2 files changed, 35 deletions(-) delete mode 100644 lib/dependency-solver.js diff --git a/lib/dependency-solver.js b/lib/dependency-solver.js deleted file mode 100644 index 36bb09fa9..000000000 --- a/lib/dependency-solver.js +++ /dev/null @@ -1,34 +0,0 @@ -const elmSolveDeps = require('elm-solve-deps-wasm'); -const depsProvider = require('./dependency-provider-offline'); - -module.exports = { - addExact -}; - -function addExact(options, elmJson, dependency, version) { - initElmSolveDeps(); - - let dependencies; - - dependencies = elmSolveDeps.solve_deps( - elmJson, - false, - {[dependency]: `${version} <= v < ${nextPatchVersion(version)}`}, - depsProvider.fetchElmJson, - depsProvider.listAvailableVersions - ); - return JSON.parse(dependencies); -} - -let elmSolveDepsWasInitialized = false; -function initElmSolveDeps() { - if (!elmSolveDepsWasInitialized) { - elmSolveDepsWasInitialized = true; - elmSolveDeps.init(); - } -} - -function nextPatchVersion(version) { - const [major, minor, patch] = version.split('.'); - return `${major}.${minor}.${parseInt(patch, 10) + 1}`; -} diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 3b5600d15..2a8df2347 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -8,7 +8,6 @@ const Cache = require('./cache'); const FS = require('./fs-wrapper'); const ErrorMessage = require('./error-message'); const getExecutable = require('elm-tooling/getExecutable'); -const dependencySolver = require('./dependency-solver'); const DependencyProvider = require('./dependency-provider'); let dependencyProvider; From 20acb0872c306f3f7b4951546e5fba344afc7615 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 00:53:09 +0200 Subject: [PATCH 26/98] Pass useTest --- lib/dependency-provider.js | 6 ++++-- lib/template-dependencies.js | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index bad2e010e..757080d00 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -172,13 +172,14 @@ class DependencyProvider { // Solve dependencies completely offline, without any http request. solveOffline( elmJson /*: string */, + useTest /*: boolean */, extra /*: { [string]: string } */ ) /*: string */ { const lister = new OfflineAvailableVersionLister(); try { return wasm.solve_deps( elmJson, - false, + useTest, extra, fetchElmJsonOffline(this.elmVersion), (pkg) => lister.list(this.elmVersion, pkg) @@ -191,6 +192,7 @@ class DependencyProvider { // Solve dependencies with http requests when required. solveOnline( elmJson /*: string */, + useTest /*: boolean */, extra /*: { [string]: string } */ ) /*: string */ { const lister = new OnlineAvailableVersionLister( @@ -198,7 +200,7 @@ class DependencyProvider { this.elmVersion ); try { - return wasm.solve_deps(elmJson, false, extra, fetchElmJsonOnline, (pkg) => + return wasm.solve_deps(elmJson, useTest, extra, fetchElmJsonOnline, (pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 2a8df2347..66b5efefb 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -47,7 +47,8 @@ function computeDependencies(options, elmJson) { options, elmJson['elm-version'], JSON.stringify(elmJson), - extra + extra, + false ); if (options.localElmReviewSrc) { delete dependencies.direct['jfmengels/elm-review']; @@ -108,19 +109,19 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { )}` }; - return solve(options, elmVersion, elmJson, extra); + return solve(options, elmVersion, elmJson, extra, false); } -function solve(options, elmVersion, elmJson, extra) { +function solve(options, elmVersion, elmJson, extra, useTest /*: boolean */) { dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); try { - return JSON.parse(dependencyProvider.solveOffline(elmJson, extra)); + return JSON.parse(dependencyProvider.solveOffline(elmJson, useTest, extra)); } catch (error) { if (options.offline) { throw error; } - return JSON.parse(dependencyProvider.solveOnline(elmJson, extra)); + return JSON.parse(dependencyProvider.solveOnline(elmJson, useTest, extra)); } } From 3836223f0f0c763ed01cb00e41394458f8f501da Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 10:14:35 +0200 Subject: [PATCH 27/98] Initialize syncGetWorker when needed --- lib/dependency-provider.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 757080d00..670dc7b86 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -5,10 +5,7 @@ const ProjectJsonFiles = require('./project-json-files'); const SyncGet = require('./sync-get'); const collator = new Intl.Collator('en', {numeric: true}); // for sorting SemVer strings -// Initialization work done only once. -wasm.init(); -const syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */ = - SyncGet.startWorker(); +let syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */; // Cache of existing versions according to the package website. class OnlineVersionsCache { @@ -167,6 +164,8 @@ class DependencyProvider { constructor(elmVersion /*: string */) { this.elmVersion = elmVersion; + wasm.init(); + syncGetWorker = SyncGet.startWorker(); } // Solve dependencies completely offline, without any http request. @@ -207,6 +206,10 @@ class DependencyProvider { throw new Error(errorMessage); } } + + tearDown() { + syncGetWorker.shutDown(); + } } function fetchElmJsonOnline( From 0fa6a448d0f3e24dec4589515f54879a57b26ea2 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 10:15:28 +0200 Subject: [PATCH 28/98] Use elm-solve-deps to init a new project --- lib/init.js | 10 +++--- lib/template-dependencies.js | 63 ++++++++++++++---------------------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/lib/init.js b/lib/init.js index c93ffa585..ff512fc8a 100644 --- a/lib/init.js +++ b/lib/init.js @@ -158,12 +158,10 @@ async function createElmJson(options, directory) { } }; - const pathToElmJson = path.join(directory, 'elm.json'); - fs.writeFileSync(pathToElmJson, JSON.stringify(elmJson, null, 4)); - await TemplateDependencies.add(options, pathToElmJson); - - const elmJsonWithDeps = FS.readJsonFileSync(pathToElmJson); - fs.writeFileSync(pathToElmJson, JSON.stringify(elmJsonWithDeps, null, 4)); + fs.writeFileSync( + path.join(directory, 'elm.json'), + JSON.stringify(TemplateDependencies.add(options, elmJson), null, 4) + ); } function createReviewConfig(directory, template) { diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 66b5efefb..0dc66b073 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -6,6 +6,7 @@ const spawnAsync = require('cross-spawn'); const Hash = require('./hash'); const Cache = require('./cache'); const FS = require('./fs-wrapper'); +const MinVersion = require("./min-version"); const ErrorMessage = require('./error-message'); const getExecutable = require('elm-tooling/getExecutable'); const DependencyProvider = require('./dependency-provider'); @@ -132,49 +133,35 @@ function nextPatchVersion(version) { // ADD -async function add(options, pathToElmJson) { - await spawnElmJsonAsync( - options, - [ - 'install', - '--yes', - 'elm/core@1', - 'jfmengels/elm-review@2', - 'stil4m/elm-syntax@7', - '--', - pathToElmJson - ], - (error) => { - throw new ErrorMessage.CustomError( - 'CONFIGURATION COMPILATION ERROR', - `I encountered a problem when adding base dependencies: +function add(options, elmJson) { + const extra = { + 'elm/core': '1.0.0 <= v < 2.0.0', + 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', + 'jfmengels/elm-review': MinVersion.supportedRange + }; + const extraTestDependencies = { + 'elm-explorations/test': '2.0.0 <= v < 3.0.0' + }; -${formatElmJsonError(error, options)}`, - null - ); - } + elmJson.dependencies = solve( + options, + elmJson['elm-version'], + JSON.stringify(elmJson), + extra, + false ); - return spawnElmJsonAsync( + elmJson.dependencies = solve( options, - [ - 'install', - '--test', - '--yes', - 'elm-explorations/test@2', - '--', - pathToElmJson - ], - (error) => { - throw new ErrorMessage.CustomError( - 'CONFIGURATION COMPILATION ERROR', - `I encountered a problem when adding test dependencies: - -${formatElmJsonError(error, options)}`, - null - ); - } + elmJson['elm-version'], + JSON.stringify(elmJson), + extraTestDependencies, + true ); + + dependencyProvider.tearDown(); + + return elmJson; } // UPDATE From 1087e1389bcea4e7fd25d923f321196e04a5cf89 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 17:15:15 +0200 Subject: [PATCH 29/98] Use elm-solve-deps for --template --- lib/remote-template.js | 4 +--- lib/template-dependencies.js | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/lib/remote-template.js b/lib/remote-template.js index ba75b5d8b..12b88b1fc 100644 --- a/lib/remote-template.js +++ b/lib/remote-template.js @@ -94,9 +94,7 @@ async function getRemoteElmJson( elmJson['test-dependencies'].direct = {}; elmJson['test-dependencies'].indirect = {}; - await FS.mkdirp(path.dirname(reviewElmJsonPath)); - await FS.writeJson(reviewElmJsonPath, elmJson, 4); - return TemplateDependencies.update(options, reviewElmJsonPath); + return TemplateDependencies.update(options, reviewElmJsonPath, elmJson); } async function downloadTemplateElmJson(template, commit) { diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 0dc66b073..61d8fbfdc 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -166,23 +166,22 @@ function add(options, elmJson) { // UPDATE -async function update(options, pathToElmJson) { - await spawnElmJsonAsync( - options, - ['upgrade', '--yes', pathToElmJson], - (error) => { - throw new ErrorMessage.CustomError( - 'CONFIGURATION COMPILATION ERROR', - `I encountered a problem when attempting to update the dependencies: +async function update(options, pathToElmJson, elmJson) { + const extra = { + 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', + 'jfmengels/elm-review': MinVersion.supportedRange + }; -${formatElmJsonError(error, options)}`, - null - ); - } + elmJson.dependencies = solve( + options, + elmJson['elm-version'], + JSON.stringify(elmJson), + extra, + false ); - const elmJson = await FS.readJsonFile(pathToElmJson); if (options.subcommand === 'init') { + dependencyProvider.tearDown(); await FS.writeJson(pathToElmJson, elmJson, 4); } From b8154f5b7901450d57d1d4c59f843f3298c94462 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 17:27:05 +0200 Subject: [PATCH 30/98] Remove elm-json related code from project --- lib/template-dependencies.js | 150 +---------------------------------- test/run.sh | 2 - 2 files changed, 1 insertion(+), 151 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 61d8fbfdc..fb0646622 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -2,13 +2,10 @@ * Credit goes to @zwilias, from his PR here https://github.com/rtfeldman/node-test-runner/pull/356/files */ -const spawnAsync = require('cross-spawn'); const Hash = require('./hash'); const Cache = require('./cache'); const FS = require('./fs-wrapper'); const MinVersion = require("./min-version"); -const ErrorMessage = require('./error-message'); -const getExecutable = require('elm-tooling/getExecutable'); const DependencyProvider = require('./dependency-provider'); let dependencyProvider; @@ -59,29 +56,6 @@ function computeDependencies(options, elmJson) { return dependencies; } -function formatElmJsonError(error, options) { - if (error.stderr === undefined) { - return error.message; - } - - const stderrMessage = error.stderr.toString().trim(); - const exec = /^([^]+\n)?--\s([A-Z ]+)\s-*\n\n([^]+)$/.exec(stderrMessage); - if (exec === null) { - return `${error.message}\n\n${ - stderrMessage === '' ? '(empty stderr)' : stderrMessage - }`; - } - - const [, before, title, message] = exec; - return ErrorMessage.formatHuman( - options.debug, - new ErrorMessage.CustomError( - title, - before === undefined ? message : `${message}\n\n${before}` - ) - ); -} - // ADD ELM-SYNTAX function addElmSyntax(options, elmVersion, elmSyntaxVersion) { @@ -186,126 +160,4 @@ async function update(options, pathToElmJson, elmJson) { } return elmJson; -} - -// SPAWNING - -let elmJsonPromise; - -/** - * Run elm-json - * @param {Options} options - * @param {string[]} args - * @param {(Error) => Error} onError - * @returns {Promise} - */ -function spawnElmJsonAsync(options, args, onError) { - if (elmJsonPromise === undefined) { - elmJsonPromise = getExecutable({ - name: 'elm-json', - version: '^0.2.10', - onProgress: (percentage) => { - const message = `Downloading elm-json... ${Math.round( - percentage * 100 - )}%`; - - if (options.report !== 'json' || options.debug) { - process.stderr.write( - percentage >= 1 - ? `${'Working...'.padEnd(message.length, ' ')}\r` - : `${message}\r` - ); - } - } - }).catch((error) => { - throw new ErrorMessage.CustomError( - // prettier-ignore - 'PROBLEM INSTALLING elm-json', - // prettier-ignore - `I need a tool called elm-json for some of my inner workings, -but there was some trouble installing it. This is what we know: - -${error.message}` - ); - }); - } - - return elmJsonPromise - .then( - (elmJsonCLI) => - new Promise((resolve, reject) => { - const child = spawnAsync(elmJsonCLI, args, { - silent: true, - env: process.env - }); - let stdout = ''; - let stderr = ''; - - child.on('error', reject); - - child.stdout.on('data', (chunk) => { - stdout += chunk.toString(); - }); - - child.stderr.on('data', (chunk) => { - stderr += chunk.toString(); - }); - - child.on('close', (code, signal) => { - if (code === 0) { - resolve(stdout); - } else { - /** @type {TemplateDependenciesError} */ - const error = new Error( - `elm-json exited with ${exitReason( - code, - signal - )}\n\n${stdout}\n\n${stderr}` - ); - error.stderr = stderr; - reject(error); - } - }); - }) - ) - .catch((error) => { - if ( - error && - error.message && - error.message.startsWith('phase: retrieve') - ) { - return Promise.reject( - new ErrorMessage.CustomError( - // prettier-ignore - 'MISSING INTERNET ACCESS', - // prettier-ignore - `I’m sorry, but it looks like you don’t have Internet access at the moment. -I require it for some of my inner workings. - -Please connect to the Internet and try again. After that, as long as you don’t -change your configuration or remove \`elm-stuff/\`, you should be able to go -offline again.` - ) - ); - } - - return Promise.reject(onError(error)); - }); -} - -/** - * @param {number | null} code - * @param {string | null} signal - * @returns {string} - */ -function exitReason(code, signal) { - if (code !== null) { - return `exit code ${code}`; - } - - if (signal !== null) { - return `signal ${signal}`; - } - - return 'unknown reason'; -} +} \ No newline at end of file diff --git a/test/run.sh b/test/run.sh index 978cd82b5..737c82615 100755 --- a/test/run.sh +++ b/test/run.sh @@ -35,7 +35,6 @@ function runCommandAndCompareToSnapshot { eval "$LOCAL_COMMAND$AUTH --FOR-TESTS $ARGS" 2>&1 \ | $REPLACE_SCRIPT \ - | grep -v "Downloading elm-json" \ > "$TMP/$FILE" if [ "$(diff "$TMP/$FILE" "$SNAPSHOTS/$FILE")" != "" ] then @@ -60,7 +59,6 @@ function runAndRecord { echo -e "\x1B[33m- $TITLE\x1B[0m: \x1B[34m elm-review --FOR-TESTS $ARGS\x1B[0m" eval "$LOCAL_COMMAND$AUTH --FOR-TESTS $ARGS" 2>&1 \ | $REPLACE_SCRIPT \ - | grep -v "Downloading elm-json" \ > "$SNAPSHOTS/$FILE" } From 09d63424b82f9f54c416afecd8007888a564a40a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 17:34:07 +0200 Subject: [PATCH 31/98] Format --- lib/dependency-provider.js | 8 ++++++-- lib/template-dependencies.js | 14 +++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 670dc7b86..00726c7c5 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -199,8 +199,12 @@ class DependencyProvider { this.elmVersion ); try { - return wasm.solve_deps(elmJson, useTest, extra, fetchElmJsonOnline, (pkg) => - lister.list(this.elmVersion, pkg) + return wasm.solve_deps( + elmJson, + useTest, + extra, + fetchElmJsonOnline, + (pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { throw new Error(errorMessage); diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index fb0646622..87c19ea0b 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -5,7 +5,7 @@ const Hash = require('./hash'); const Cache = require('./cache'); const FS = require('./fs-wrapper'); -const MinVersion = require("./min-version"); +const MinVersion = require('./min-version'); const DependencyProvider = require('./dependency-provider'); let dependencyProvider; @@ -147,11 +147,11 @@ async function update(options, pathToElmJson, elmJson) { }; elmJson.dependencies = solve( - options, - elmJson['elm-version'], - JSON.stringify(elmJson), - extra, - false + options, + elmJson['elm-version'], + JSON.stringify(elmJson), + extra, + false ); if (options.subcommand === 'init') { @@ -160,4 +160,4 @@ async function update(options, pathToElmJson, elmJson) { } return elmJson; -} \ No newline at end of file +} From 00b28e4e36794d44086638ec7af022a0efadfe0b Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 18:20:50 +0200 Subject: [PATCH 32/98] Have some commands try online first --- lib/template-dependencies.js | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 87c19ea0b..752ec5811 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -46,6 +46,7 @@ function computeDependencies(options, elmJson) { elmJson['elm-version'], JSON.stringify(elmJson), extra, + false, false ); if (options.localElmReviewSrc) { @@ -84,19 +85,35 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { )}` }; - return solve(options, elmVersion, elmJson, extra, false); + return solve(options, elmVersion, elmJson, extra, false, false); } -function solve(options, elmVersion, elmJson, extra, useTest /*: boolean */) { +function solve( + options, + elmVersion, + elmJson, + extra, + useTest /*: boolean */, + onlineFirst /*: boolean */ +) { dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); try { - return JSON.parse(dependencyProvider.solveOffline(elmJson, useTest, extra)); + return JSON.parse( + onlineFirst && !options.offline + ? dependencyProvider.solveOnline(elmJson, useTest, extra) + : dependencyProvider.solveOffline(elmJson, useTest, extra) + ); } catch (error) { if (options.offline) { throw error; } - return JSON.parse(dependencyProvider.solveOnline(elmJson, useTest, extra)); + + return JSON.parse( + onlineFirst + ? dependencyProvider.solveOffline(elmJson, useTest, extra) + : dependencyProvider.solveOnline(elmJson, useTest, extra) + ); } } @@ -122,7 +139,8 @@ function add(options, elmJson) { elmJson['elm-version'], JSON.stringify(elmJson), extra, - false + false, + true ); elmJson.dependencies = solve( @@ -130,6 +148,7 @@ function add(options, elmJson) { elmJson['elm-version'], JSON.stringify(elmJson), extraTestDependencies, + true, true ); @@ -151,7 +170,8 @@ async function update(options, pathToElmJson, elmJson) { elmJson['elm-version'], JSON.stringify(elmJson), extra, - false + false, + true ); if (options.subcommand === 'init') { From 15e54bab05493447215b1fc3fa6a77996038cd44 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 22:08:10 +0200 Subject: [PATCH 33/98] Fix online fetching not working --- lib/dependency-provider.js | 39 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 00726c7c5..eecab56bf 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -203,7 +203,7 @@ class DependencyProvider { elmJson, useTest, extra, - fetchElmJsonOnline, + fetchElmJsonOnline(this.elmVersion), (pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { @@ -216,25 +216,24 @@ class DependencyProvider { } } -function fetchElmJsonOnline( - pkg /*: string */, - version /*: string */ -) /*: string */ { - try { - return fetchElmJsonOffline(pkg, version); - } catch (_) { - // `fetchElmJsonOffline` can only fail in ways that are either expected - // (such as file does not exist or no permissions) - // or because there was an error parsing `pkg` and `version`. - // In such case, this will throw again with `cacheElmJsonPath()` so it's fine. - const remoteUrl = remoteElmJsonUrl(pkg, version); - const elmJson = syncGetWorker.get(remoteUrl); - const cachePath = cacheElmJsonPath(pkg, version); - const parentDir = path.dirname(cachePath); - fs.mkdirSync(parentDir, {recursive: true}); - fs.writeFileSync(cachePath, elmJson); - return elmJson; - } +function fetchElmJsonOnline(elmVersion) { + return (pkg /*: string */, version /*: string */) /*: string */ => { + try { + return fetchElmJsonOffline(elmVersion)(pkg, version); + } catch (_) { + // `fetchElmJsonOffline` can only fail in ways that are either expected + // (such as file does not exist or no permissions) + // or because there was an error parsing `pkg` and `version`. + // In such case, this will throw again with `cacheElmJsonPath()` so it's fine. + const remoteUrl = remoteElmJsonUrl(pkg, version); + const elmJson = syncGetWorker.get(remoteUrl); + const cachePath = cacheElmJsonPath(pkg, version); + const parentDir = path.dirname(cachePath); + fs.mkdirSync(parentDir, {recursive: true}); + fs.writeFileSync(cachePath, elmJson); + return elmJson; + } + }; } function fetchElmJsonOffline(elmVersion) { From de663ed297b1120426c765be74d35f5dbe12bda3 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 22:21:03 +0200 Subject: [PATCH 34/98] Rename TemplateDependencies.add to createNewReviewElmJson --- lib/init.js | 6 +++++- lib/template-dependencies.js | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/init.js b/lib/init.js index ff512fc8a..892e140e5 100644 --- a/lib/init.js +++ b/lib/init.js @@ -160,7 +160,11 @@ async function createElmJson(options, directory) { fs.writeFileSync( path.join(directory, 'elm.json'), - JSON.stringify(TemplateDependencies.add(options, elmJson), null, 4) + JSON.stringify( + TemplateDependencies.createNewReviewElmJson(options, elmJson), + null, + 4 + ) ); } diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 752ec5811..04b532607 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -17,7 +17,7 @@ let dependencyProvider; module.exports = { get, - add, + createNewReviewElmJson, update, addElmSyntax }; @@ -124,7 +124,7 @@ function nextPatchVersion(version) { // ADD -function add(options, elmJson) { +function createNewReviewElmJson(options, elmJson) { const extra = { 'elm/core': '1.0.0 <= v < 2.0.0', 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', From 1fac3215e4cb025ed94fb02f90507dff3e03a29b Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 22:21:48 +0200 Subject: [PATCH 35/98] Move definition of elmJson --- lib/init.js | 16 +--------------- lib/template-dependencies.js | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/init.js b/lib/init.js index 892e140e5..bf32ecd1f 100644 --- a/lib/init.js +++ b/lib/init.js @@ -144,24 +144,10 @@ async function createElmJson(options, directory) { const elmBinary = await getElmBinary(options); const elmVersion = await getElmVersion(elmBinary); - const elmJson = { - type: 'application', - 'source-directories': ['src'], - 'elm-version': elmVersion || '0.19.1', - dependencies: { - direct: {}, - indirect: {} - }, - 'test-dependencies': { - direct: {}, - indirect: {} - } - }; - fs.writeFileSync( path.join(directory, 'elm.json'), JSON.stringify( - TemplateDependencies.createNewReviewElmJson(options, elmJson), + TemplateDependencies.createNewReviewElmJson(options, elmVersion), null, 4 ) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 04b532607..bf98d9572 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -124,7 +124,21 @@ function nextPatchVersion(version) { // ADD -function createNewReviewElmJson(options, elmJson) { +function createNewReviewElmJson(options, elmVersion) { + const elmJson = { + type: 'application', + 'source-directories': ['src'], + 'elm-version': elmVersion || '0.19.1', + dependencies: { + direct: {}, + indirect: {} + }, + 'test-dependencies': { + direct: {}, + indirect: {} + } + }; + const extra = { 'elm/core': '1.0.0 <= v < 2.0.0', 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', From 5dc9f53d0c4de11209c97a56b4f8ee47543bec88 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 22:26:15 +0200 Subject: [PATCH 36/98] Compute test dependencies first --- lib/template-dependencies.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index bf98d9572..5391c7159 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -139,30 +139,28 @@ function createNewReviewElmJson(options, elmVersion) { } }; - const extra = { - 'elm/core': '1.0.0 <= v < 2.0.0', - 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', - 'jfmengels/elm-review': MinVersion.supportedRange - }; - const extraTestDependencies = { - 'elm-explorations/test': '2.0.0 <= v < 3.0.0' - }; - - elmJson.dependencies = solve( + elmJson['test-dependencies'] = solve( options, elmJson['elm-version'], JSON.stringify(elmJson), - extra, - false, - true + { + 'elm/core': '1.0.0 <= v < 2.0.0', + 'elm-explorations/test': '2.0.0 <= v < 3.0.0' + }, + true, + false ); elmJson.dependencies = solve( options, elmJson['elm-version'], JSON.stringify(elmJson), - extraTestDependencies, - true, + { + 'elm/core': '1.0.0 <= v < 2.0.0', + 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', + 'jfmengels/elm-review': MinVersion.supportedRange + }, + false, true ); From b3168c2b86d1b748c6cfd6a155a51a8bf34f1df1 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 22:36:25 +0200 Subject: [PATCH 37/98] Fix generation of review/elm.json during init --- lib/template-dependencies.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 5391c7159..f64e048ac 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -139,7 +139,7 @@ function createNewReviewElmJson(options, elmVersion) { } }; - elmJson['test-dependencies'] = solve( + const testDependencies = solve( options, elmJson['elm-version'], JSON.stringify(elmJson), @@ -147,7 +147,7 @@ function createNewReviewElmJson(options, elmVersion) { 'elm/core': '1.0.0 <= v < 2.0.0', 'elm-explorations/test': '2.0.0 <= v < 3.0.0' }, - true, + false, false ); @@ -164,11 +164,29 @@ function createNewReviewElmJson(options, elmVersion) { true ); + elmJson['test-dependencies'].direct = filterOutDuplicateDependencies( + testDependencies.direct, + elmJson.dependencies.direct, + {} + ); + elmJson['test-dependencies'].indirect = filterOutDuplicateDependencies( + testDependencies.indirect, + elmJson.dependencies.indirect + ); + dependencyProvider.tearDown(); return elmJson; } +function filterOutDuplicateDependencies(testDependencies, regularDependencies) { + return Object.fromEntries( + Object.entries(testDependencies).filter( + ([pkg, _]) => !regularDependencies[pkg] + ) + ); +} + // UPDATE async function update(options, pathToElmJson, elmJson) { From 658365c59cdc49f7db5b7b4dd75a2e41d7002222 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 22:59:37 +0200 Subject: [PATCH 38/98] Set dependency provider to null when tearing it down --- lib/template-dependencies.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index f64e048ac..a8d0d9c2a 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -8,7 +8,7 @@ const FS = require('./fs-wrapper'); const MinVersion = require('./min-version'); const DependencyProvider = require('./dependency-provider'); -let dependencyProvider; +let dependencyProvider = null; /** * @typedef { import("./types/options").Options } Options @@ -174,7 +174,7 @@ function createNewReviewElmJson(options, elmVersion) { elmJson.dependencies.indirect ); - dependencyProvider.tearDown(); + teardownDependenciesProvider(); return elmJson; } @@ -205,9 +205,14 @@ async function update(options, pathToElmJson, elmJson) { ); if (options.subcommand === 'init') { - dependencyProvider.tearDown(); + teardownDependenciesProvider(); await FS.writeJson(pathToElmJson, elmJson, 4); } return elmJson; } + +function teardownDependenciesProvider() { + dependencyProvider.tearDown(); + dependencyProvider = null; +} \ No newline at end of file From 3e74a4ea0afff43cf88636c506342356f76cd74f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 23:05:47 +0200 Subject: [PATCH 39/98] Improve teardown of dependencies provider --- lib/dependency-provider.js | 10 ++++++++-- lib/template-dependencies.js | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index eecab56bf..535b7911e 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -5,7 +5,9 @@ const ProjectJsonFiles = require('./project-json-files'); const SyncGet = require('./sync-get'); const collator = new Intl.Collator('en', {numeric: true}); // for sorting SemVer strings -let syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */; +let inited = false; +let syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */ = + null; // Cache of existing versions according to the package website. class OnlineVersionsCache { @@ -164,7 +166,10 @@ class DependencyProvider { constructor(elmVersion /*: string */) { this.elmVersion = elmVersion; - wasm.init(); + if (!inited) { + wasm.init(); + inited = true; + } syncGetWorker = SyncGet.startWorker(); } @@ -213,6 +218,7 @@ class DependencyProvider { tearDown() { syncGetWorker.shutDown(); + syncGetWorker = null; } } diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index a8d0d9c2a..715334631 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -215,4 +215,4 @@ async function update(options, pathToElmJson, elmJson) { function teardownDependenciesProvider() { dependencyProvider.tearDown(); dependencyProvider = null; -} \ No newline at end of file +} From 13ef1da2e2ffc60a1ad1aa9ef4a3bb0204e6bea0 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 23:07:50 +0200 Subject: [PATCH 40/98] Fix process staying alive? --- lib/template-dependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 715334631..44e77e883 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -204,7 +204,7 @@ async function update(options, pathToElmJson, elmJson) { true ); - if (options.subcommand === 'init') { + if (options.subcommand === 'init' || options.subcommand === 'new-package') { teardownDependenciesProvider(); await FS.writeJson(pathToElmJson, elmJson, 4); } From 0e5af93754a59af12a1f2f3befe74ad4d4b8355f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 23:18:31 +0200 Subject: [PATCH 41/98] Remove useTest argument --- lib/dependency-provider.js | 6 ++---- lib/template-dependencies.js | 13 ++++--------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 535b7911e..fdf3610af 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -176,14 +176,13 @@ class DependencyProvider { // Solve dependencies completely offline, without any http request. solveOffline( elmJson /*: string */, - useTest /*: boolean */, extra /*: { [string]: string } */ ) /*: string */ { const lister = new OfflineAvailableVersionLister(); try { return wasm.solve_deps( elmJson, - useTest, + false, extra, fetchElmJsonOffline(this.elmVersion), (pkg) => lister.list(this.elmVersion, pkg) @@ -196,7 +195,6 @@ class DependencyProvider { // Solve dependencies with http requests when required. solveOnline( elmJson /*: string */, - useTest /*: boolean */, extra /*: { [string]: string } */ ) /*: string */ { const lister = new OnlineAvailableVersionLister( @@ -206,7 +204,7 @@ class DependencyProvider { try { return wasm.solve_deps( elmJson, - useTest, + false, extra, fetchElmJsonOnline(this.elmVersion), (pkg) => lister.list(this.elmVersion, pkg) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 44e77e883..fff56d7fd 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -46,7 +46,6 @@ function computeDependencies(options, elmJson) { elmJson['elm-version'], JSON.stringify(elmJson), extra, - false, false ); if (options.localElmReviewSrc) { @@ -93,7 +92,6 @@ function solve( elmVersion, elmJson, extra, - useTest /*: boolean */, onlineFirst /*: boolean */ ) { dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); @@ -101,8 +99,8 @@ function solve( try { return JSON.parse( onlineFirst && !options.offline - ? dependencyProvider.solveOnline(elmJson, useTest, extra) - : dependencyProvider.solveOffline(elmJson, useTest, extra) + ? dependencyProvider.solveOnline(elmJson, extra) + : dependencyProvider.solveOffline(elmJson, extra) ); } catch (error) { if (options.offline) { @@ -111,8 +109,8 @@ function solve( return JSON.parse( onlineFirst - ? dependencyProvider.solveOffline(elmJson, useTest, extra) - : dependencyProvider.solveOnline(elmJson, useTest, extra) + ? dependencyProvider.solveOffline(elmJson, extra) + : dependencyProvider.solveOnline(elmJson, extra) ); } } @@ -147,7 +145,6 @@ function createNewReviewElmJson(options, elmVersion) { 'elm/core': '1.0.0 <= v < 2.0.0', 'elm-explorations/test': '2.0.0 <= v < 3.0.0' }, - false, false ); @@ -160,7 +157,6 @@ function createNewReviewElmJson(options, elmVersion) { 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', 'jfmengels/elm-review': MinVersion.supportedRange }, - false, true ); @@ -200,7 +196,6 @@ async function update(options, pathToElmJson, elmJson) { elmJson['elm-version'], JSON.stringify(elmJson), extra, - false, true ); From a05f7504a4736357e695e6e64ddaee876597b86a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Fri, 13 Oct 2023 23:26:20 +0200 Subject: [PATCH 42/98] Fix upgrade of dependencies --- lib/template-dependencies.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index fff56d7fd..457bfc0a7 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -191,6 +191,9 @@ async function update(options, pathToElmJson, elmJson) { 'jfmengels/elm-review': MinVersion.supportedRange }; + delete elmJson.dependencies.direct['jfmengels/elm-review']; + delete elmJson.dependencies.direct['stil4m/elm-syntax']; + elmJson.dependencies = solve( options, elmJson['elm-version'], From 4e6d4408ca4f54d36daef25c401e8531afdcd4a8 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 14:30:52 +0200 Subject: [PATCH 43/98] Move validation of jfmengels/elm-review version for templates --- lib/min-version.js | 1 + lib/remote-template.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/min-version.js b/lib/min-version.js index 144cce7fd..73485b494 100644 --- a/lib/min-version.js +++ b/lib/min-version.js @@ -16,6 +16,7 @@ const supportedRange = `${minimalVersion.major}.${minimalVersion.minor}.0 <= v < module.exports = { updateToAtLeastMinimalVersion, validate, + minimalVersion, supportedRange }; diff --git a/lib/remote-template.js b/lib/remote-template.js index 12b88b1fc..2583cc2e6 100644 --- a/lib/remote-template.js +++ b/lib/remote-template.js @@ -80,15 +80,21 @@ async function getRemoteElmJson( } const elmJson = await downloadTemplateElmJson(template, commit); + if ( elmJson.dependencies && elmJson.dependencies.direct && elmJson.dependencies.direct['jfmengels/elm-review'] ) { + const packageVersion = elmJson.dependencies.direct['jfmengels/elm-review']; + const [major] = packageVersion.split('.'); + + if (Number.parseInt(major, 10) !== MinVersion.minimalVersion.major) { + // Major version for which the configuration exists is not compatible + MinVersion.validate(options, reviewElmJsonPath, packageVersion); + } elmJson.dependencies.direct['jfmengels/elm-review'] = - MinVersion.updateToAtLeastMinimalVersion( - elmJson.dependencies.direct['jfmengels/elm-review'] - ); + MinVersion.updateToAtLeastMinimalVersion(packageVersion); } elmJson['test-dependencies'].direct = {}; From 331bc1e8981012252beeb37805af9e503b574a74 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 18:40:58 +0200 Subject: [PATCH 44/98] Only save elm.json file during init, not new-package --- lib/template-dependencies.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 457bfc0a7..2e7f44f3d 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -204,6 +204,9 @@ async function update(options, pathToElmJson, elmJson) { if (options.subcommand === 'init' || options.subcommand === 'new-package') { teardownDependenciesProvider(); + } + + if (options.subcommand === 'init') { await FS.writeJson(pathToElmJson, elmJson, 4); } From 8db019dd4beba862c79d02fb75cbcf574e79df69 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 21:33:43 +0200 Subject: [PATCH 45/98] Upgrade dependencies --- lib/template-dependencies.js | 11 ++- .../remote-without-elm-review-json.txt | 67 +++++++++++++++++-- .../remote-without-elm-review-ndjson.txt | 9 +-- .../remote-without-elm-review.txt | 17 +++-- 4 files changed, 85 insertions(+), 19 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 2e7f44f3d..c6b90071c 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -115,6 +115,11 @@ function solve( } } +function nextMajorVersion(version) { + const [major] = version.split('.'); + return `${parseInt(major, 10) + 1}.0.0`; +} + function nextPatchVersion(version) { const [major, minor, patch] = version.split('.'); return `${major}.${minor}.${parseInt(patch, 10) + 1}`; @@ -191,13 +196,17 @@ async function update(options, pathToElmJson, elmJson) { 'jfmengels/elm-review': MinVersion.supportedRange }; + Object.entries(elmJson.dependencies.direct).forEach(([pkg, version]) => { + extra[pkg] = `${version} <= v < ${nextMajorVersion(version)}`; + }); + delete elmJson.dependencies.direct['jfmengels/elm-review']; delete elmJson.dependencies.direct['stil4m/elm-syntax']; elmJson.dependencies = solve( options, elmJson['elm-version'], - JSON.stringify(elmJson), + JSON.stringify({...elmJson, dependencies: {direct: {}, indirect: {}}}), extra, true ); diff --git a/test/run-snapshots/remote-without-elm-review-json.txt b/test/run-snapshots/remote-without-elm-review-json.txt index 3a3a472d5..1973276ea 100644 --- a/test/run-snapshots/remote-without-elm-review-json.txt +++ b/test/run-snapshots/remote-without-elm-review-json.txt @@ -1,8 +1,63 @@ { - "type": "error", - "title": "MISSING ELM-REVIEW DEPENDENCY", - "path": "/test/project-with-errors/review/elm.json", - "message": [ - "The template's configuration does not include jfmengels/elm-review in its direct dependencies.\n\nMaybe you chose the wrong template, or the template is malformed. If the latter is the case, please inform the template author." - ] + "type": "review-errors", + "errors": [ + { + "path": "src/Main.elm", + "errors": [ + { + "rule": "NoUnused.Variables", + "message": "Imported variable `span` is not used", + "ruleLink": "https://package.elm-lang.org/packages/jfmengels/elm-review-unused/1.2.0/NoUnused-Variables", + "details": [ + "You should either use this value somewhere, or remove it at the location I pointed at." + ], + "region": { + "start": { + "line": 11, + "column": 11 + }, + "end": { + "line": 11, + "column": 15 + } + }, + "fix": [ + { + "range": { + "start": { + "line": 9, + "column": 14 + }, + "end": { + "line": 11, + "column": 15 + } + }, + "string": "" + } + ], + "formatted": [ + { + "string": "(fix) ", + "color": "#33BBC8" + }, + { + "string": "NoUnused.Variables", + "color": "#FF0000", + "href": "https://package.elm-lang.org/packages/jfmengels/elm-review-unused/1.2.0/NoUnused-Variables" + }, + ": Imported variable `span` is not used\n\n10| -- span is unused\n11| , span\n ", + { + "string": "^^^^", + "color": "#FF0000" + }, + "\n12| , text\n\nYou should either use this value somewhere, or remove it at the location I pointed at." + ], + "suppressed": false, + "originallySuppressed": false + } + ] + } + ], + "extracts": {} } diff --git a/test/run-snapshots/remote-without-elm-review-ndjson.txt b/test/run-snapshots/remote-without-elm-review-ndjson.txt index 3a3a472d5..f42c6599d 100644 --- a/test/run-snapshots/remote-without-elm-review-ndjson.txt +++ b/test/run-snapshots/remote-without-elm-review-ndjson.txt @@ -1,8 +1 @@ -{ - "type": "error", - "title": "MISSING ELM-REVIEW DEPENDENCY", - "path": "/test/project-with-errors/review/elm.json", - "message": [ - "The template's configuration does not include jfmengels/elm-review in its direct dependencies.\n\nMaybe you chose the wrong template, or the template is malformed. If the latter is the case, please inform the template author." - ] -} +{"path":"src/Main.elm","rule":"NoUnused.Variables","message":"Imported variable `span` is not used","ruleLink":"https://package.elm-lang.org/packages/jfmengels/elm-review-unused/1.2.0/NoUnused-Variables","details":["You should either use this value somewhere, or remove it at the location I pointed at."],"region":{"start":{"line":11,"column":11},"end":{"line":11,"column":15}},"fix":[{"range":{"start":{"line":9,"column":14},"end":{"line":11,"column":15}},"string":""}],"formatted":[{"string":"(fix) ","color":"#33BBC8"},{"string":"NoUnused.Variables","color":"#FF0000","href":"https://package.elm-lang.org/packages/jfmengels/elm-review-unused/1.2.0/NoUnused-Variables"},": Imported variable `span` is not used\n\n10| -- span is unused\n11| , span\n ",{"string":"^^^^","color":"#FF0000"},"\n12| , text\n\nYou should either use this value somewhere, or remove it at the location I pointed at."],"suppressed":false,"originallySuppressed":false} diff --git a/test/run-snapshots/remote-without-elm-review.txt b/test/run-snapshots/remote-without-elm-review.txt index fe8259b8e..8f0377f31 100644 --- a/test/run-snapshots/remote-without-elm-review.txt +++ b/test/run-snapshots/remote-without-elm-review.txt @@ -1,8 +1,17 @@ - Fetching template information -✖ Fetching template information --- MISSING ELM-REVIEW DEPENDENCY ----------------------------------------------- +✔ Build finished! Now reviewing your project... +-- ELM-REVIEW ERROR ----------------------------------------- src/Main.elm:11:11 -The template's configuration does not include jfmengels/elm-review in its direct dependencies. +(fix) NoUnused.Variables: Imported variable `span` is not used -Maybe you chose the wrong template, or the template is malformed. If the latter is the case, please inform the template author. +10| -- span is unused +11| , span + ^^^^ +12| , text +You should either use this value somewhere, or remove it at the location I +pointed at. + +Errors marked with (fix) can be fixed automatically using `elm-review --fix`. + +I found 1 error in 1 file. From b3764ba5223693729a6b71ed4730288f2e930045 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 22:36:16 +0200 Subject: [PATCH 46/98] Convert types to TypeScript --- lib/dependency-provider.js | 229 +++++++++++++++++++++++------------ lib/sync-get-worker.js | 9 +- lib/sync-get.js | 12 +- lib/template-dependencies.js | 16 +-- 4 files changed, 174 insertions(+), 92 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index fdf3610af..8fbc7008e 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -5,15 +5,21 @@ const ProjectJsonFiles = require('./project-json-files'); const SyncGet = require('./sync-get'); const collator = new Intl.Collator('en', {numeric: true}); // for sorting SemVer strings -let inited = false; -let syncGetWorker /*: {| get: (string) => string, shutDown: () => void |} */ = - null; +/** @type {boolean} */ +let wasmWasInitialized = false; +/** @type {{ get: (string) => string, shutDown: () => void } | null} */ +let syncGetWorker = null; // Cache of existing versions according to the package website. class OnlineVersionsCache { - map /*: Map> */ = new Map(); - - update(elmVersion /*: string */) { + /** @type {Map>} */ + map = new Map(); + + /** + * @param {string} elmVersion + * @returns {void} + */ + update(elmVersion) { const pubgrubHome = path.join( ProjectJsonFiles.elmRoot, 'elm-review', @@ -44,11 +50,13 @@ class OnlineVersionsCache { this.updateWithRequestSince(cachePath, remotePackagesUrl); } - // Update the cache with a request to the package server. - updateWithRequestSince( - cachePath /*: string */, - remotePackagesUrl /*: string */ - ) /*: void */ { + /** Update the cache with a request to the package server. + * + * @param {string} cachePath + * @param {string} remotePackagesUrl + * @returns {void} + */ + updateWithRequestSince(cachePath, remotePackagesUrl) { // Count existing versions. let versionsCount = 0; for (const versions of this.map.values()) { @@ -57,6 +65,9 @@ class OnlineVersionsCache { // Complete cache with a remote call to the package server. const remoteUrl = remotePackagesUrl + '/since/' + (versionsCount - 1); // -1 to check if no package was deleted. + if (!syncGetWorker) { + return; + } const newVersions = JSON.parse(syncGetWorker.get(remoteUrl)); if (newVersions.length === 0) { // Reload from scratch since it means at least one package was deleted from the registry. @@ -90,7 +101,12 @@ class OnlineVersionsCache { } } - getVersions(pkg /*: string */) /*: Array */ { + /** List the versions for a package. + * + * @param {string} pkg + * @returns {string[]} + */ + getVersions(pkg) { const versions = this.map.get(pkg); return versions === undefined ? [] : versions; } @@ -98,18 +114,26 @@ class OnlineVersionsCache { class OnlineAvailableVersionLister { // Memoization cache to avoid doing the same work twice in list. - memoCache /*: Map> */ = new Map(); - onlineCache /*: OnlineVersionsCache */; - - constructor( - onlineCache /*: OnlineVersionsCache */, - elmVersion /*: string */ - ) { + /** @type {Map>} */ + memoCache = new Map(); + /** @type {OnlineVersionsCache} */ + onlineCache; + + /** + * @param {OnlineVersionsCache} onlineCache + * @param {string} elmVersion + */ + constructor(onlineCache, elmVersion) { onlineCache.update(elmVersion); this.onlineCache = onlineCache; } - list(elmVersion /*: string */, pkg /*: string */) /*: Array */ { + /** + * @param {string} elmVersion + * @param {string} pkg + * @return {string[]} + */ + list(elmVersion, pkg) { const memoVersions = this.memoCache.get(pkg); if (memoVersions !== undefined) { return memoVersions; @@ -128,9 +152,15 @@ class OnlineAvailableVersionLister { class OfflineAvailableVersionLister { // Memoization cache to avoid doing the same work twice in list. - cache /*: Map> */ = new Map(); - - list(elmVersion, pkg /*: string */) /*: Array */ { + /** @type {Map} */ + cache = new Map(); + + /** + * @param {string} elmVersion + * @param {string} pkg + * @return {string[]} + */ + list(elmVersion, pkg) { const memoVersions = this.cache.get(pkg); if (memoVersions !== undefined) { return memoVersions; @@ -143,10 +173,12 @@ class OfflineAvailableVersionLister { } } -function readVersionsInElmHomeAndSort( - elmVersion /*: string*/, - pkg /*: string */ -) /*: Array */ { +/** + * @param {string} elmVersion + * @param {string} pkg + * @return {string[]} + */ +function readVersionsInElmHomeAndSort(elmVersion, pkg) { const pkgPath = ProjectJsonFiles.getPackagePathInElmHome(elmVersion, pkg); let offlineVersions; try { @@ -161,23 +193,31 @@ function readVersionsInElmHomeAndSort( } class DependencyProvider { - cache /*: OnlineVersionsCache */ = new OnlineVersionsCache(); - elmVersion /*: string */; - - constructor(elmVersion /*: string */) { + /** @type {OnlineVersionsCache} */ + cache; + /** @type {string} */ + elmVersion; + + /** + * @param {string} elmVersion + */ + constructor(elmVersion) { + this.cache = new OnlineVersionsCache(); this.elmVersion = elmVersion; - if (!inited) { + if (!wasmWasInitialized) { wasm.init(); - inited = true; + wasmWasInitialized = true; } syncGetWorker = SyncGet.startWorker(); } - // Solve dependencies completely offline, without any http request. - solveOffline( - elmJson /*: string */, - extra /*: { [string]: string } */ - ) /*: string */ { + /** Solve dependencies completely offline, without any http request. + * + * @param {string} elmJson + * @param {Record} extra + * @return {string} + */ + solveOffline(elmJson, extra) { const lister = new OfflineAvailableVersionLister(); try { return wasm.solve_deps( @@ -185,18 +225,20 @@ class DependencyProvider { false, extra, fetchElmJsonOffline(this.elmVersion), - (pkg) => lister.list(this.elmVersion, pkg) + (/** @type {string} */ pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { throw new Error(errorMessage); } } - // Solve dependencies with http requests when required. - solveOnline( - elmJson /*: string */, - extra /*: { [string]: string } */ - ) /*: string */ { + /** Solve dependencies with http requests when required. + * + * @param {string} elmJson + * @param {Record} extra + * @return {string} + */ + solveOnline(elmJson, extra) { const lister = new OnlineAvailableVersionLister( this.cache, this.elmVersion @@ -214,14 +256,25 @@ class DependencyProvider { } } + /** + * @returns {void} + */ tearDown() { + if (!syncGetWorker) { + return; + } syncGetWorker.shutDown(); syncGetWorker = null; } } +/** Solve dependencies completely offline, without any http request. + * + * @param {string} elmVersion + * @returns {(pkg: string, version: string) => string} + */ function fetchElmJsonOnline(elmVersion) { - return (pkg /*: string */, version /*: string */) /*: string */ => { + return (pkg, version) => { try { return fetchElmJsonOffline(elmVersion)(pkg, version); } catch (_) { @@ -230,6 +283,9 @@ function fetchElmJsonOnline(elmVersion) { // or because there was an error parsing `pkg` and `version`. // In such case, this will throw again with `cacheElmJsonPath()` so it's fine. const remoteUrl = remoteElmJsonUrl(pkg, version); + if (!syncGetWorker) { + return ''; + } const elmJson = syncGetWorker.get(remoteUrl); const cachePath = cacheElmJsonPath(pkg, version); const parentDir = path.dirname(cachePath); @@ -240,8 +296,12 @@ function fetchElmJsonOnline(elmVersion) { }; } +/** + * @param {string} elmVersion + * @returns {(pkg: string, version: string) => string} + */ function fetchElmJsonOffline(elmVersion) { - return (pkg /*: string */, version /*: string */) /*: string */ => { + return (pkg, version) => { try { return fs.readFileSync( ProjectJsonFiles.getElmJsonFromElmHomePath(elmVersion, pkg, version), @@ -258,12 +318,16 @@ function fetchElmJsonOffline(elmVersion) { }; } -// Reset the cache of existing versions from scratch -// with a request to the package server. -function onlineVersionsFromScratch( - cachePath /*: string */, - remotePackagesUrl /*: string */ -) /*: Map> */ { +/** Reset the cache of existing versions from scratch with a request to the package server. + * + * @param {string} cachePath + * @param {string} remotePackagesUrl + * @return {Map} + */ +function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { + if (!syncGetWorker) { + return new Map(); + } const onlineVersionsJson = syncGetWorker.get(remotePackagesUrl); fs.writeFileSync(cachePath, onlineVersionsJson); const onlineVersions = JSON.parse(onlineVersionsJson); @@ -278,14 +342,22 @@ function onlineVersionsFromScratch( // Helper functions ################################################## -/* Compares two versions so that newer versions appear first when sorting with this function. */ -function flippedSemverCompare(a /*: string */, b /*: string */) /*: number */ { +/** Compares two versions so that newer versions appear first when sorting with this function. + * + * @param {string} a + * @param {string} b + * @return {number} + */ + +function flippedSemverCompare(a, b) { return collator.compare(b, a); } -function parseOnlineVersions( - json /*: mixed */ -) /*: Map> */ { +/** + * @param {unknown} json + * @return {Map} + */ +function parseOnlineVersions(json) { if (typeof json !== 'object' || json === null || Array.isArray(json)) { throw new Error( `Expected an object, but got: ${ @@ -303,10 +375,12 @@ function parseOnlineVersions( return result; } -function parseVersions( - key /*: string */, - json /*: mixed */ -) /*: Array */ { +/** + * @param {string} key + * @param {unknown} json + * @return {string[]} + */ +function parseVersions(key, json) { if (!Array.isArray(json)) { throw new Error( `Expected ${JSON.stringify(key)} to be an array, but got: ${typeof json}` @@ -326,17 +400,21 @@ function parseVersions( return json; } -function remoteElmJsonUrl( - pkg /*: string */, - version /*: string */ -) /*: string */ { +/** + * @param {string} pkg + * @param {string} version + * @return {string} + */ +function remoteElmJsonUrl(pkg, version) { return `https://package.elm-lang.org/packages/${pkg}/${version}/elm.json`; } -function cacheElmJsonPath( - pkg /*: string */, - version /*: string */ -) /*: string */ { +/** + * @param {string} pkg + * @param {string} version + * @return {string} + */ +function cacheElmJsonPath(pkg, version) { const [author, pkgName] = pkg.split('/'); return path.join( ProjectJsonFiles.elmRoot, @@ -349,12 +427,13 @@ function cacheElmJsonPath( ); } -function splitPkgVersion(str /*: string */) /*: { - pkg: string, - version: string, -} */ { - const parts = str.split('@'); - return {pkg: parts[0], version: parts[1]}; +/** + * @param {string} str + * @return {{pkg: string, version: string}} + */ +function splitPkgVersion(str) { + const [pkg, version] = str.split('@'); + return {pkg, version}; } module.exports = DependencyProvider; diff --git a/lib/sync-get-worker.js b/lib/sync-get-worker.js index 14a25ffdf..600d53fa9 100644 --- a/lib/sync-get-worker.js +++ b/lib/sync-get-worker.js @@ -1,6 +1,3 @@ -// @flow - -// $FlowFixMe[cannot-resolve-module]: Flow doesn’t seem to know about the `worker_threads` module yet. const {parentPort, workerData} = require('worker_threads'); const https = require('https'); @@ -17,7 +14,11 @@ parentPort.on('message', async (url) => { Atomics.notify(sharedLockArray, 0, Infinity); }); -async function getBody(url /*: string */) /*: Promise */ { +/** + * @param {string} url + * @return {Promise} + */ +async function getBody(url) { return new Promise(function (resolve, reject) { https .get(url, function (res) { diff --git a/lib/sync-get.js b/lib/sync-get.js index 252a6c547..4a2b9c27e 100644 --- a/lib/sync-get.js +++ b/lib/sync-get.js @@ -5,12 +5,12 @@ const { receiveMessageOnPort } = require('worker_threads'); -// Start a worker thread and return a `syncGetWorker` -// capable of making sync requests until shut down. -function startWorker() /*: { - get: (string) => string, - shutDown: () => void, -} */ { +/** Start a worker thread and return a `syncGetWorker` + * capable of making sync requests until shut down. + * + * @return {{get: (string) => string, shutDown: () => void}} + */ +function startWorker() { const {port1: localPort, port2: workerPort} = new MessageChannel(); const sharedLock = new SharedArrayBuffer(4); const sharedLockArray = new Int32Array(sharedLock); diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index c6b90071c..537526f87 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -87,13 +87,15 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { return solve(options, elmVersion, elmJson, extra, false, false); } -function solve( - options, - elmVersion, - elmJson, - extra, - onlineFirst /*: boolean */ -) { +/** + * @param {Options} options + * @param {string} elmVersion + * @param {string} elmJson + * @param {Record}extra + * @param {boolean} onlineFirst + * @return {{direct: Record, indirect: Record}} + */ +function solve(options, elmVersion, elmJson, extra, onlineFirst) { dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); try { From a613ab9a8fe746c235a5e1fe5540a196307b3399 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 23:07:56 +0200 Subject: [PATCH 47/98] Remove unused file --- lib/dependency-provider-offline.js | 98 ------------------------------ 1 file changed, 98 deletions(-) delete mode 100644 lib/dependency-provider-offline.js diff --git a/lib/dependency-provider-offline.js b/lib/dependency-provider-offline.js deleted file mode 100644 index 673bd721c..000000000 --- a/lib/dependency-provider-offline.js +++ /dev/null @@ -1,98 +0,0 @@ -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const process = require('process'); - -module.exports = { - fetchElmJson, - listAvailableVersions -}; - -// FetchElmJson(pkg: &str, version: &str) -> String; -function fetchElmJson(pkg, version) { - // Console.log("Fetching: " + pkg + " @ " + version); - try { - return fs.readFileSync(homeElmJsonPath(pkg, version), 'utf8'); - } catch (_) { - try { - return fs.readFileSync(cacheElmJsonPath(pkg, version), 'utf8'); - } catch (_) { - const remoteUrl = remoteElmJsonUrl(pkg, version); - throw `Not doing a remote request to ${remoteUrl}. Please run at least once elm-test first.`; - } - } -} - -const listAvailableVersionsCache = new Map(); - -// ListAvailableVersions(pkg: &str) -> Vec; -function listAvailableVersions(pkg) { - if (listAvailableVersionsCache.has(pkg)) { - return listAvailableVersionsCache.get(pkg); - } - - try { - // Reverse order of subdirectories to have newest versions first. - const dependencies = fs.readdirSync(homePkgPath(pkg)).reverse(); - listAvailableVersionsCache.set(pkg, dependencies); - return dependencies; - } catch (_) { - console.log(`Directory "${homePkgPath(pkg)} does not exist`); - console.log( - `Not doing a request to the package server to find out existing versions. Please run at least once elm-test first.` - ); - return []; - } -} - -// Helper functions ################################################## - -function remoteElmJsonUrl(pkg, version) { - return `https://package.elm-lang.org/packages/${pkg}/${version}/elm.json`; -} - -function cacheElmJsonPath(pkg, version) { - const parts = splitAuthorPkg(pkg); - return path.join( - elmHome(), - 'pubgrub', - 'elm_json_cache', - parts.author, - parts.pkg, - version, - 'elm.json' - ); -} - -function homeElmJsonPath(pkg, version) { - return path.join(homePkgPath(pkg), version, 'elm.json'); -} - -function homePkgPath(pkg) { - const parts = splitAuthorPkg(pkg); - return path.join(elmHome(), '0.19.1', 'packages', parts.author, parts.pkg); -} - -function splitAuthorPkg(pkgIdentifier) { - const parts = pkgIdentifier.split('/'); - return {author: parts[0], pkg: parts[1]}; -} - -function elmHome() { - const elmHomeEnv = process.env.ELM_HOME; - return elmHomeEnv ? elmHomeEnv : defaultElmHome(); -} - -function defaultElmHome() { - return process.platform === 'win32' - ? defaultWindowsElmHome() - : defaultUnixElmHome(); -} - -function defaultUnixElmHome() { - return path.join(os.homedir(), '.elm'); -} - -function defaultWindowsElmHome() { - return path.join(process.env.APPDATA, 'elm'); -} From 033a99eb22d55c4b30ef0b04dfb90bbd88ea99cd Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 23:11:30 +0200 Subject: [PATCH 48/98] Remove extraneous arguments --- lib/template-dependencies.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 537526f87..402f7af9f 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -84,7 +84,7 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { )}` }; - return solve(options, elmVersion, elmJson, extra, false, false); + return solve(options, elmVersion, elmJson, extra, false); } /** @@ -169,8 +169,7 @@ function createNewReviewElmJson(options, elmVersion) { elmJson['test-dependencies'].direct = filterOutDuplicateDependencies( testDependencies.direct, - elmJson.dependencies.direct, - {} + elmJson.dependencies.direct ); elmJson['test-dependencies'].indirect = filterOutDuplicateDependencies( testDependencies.indirect, From eba60df354a5081bd0d53b2219f8f9adc375c727 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 23:13:22 +0200 Subject: [PATCH 49/98] Remove TypeScript errors --- lib/build.js | 1 - lib/sync-get-worker.js | 20 +++++++++++--------- lib/sync-get.js | 3 +++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/build.js b/lib/build.js index 084ee5b01..c5830bba0 100644 --- a/lib/build.js +++ b/lib/build.js @@ -287,7 +287,6 @@ async function buildFromGitHubTemplate(options, template) { /** * Create a local temporary project to build an Elm application in. * @param {Options} options - * @param {Path} reviewElmJsonPath * @param {Path} userSrc * @param {Path} projectFolder * @param {ReviewElmJson} reviewElmJson diff --git a/lib/sync-get-worker.js b/lib/sync-get-worker.js index 600d53fa9..b392d5b9f 100644 --- a/lib/sync-get-worker.js +++ b/lib/sync-get-worker.js @@ -4,15 +4,17 @@ const https = require('https'); const {sharedLock, requestPort} = workerData; const sharedLockArray = new Int32Array(sharedLock); -parentPort.on('message', async (url) => { - try { - const response = await getBody(url); - requestPort.postMessage(response); - } catch (error) { - requestPort.postMessage({error}); - } - Atomics.notify(sharedLockArray, 0, Infinity); -}); +if (parentPort) { + parentPort.on('message', async (url) => { + try { + const response = await getBody(url); + requestPort.postMessage(response); + } catch (error) { + requestPort.postMessage({error}); + } + Atomics.notify(sharedLockArray, 0, Infinity); + }); +} /** * @param {string} url diff --git a/lib/sync-get.js b/lib/sync-get.js index 4a2b9c27e..210587647 100644 --- a/lib/sync-get.js +++ b/lib/sync-get.js @@ -23,6 +23,9 @@ function startWorker() { worker.postMessage(url); Atomics.wait(sharedLockArray, 0, 0); // blocks until notified at index 0. const response = receiveMessageOnPort(localPort); + if (!response || !response.message) { + return ''; + } if (response.message.error) { throw response.message.error; } else { From 36cfc4539d871b3643be5363b89201ecd74c4f3d Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 23:15:33 +0200 Subject: [PATCH 50/98] Fix ESLint errors --- lib/dependency-provider.js | 13 ++++++++++++- lib/project-json-files.js | 1 + lib/remote-template.js | 1 + lib/sync-get-worker.js | 11 ++++++----- lib/sync-get.js | 5 ++++- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 8fbc7008e..cfbe19f4d 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -3,7 +3,7 @@ const path = require('path'); const wasm = require('elm-solve-deps-wasm'); const ProjectJsonFiles = require('./project-json-files'); const SyncGet = require('./sync-get'); -const collator = new Intl.Collator('en', {numeric: true}); // for sorting SemVer strings +const collator = new Intl.Collator('en', {numeric: true}); // For sorting SemVer strings /** @type {boolean} */ let wasmWasInitialized = false; @@ -39,6 +39,7 @@ class OnlineVersionsCache { this.map = onlineVersionsFromScratch(cachePath, remotePackagesUrl); return; } + try { this.map = parseOnlineVersions(JSON.parse(cacheFile)); } catch (error) { @@ -47,6 +48,7 @@ class OnlineVersionsCache { ); } } + this.updateWithRequestSince(cachePath, remotePackagesUrl); } @@ -68,12 +70,14 @@ class OnlineVersionsCache { if (!syncGetWorker) { return; } + const newVersions = JSON.parse(syncGetWorker.get(remoteUrl)); if (newVersions.length === 0) { // Reload from scratch since it means at least one package was deleted from the registry. this.map = onlineVersionsFromScratch(cachePath, remotePackagesUrl); return; } + // Check that the last package in the list was already in cache // since the list returned by the package server is sorted newest first. const {pkg, version} = splitPkgVersion(newVersions.pop()); @@ -92,6 +96,7 @@ class OnlineVersionsCache { versionsOfPkg.push(version); } } + // Save the updated onlineVersionsCache to disk. const onlineVersions = Object.fromEntries(this.map.entries()); fs.writeFileSync(cachePath, JSON.stringify(onlineVersions)); @@ -138,12 +143,14 @@ class OnlineAvailableVersionLister { if (memoVersions !== undefined) { return memoVersions; } + const offlineVersions = readVersionsInElmHomeAndSort(elmVersion, pkg); const allVersionsSet = new Set(this.onlineCache.getVersions(pkg)); // Combine local and online versions. for (const version of offlineVersions) { allVersionsSet.add(version); } + const allVersions = [...allVersionsSet].sort(flippedSemverCompare); this.memoCache.set(pkg, allVersions); return allVersions; @@ -208,6 +215,7 @@ class DependencyProvider { wasm.init(); wasmWasInitialized = true; } + syncGetWorker = SyncGet.startWorker(); } @@ -263,6 +271,7 @@ class DependencyProvider { if (!syncGetWorker) { return; } + syncGetWorker.shutDown(); syncGetWorker = null; } @@ -286,6 +295,7 @@ function fetchElmJsonOnline(elmVersion) { if (!syncGetWorker) { return ''; } + const elmJson = syncGetWorker.get(remoteUrl); const cachePath = cacheElmJsonPath(pkg, version); const parentDir = path.dirname(cachePath); @@ -328,6 +338,7 @@ function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { if (!syncGetWorker) { return new Map(); } + const onlineVersionsJson = syncGetWorker.get(remotePackagesUrl); fs.writeFileSync(cachePath, onlineVersionsJson); const onlineVersions = JSON.parse(onlineVersionsJson); diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 4978109bf..68aee0bc0 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -62,6 +62,7 @@ function getElmJsonFromElmHome(elmVersion, name, packageVersion) { if (promise) { return promise; } + const elmJsonPath = getElmJsonFromElmHomePath( elmVersion, name, diff --git a/lib/remote-template.js b/lib/remote-template.js index 2583cc2e6..25c8aa722 100644 --- a/lib/remote-template.js +++ b/lib/remote-template.js @@ -93,6 +93,7 @@ async function getRemoteElmJson( // Major version for which the configuration exists is not compatible MinVersion.validate(options, reviewElmJsonPath, packageVersion); } + elmJson.dependencies.direct['jfmengels/elm-review'] = MinVersion.updateToAtLeastMinimalVersion(packageVersion); } diff --git a/lib/sync-get-worker.js b/lib/sync-get-worker.js index b392d5b9f..0cb137797 100644 --- a/lib/sync-get-worker.js +++ b/lib/sync-get-worker.js @@ -12,6 +12,7 @@ if (parentPort) { } catch (error) { requestPort.postMessage({error}); } + Atomics.notify(sharedLockArray, 0, Infinity); }); } @@ -21,18 +22,18 @@ if (parentPort) { * @return {Promise} */ async function getBody(url) { - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { https - .get(url, function (res) { + .get(url, (res) => { let body = ''; - res.on('data', function (chunk) { + res.on('data', (chunk) => { body += chunk; }); - res.on('end', function () { + res.on('end', () => { resolve(body); }); }) - .on('error', function (err) { + .on('error', (err) => { reject(err); }); }); diff --git a/lib/sync-get.js b/lib/sync-get.js index 210587647..506b0a5e8 100644 --- a/lib/sync-get.js +++ b/lib/sync-get.js @@ -21,21 +21,24 @@ function startWorker() { }); function get(url) { worker.postMessage(url); - Atomics.wait(sharedLockArray, 0, 0); // blocks until notified at index 0. + Atomics.wait(sharedLockArray, 0, 0); // Blocks until notified at index 0. const response = receiveMessageOnPort(localPort); if (!response || !response.message) { return ''; } + if (response.message.error) { throw response.message.error; } else { return response.message; } } + function shutDown() { localPort.close(); worker.terminate(); } + return {get, shutDown}; } From fbe074a0fdac072532ff6331df3cb24b5dde1dd0 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 11:02:40 +0200 Subject: [PATCH 51/98] Improve TS for sync-get --- lib/sync-get.js | 19 ++++++++++++++----- tsconfig.no-implicit-any.json | 2 ++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/sync-get.js b/lib/sync-get.js index 506b0a5e8..e38f14cc5 100644 --- a/lib/sync-get.js +++ b/lib/sync-get.js @@ -5,10 +5,14 @@ const { receiveMessageOnPort } = require('worker_threads'); +module.exports = { + startWorker +}; + /** Start a worker thread and return a `syncGetWorker` * capable of making sync requests until shut down. * - * @return {{get: (string) => string, shutDown: () => void}} + * @return {{get: (url : string) => string, shutDown: () => void}} */ function startWorker() { const {port1: localPort, port2: workerPort} = new MessageChannel(); @@ -19,6 +23,11 @@ function startWorker() { workerData: {sharedLock, requestPort: workerPort}, transferList: [workerPort] }); + + /** + * @param {string} url + * @return {string} + */ function get(url) { worker.postMessage(url); Atomics.wait(sharedLockArray, 0, 0); // Blocks until notified at index 0. @@ -34,6 +43,10 @@ function startWorker() { } } + /** Shut down the worker thread. + * + * @returns {void} + */ function shutDown() { localPort.close(); worker.terminate(); @@ -41,7 +54,3 @@ function startWorker() { return {get, shutDown}; } - -module.exports = { - startWorker -}; diff --git a/tsconfig.no-implicit-any.json b/tsconfig.no-implicit-any.json index a797fce0b..4f5584136 100644 --- a/tsconfig.no-implicit-any.json +++ b/tsconfig.no-implicit-any.json @@ -6,6 +6,8 @@ "include": [ "lib/anonymize.js", "lib/benchmark.js", + "lib/sync-get.js", + "lib/sync-get-worker.js", "lib/error-message.js", "lib/flags.js", "lib/hash.js", From 89e284365aefaaac9ca296c831b0a37881beeb3a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 11:07:24 +0200 Subject: [PATCH 52/98] Re-order classes and add documentation --- lib/dependency-provider.js | 211 +++++++++++++++++++------------------ 1 file changed, 106 insertions(+), 105 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index cfbe19f4d..0a91f05e1 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -10,7 +10,109 @@ let wasmWasInitialized = false; /** @type {{ get: (string) => string, shutDown: () => void } | null} */ let syncGetWorker = null; -// Cache of existing versions according to the package website. +class DependencyProvider { + /** @type {OnlineVersionsCache} */ + cache; + /** @type {string} */ + elmVersion; + + /** + * @param {string} elmVersion + */ + constructor(elmVersion) { + this.cache = new OnlineVersionsCache(); + this.elmVersion = elmVersion; + if (!wasmWasInitialized) { + wasm.init(); + wasmWasInitialized = true; + } + + syncGetWorker = SyncGet.startWorker(); + } + + /** Solve dependencies completely offline, without any http request. + * + * @param {string} elmJson + * @param {Record} extra + * @return {string} + */ + solveOffline(elmJson, extra) { + const lister = new OfflineAvailableVersionLister(); + try { + return wasm.solve_deps( + elmJson, + false, + extra, + fetchElmJsonOffline(this.elmVersion), + (/** @type {string} */ pkg) => lister.list(this.elmVersion, pkg) + ); + } catch (errorMessage) { + throw new Error(errorMessage); + } + } + + /** Solve dependencies with http requests when required. + * + * @param {string} elmJson + * @param {Record} extra + * @return {string} + */ + solveOnline(elmJson, extra) { + const lister = new OnlineAvailableVersionLister( + this.cache, + this.elmVersion + ); + try { + return wasm.solve_deps( + elmJson, + false, + extra, + fetchElmJsonOnline(this.elmVersion), + (pkg) => lister.list(this.elmVersion, pkg) + ); + } catch (errorMessage) { + throw new Error(errorMessage); + } + } + + /** + * @returns {void} + */ + tearDown() { + if (!syncGetWorker) { + return; + } + + syncGetWorker.shutDown(); + syncGetWorker = null; + } +} + +class OfflineAvailableVersionLister { + /** Memoization cache to avoid doing the same work twice in list. + * @type {Map} + */ + cache = new Map(); + + /** + * @param {string} elmVersion + * @param {string} pkg + * @return {string[]} + */ + list(elmVersion, pkg) { + const memoVersions = this.cache.get(pkg); + if (memoVersions !== undefined) { + return memoVersions; + } + + const offlineVersions = readVersionsInElmHomeAndSort(elmVersion, pkg); + + this.cache.set(pkg, offlineVersions); + return offlineVersions; + } +} + +/** Cache of existing versions according to the package website. */ class OnlineVersionsCache { /** @type {Map>} */ map = new Map(); @@ -118,8 +220,9 @@ class OnlineVersionsCache { } class OnlineAvailableVersionLister { - // Memoization cache to avoid doing the same work twice in list. - /** @type {Map>} */ + /** Memoization cache to avoid doing the same work twice in list. + * @type {Map>} + */ memoCache = new Map(); /** @type {OnlineVersionsCache} */ onlineCache; @@ -157,29 +260,6 @@ class OnlineAvailableVersionLister { } } -class OfflineAvailableVersionLister { - // Memoization cache to avoid doing the same work twice in list. - /** @type {Map} */ - cache = new Map(); - - /** - * @param {string} elmVersion - * @param {string} pkg - * @return {string[]} - */ - list(elmVersion, pkg) { - const memoVersions = this.cache.get(pkg); - if (memoVersions !== undefined) { - return memoVersions; - } - - const offlineVersions = readVersionsInElmHomeAndSort(elmVersion, pkg); - - this.cache.set(pkg, offlineVersions); - return offlineVersions; - } -} - /** * @param {string} elmVersion * @param {string} pkg @@ -199,84 +279,6 @@ function readVersionsInElmHomeAndSort(elmVersion, pkg) { return offlineVersions.sort(flippedSemverCompare); } -class DependencyProvider { - /** @type {OnlineVersionsCache} */ - cache; - /** @type {string} */ - elmVersion; - - /** - * @param {string} elmVersion - */ - constructor(elmVersion) { - this.cache = new OnlineVersionsCache(); - this.elmVersion = elmVersion; - if (!wasmWasInitialized) { - wasm.init(); - wasmWasInitialized = true; - } - - syncGetWorker = SyncGet.startWorker(); - } - - /** Solve dependencies completely offline, without any http request. - * - * @param {string} elmJson - * @param {Record} extra - * @return {string} - */ - solveOffline(elmJson, extra) { - const lister = new OfflineAvailableVersionLister(); - try { - return wasm.solve_deps( - elmJson, - false, - extra, - fetchElmJsonOffline(this.elmVersion), - (/** @type {string} */ pkg) => lister.list(this.elmVersion, pkg) - ); - } catch (errorMessage) { - throw new Error(errorMessage); - } - } - - /** Solve dependencies with http requests when required. - * - * @param {string} elmJson - * @param {Record} extra - * @return {string} - */ - solveOnline(elmJson, extra) { - const lister = new OnlineAvailableVersionLister( - this.cache, - this.elmVersion - ); - try { - return wasm.solve_deps( - elmJson, - false, - extra, - fetchElmJsonOnline(this.elmVersion), - (pkg) => lister.list(this.elmVersion, pkg) - ); - } catch (errorMessage) { - throw new Error(errorMessage); - } - } - - /** - * @returns {void} - */ - tearDown() { - if (!syncGetWorker) { - return; - } - - syncGetWorker.shutDown(); - syncGetWorker = null; - } -} - /** Solve dependencies completely offline, without any http request. * * @param {string} elmVersion @@ -359,7 +361,6 @@ function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { * @param {string} b * @return {number} */ - function flippedSemverCompare(a, b) { return collator.compare(b, a); } From b18a07a8f7d695c20c18266a2bbaf9a0a81e09f1 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 11:11:43 +0200 Subject: [PATCH 53/98] Use plain conversions instead of using Intl.collator --- lib/dependency-provider.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 0a91f05e1..2eb8d6d2a 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -3,7 +3,6 @@ const path = require('path'); const wasm = require('elm-solve-deps-wasm'); const ProjectJsonFiles = require('./project-json-files'); const SyncGet = require('./sync-get'); -const collator = new Intl.Collator('en', {numeric: true}); // For sorting SemVer strings /** @type {boolean} */ let wasmWasInitialized = false; @@ -362,7 +361,23 @@ function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { * @return {number} */ function flippedSemverCompare(a, b) { - return collator.compare(b, a); + const vA = a.split('.'); + const vB = b.split('.'); + return ( + compareNumber(vA[0], vB[0]) || + compareNumber(vA[1], vB[1]) || + compareNumber(vA[2], vB[2]) + ); +} + +/** COmpare 2 numbers as string + * + * @param {string} a + * @param {string} b + * @return {number} + */ +function compareNumber(a, b) { + return parseInt(b, 10) - parseInt(a, 10); } /** From 03a6ef6a27223533916947248a72027b3f6fb7e6 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 10:53:20 +0200 Subject: [PATCH 54/98] Update tooling integration documentation --- documentation/tooling-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/tooling-integration.md b/documentation/tooling-integration.md index 9afa5b431..37ef07b1f 100644 --- a/documentation/tooling-integration.md +++ b/documentation/tooling-integration.md @@ -18,7 +18,7 @@ The CLI creates a bunch of cache inside `elm-stuff/generated-code/jfmengels/elm- - `file-cache/`: Caching of the file's ASTs. - `review-applications/`: Caching of the project's configuration. This is the application we build by compiling the source code in the CLI's `template/` directory. - `result-cache/`: Caching of the results of each module's analysis for every rule. -- `dependencies-cache/`: Caching of the dependencies of the project's configuration computed by `elm-json`. `elm-json` is a bit slow, and doesn't work great offline. This is done so we don't have to compute the dependencies again if the configuration changed but not `review/elm.json`. +- `dependencies-cache/`: Caching of data related to Elm dependencies computed by `elm-solve-deps-wasm` (or `elm-json` prior to `2.11.0`). - `elm-parser/`: Caching of the parser application. This is the application we build by compiling with the user's version of `stil4m/elm-syntax`, and the name of the file reflect the version. This is used to parallelize the parsing of the files, which is kind of slow, at startup. Namespacing things means that data will unfortunately be duplicated, but it is meant to prevent different tools from stepping on each other's toes by saving the same files at the same time as another, potentially corrupting the files. From 0aff6c13e8a80c6eec8a877886673522ea9e5ace Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 11:55:39 +0200 Subject: [PATCH 55/98] Pass options to onlineCache.update --- lib/dependency-provider.js | 16 ++++++++++++---- lib/template-dependencies.js | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 2eb8d6d2a..fa9370fa8 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -4,6 +4,10 @@ const wasm = require('elm-solve-deps-wasm'); const ProjectJsonFiles = require('./project-json-files'); const SyncGet = require('./sync-get'); +/** + * @typedef { import("./types/options").Options } Options + */ + /** @type {boolean} */ let wasmWasInitialized = false; /** @type {{ get: (string) => string, shutDown: () => void } | null} */ @@ -52,12 +56,14 @@ class DependencyProvider { /** Solve dependencies with http requests when required. * + * @param {Options} options * @param {string} elmJson * @param {Record} extra * @return {string} */ - solveOnline(elmJson, extra) { + solveOnline(options, elmJson, extra) { const lister = new OnlineAvailableVersionLister( + options, this.cache, this.elmVersion ); @@ -117,10 +123,11 @@ class OnlineVersionsCache { map = new Map(); /** + * @param {Options} options * @param {string} elmVersion * @returns {void} */ - update(elmVersion) { + update(options, elmVersion) { const pubgrubHome = path.join( ProjectJsonFiles.elmRoot, 'elm-review', @@ -227,11 +234,12 @@ class OnlineAvailableVersionLister { onlineCache; /** + * @param {Options} options * @param {OnlineVersionsCache} onlineCache * @param {string} elmVersion */ - constructor(onlineCache, elmVersion) { - onlineCache.update(elmVersion); + constructor(options, onlineCache, elmVersion) { + onlineCache.update(options, elmVersion); this.onlineCache = onlineCache; } diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 402f7af9f..187056c5e 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -101,7 +101,7 @@ function solve(options, elmVersion, elmJson, extra, onlineFirst) { try { return JSON.parse( onlineFirst && !options.offline - ? dependencyProvider.solveOnline(elmJson, extra) + ? dependencyProvider.solveOnline(options, elmJson, extra) : dependencyProvider.solveOffline(elmJson, extra) ); } catch (error) { @@ -112,7 +112,7 @@ function solve(options, elmVersion, elmJson, extra, onlineFirst) { return JSON.parse( onlineFirst ? dependencyProvider.solveOffline(elmJson, extra) - : dependencyProvider.solveOnline(elmJson, extra) + : dependencyProvider.solveOnline(options, elmJson, extra) ); } } From 9a570232d26cdbb628b30d7f9de6cea15da423b6 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 12:05:17 +0200 Subject: [PATCH 56/98] Don't cache dependencies computation anymore --- lib/build.js | 6 +++--- lib/template-dependencies.js | 14 +------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/build.js b/lib/build.js index c5830bba0..97c8b602c 100644 --- a/lib/build.js +++ b/lib/build.js @@ -303,9 +303,9 @@ async function createTemplateProject( const elmJsonPath = path.join(projectFolder, 'elm.json'); // Load review project's elm.json file contents - const [dependencies, previousElmJson] = await Promise.all([ - TemplateDependencies.get(options, reviewElmJson), - FS.readFile(elmJsonPath).catch(() => null) + const [previousElmJson, dependencies] = await Promise.all([ + FS.readFile(elmJsonPath).catch(() => null), + TemplateDependencies.get(options, reviewElmJson) ]); const finalElmJson = updateSourceDirectories(options, userSrc, { ...reviewElmJson, diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 187056c5e..345f3689b 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -2,8 +2,6 @@ * Credit goes to @zwilias, from his PR here https://github.com/rtfeldman/node-test-runner/pull/356/files */ -const Hash = require('./hash'); -const Cache = require('./cache'); const FS = require('./fs-wrapper'); const MinVersion = require('./min-version'); const DependencyProvider = require('./dependency-provider'); @@ -24,17 +22,7 @@ module.exports = { // GET -async function get(options, elmJson) { - const dependencyHash = Hash.hash(JSON.stringify(elmJson.dependencies)); - const cacheKey = `${dependencyHash}${ - options.localElmReviewSrc ? '-local' : '' - }`; - return Cache.getOrCompute(options.dependenciesCachePath(), cacheKey, () => - computeDependencies(options, elmJson) - ); -} - -function computeDependencies(options, elmJson) { +function get(options, elmJson) { const extra = { 'elm/json': '1.0.0 <= v < 2.0.0', 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', From 95912cd245296d1f95b3ed350a769a11954bfcf7 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 12:03:42 +0200 Subject: [PATCH 57/98] Put dependency cache inside elm-stuff --- lib/dependency-provider.js | 14 +++++--------- lib/options.js | 15 +++++++++++++-- lib/types/options.d.ts | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index fa9370fa8..432665a4b 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -1,8 +1,9 @@ const fs = require('fs'); const path = require('path'); const wasm = require('elm-solve-deps-wasm'); -const ProjectJsonFiles = require('./project-json-files'); +const FS = require('./fs-wrapper'); const SyncGet = require('./sync-get'); +const ProjectJsonFiles = require('./project-json-files'); /** * @typedef { import("./types/options").Options } Options @@ -128,14 +129,8 @@ class OnlineVersionsCache { * @returns {void} */ update(options, elmVersion) { - const pubgrubHome = path.join( - ProjectJsonFiles.elmRoot, - 'elm-review', - 'dependencies-cache', - elmVersion - ); - fs.mkdirSync(pubgrubHome, {recursive: true}); - const cachePath = path.join(pubgrubHome, 'versions_cache.json'); + const cachePath = options.dependenciesCachePath(elmVersion); + const remotePackagesUrl = 'https://package.elm-lang.org/all-packages'; if (this.map.size === 0) { let cacheFile; @@ -349,6 +344,7 @@ function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { } const onlineVersionsJson = syncGetWorker.get(remotePackagesUrl); + FS.mkdirpSync(cachePath); fs.writeFileSync(cachePath, onlineVersionsJson); const onlineVersions = JSON.parse(onlineVersionsJson); try { diff --git a/lib/options.js b/lib/options.js index 07f713baf..b22ea580b 100644 --- a/lib/options.js +++ b/lib/options.js @@ -271,8 +271,19 @@ try re-running it with ${chalk.cyan('--elmjson ')}.`, 'elm.json' ); }, - dependenciesCachePath: () => - path.join(elmStuffFolder(), 'dependencies-cache'), + + /** Cache in which we'll store information related Elm depencies, computed by elm-solve-deps-wasm. + * + * @param {string} elmVersion + * @return {string} + */ + dependenciesCachePath: (elmVersion) => + path.join( + elmStuffFolder(), + 'dependencies-cache', + elmVersion, + 'versions-cache.json' + ), // PATHS - THINGS TO REVIEW elmJsonPath, diff --git a/lib/types/options.d.ts b/lib/types/options.d.ts index 3a7bebe2d..6a6cd763b 100644 --- a/lib/types/options.d.ts +++ b/lib/types/options.d.ts @@ -48,7 +48,7 @@ export type Options = { generatedCodePackageJson: () => Path; templateElmModulePath: (string) => Path; pathToTemplateElmJson: (string) => Path; - dependenciesCachePath: () => Path; + dependenciesCachePath: (string) => Path; elmJsonPath: Path; elmJsonPathWasSpecified: boolean; readmePath: Path; From 1323cdcd572ddab54a44e7f7df48ac3cb7b69174 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 13:31:28 +0200 Subject: [PATCH 58/98] Pass options to fetchElmJsonOnline --- lib/dependency-provider.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 432665a4b..a76f0447e 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -73,7 +73,7 @@ class DependencyProvider { elmJson, false, extra, - fetchElmJsonOnline(this.elmVersion), + fetchElmJsonOnline(options, this.elmVersion), (pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { @@ -283,10 +283,11 @@ function readVersionsInElmHomeAndSort(elmVersion, pkg) { /** Solve dependencies completely offline, without any http request. * + * @param {Options} options * @param {string} elmVersion * @returns {(pkg: string, version: string) => string} */ -function fetchElmJsonOnline(elmVersion) { +function fetchElmJsonOnline(options, elmVersion) { return (pkg, version) => { try { return fetchElmJsonOffline(elmVersion)(pkg, version); From bd488115b2d8b21a1b4ef350d3649eaa26cdc549 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 13:33:55 +0200 Subject: [PATCH 59/98] Add type annotation --- lib/dependency-provider.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index a76f0447e..58350d0dd 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -316,6 +316,11 @@ function fetchElmJsonOnline(options, elmVersion) { * @returns {(pkg: string, version: string) => string} */ function fetchElmJsonOffline(elmVersion) { + /** + * @param {string} pkg + * @param {string} version + * @returns {string} + */ return (pkg, version) => { try { return fs.readFileSync( From 3d8f779170053e9ecca03fb3c0f7d5ed1b9003dc Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 13:41:01 +0200 Subject: [PATCH 60/98] Re-use existing folder to store downloaded elm.json files instead of a new one --- lib/dependency-provider.js | 45 +++++++++++++++++------------------- lib/project-json-files.js | 1 + lib/template-dependencies.js | 4 ++-- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 58350d0dd..72fd71786 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -36,18 +36,19 @@ class DependencyProvider { /** Solve dependencies completely offline, without any http request. * + * @param {Options} options * @param {string} elmJson * @param {Record} extra * @return {string} */ - solveOffline(elmJson, extra) { + solveOffline(options, elmJson, extra) { const lister = new OfflineAvailableVersionLister(); try { return wasm.solve_deps( elmJson, false, extra, - fetchElmJsonOffline(this.elmVersion), + fetchElmJsonOffline(options, this.elmVersion), (/** @type {string} */ pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { @@ -290,7 +291,7 @@ function readVersionsInElmHomeAndSort(elmVersion, pkg) { function fetchElmJsonOnline(options, elmVersion) { return (pkg, version) => { try { - return fetchElmJsonOffline(elmVersion)(pkg, version); + return fetchElmJsonOffline(options, elmVersion)(pkg, version); } catch (_) { // `fetchElmJsonOffline` can only fail in ways that are either expected // (such as file does not exist or no permissions) @@ -302,7 +303,13 @@ function fetchElmJsonOnline(options, elmVersion) { } const elmJson = syncGetWorker.get(remoteUrl); - const cachePath = cacheElmJsonPath(pkg, version); + const cachePath = ProjectJsonFiles.elmReviewDependencyCache( + options, + elmVersion, + pkg, + version, + 'elm.json' + ); const parentDir = path.dirname(cachePath); fs.mkdirSync(parentDir, {recursive: true}); fs.writeFileSync(cachePath, elmJson); @@ -312,10 +319,11 @@ function fetchElmJsonOnline(options, elmVersion) { } /** + * @param {Options} options * @param {string} elmVersion * @returns {(pkg: string, version: string) => string} */ -function fetchElmJsonOffline(elmVersion) { +function fetchElmJsonOffline(options, elmVersion) { /** * @param {string} pkg * @param {string} version @@ -333,7 +341,14 @@ function fetchElmJsonOffline(elmVersion) { // Otherwise, it means that `homeElmJsonPath()` failed while processing `pkg` and `version`. // In such case, again, it's fine to catch all since the next call to `cacheElmJsonPath()` // will fail the same anyway. - return fs.readFileSync(cacheElmJsonPath(pkg, version), 'utf8'); + const cachePath = ProjectJsonFiles.elmReviewDependencyCache( + options, + elmVersion, + pkg, + version, + 'elm.json' + ); + return fs.readFileSync(cachePath, 'utf8'); } }; } @@ -446,24 +461,6 @@ function remoteElmJsonUrl(pkg, version) { return `https://package.elm-lang.org/packages/${pkg}/${version}/elm.json`; } -/** - * @param {string} pkg - * @param {string} version - * @return {string} - */ -function cacheElmJsonPath(pkg, version) { - const [author, pkgName] = pkg.split('/'); - return path.join( - ProjectJsonFiles.elmRoot, - 'pubgrub', - 'elm_json_cache', - author, - pkgName, - version, - 'elm.json' - ); -} - /** * @param {string} str * @return {{pkg: string, version: string}} diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 68aee0bc0..c1868148d 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -177,6 +177,7 @@ module.exports = { getPackagePathInElmHome, getElmJsonFromElmHomePath, getElmJsonFromElmHome, + elmReviewDependencyCache, getDocsJson, elmRoot }; diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 345f3689b..516e8d16e 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -90,7 +90,7 @@ function solve(options, elmVersion, elmJson, extra, onlineFirst) { return JSON.parse( onlineFirst && !options.offline ? dependencyProvider.solveOnline(options, elmJson, extra) - : dependencyProvider.solveOffline(elmJson, extra) + : dependencyProvider.solveOffline(options, elmJson, extra) ); } catch (error) { if (options.offline) { @@ -99,7 +99,7 @@ function solve(options, elmVersion, elmJson, extra, onlineFirst) { return JSON.parse( onlineFirst - ? dependencyProvider.solveOffline(elmJson, extra) + ? dependencyProvider.solveOffline(options, elmJson, extra) : dependencyProvider.solveOnline(options, elmJson, extra) ); } From d676356afc939dc7bb36fd1297e49a8c4b5a3597 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 13:42:20 +0200 Subject: [PATCH 61/98] Flatten functions --- lib/dependency-provider.js | 115 ++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 72fd71786..853989da1 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -48,7 +48,8 @@ class DependencyProvider { elmJson, false, extra, - fetchElmJsonOffline(options, this.elmVersion), + (pkg, version) => + fetchElmJsonOffline(options, this.elmVersion, pkg, version), (/** @type {string} */ pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { @@ -74,7 +75,8 @@ class DependencyProvider { elmJson, false, extra, - fetchElmJsonOnline(options, this.elmVersion), + (pkg, version) => + fetchElmJsonOnline(options, this.elmVersion, pkg, version), (pkg) => lister.list(this.elmVersion, pkg) ); } catch (errorMessage) { @@ -286,71 +288,66 @@ function readVersionsInElmHomeAndSort(elmVersion, pkg) { * * @param {Options} options * @param {string} elmVersion - * @returns {(pkg: string, version: string) => string} + * @param {string} pkg + * @param {string} version + * @returns {string} */ -function fetchElmJsonOnline(options, elmVersion) { - return (pkg, version) => { - try { - return fetchElmJsonOffline(options, elmVersion)(pkg, version); - } catch (_) { - // `fetchElmJsonOffline` can only fail in ways that are either expected - // (such as file does not exist or no permissions) - // or because there was an error parsing `pkg` and `version`. - // In such case, this will throw again with `cacheElmJsonPath()` so it's fine. - const remoteUrl = remoteElmJsonUrl(pkg, version); - if (!syncGetWorker) { - return ''; - } - - const elmJson = syncGetWorker.get(remoteUrl); - const cachePath = ProjectJsonFiles.elmReviewDependencyCache( - options, - elmVersion, - pkg, - version, - 'elm.json' - ); - const parentDir = path.dirname(cachePath); - fs.mkdirSync(parentDir, {recursive: true}); - fs.writeFileSync(cachePath, elmJson); - return elmJson; +function fetchElmJsonOnline(options, elmVersion, pkg, version) { + try { + return fetchElmJsonOffline(options, elmVersion, pkg, version); + } catch (_) { + // `fetchElmJsonOffline` can only fail in ways that are either expected + // (such as file does not exist or no permissions) + // or because there was an error parsing `pkg` and `version`. + // In such case, this will throw again with `cacheElmJsonPath()` so it's fine. + const remoteUrl = remoteElmJsonUrl(pkg, version); + if (!syncGetWorker) { + return ''; } - }; + + const elmJson = syncGetWorker.get(remoteUrl); + const cachePath = ProjectJsonFiles.elmReviewDependencyCache( + options, + elmVersion, + pkg, + version, + 'elm.json' + ); + const parentDir = path.dirname(cachePath); + fs.mkdirSync(parentDir, {recursive: true}); + fs.writeFileSync(cachePath, elmJson); + return elmJson; + } } /** * @param {Options} options * @param {string} elmVersion - * @returns {(pkg: string, version: string) => string} + * @param {string} pkg + * @param {string} version + * @returns {string} */ -function fetchElmJsonOffline(options, elmVersion) { - /** - * @param {string} pkg - * @param {string} version - * @returns {string} - */ - return (pkg, version) => { - try { - return fs.readFileSync( - ProjectJsonFiles.getElmJsonFromElmHomePath(elmVersion, pkg, version), - 'utf8' - ); - } catch (_) { - // The read can only fail if the elm.json file does not exist - // or if we don't have the permissions to read it so it's fine to catch all. - // Otherwise, it means that `homeElmJsonPath()` failed while processing `pkg` and `version`. - // In such case, again, it's fine to catch all since the next call to `cacheElmJsonPath()` - // will fail the same anyway. - const cachePath = ProjectJsonFiles.elmReviewDependencyCache( - options, - elmVersion, - pkg, - version, - 'elm.json' - ); - return fs.readFileSync(cachePath, 'utf8'); - } - }; +function fetchElmJsonOffline(options, elmVersion, pkg, version) { + try { + return fs.readFileSync( + ProjectJsonFiles.getElmJsonFromElmHomePath(elmVersion, pkg, version), + 'utf8' + ); + } catch (_) { + // The read can only fail if the elm.json file does not exist + // or if we don't have the permissions to read it so it's fine to catch all. + // Otherwise, it means that `homeElmJsonPath()` failed while processing `pkg` and `version`. + // In such case, again, it's fine to catch all since the next call to `cacheElmJsonPath()` + // will fail the same anyway. + const cachePath = ProjectJsonFiles.elmReviewDependencyCache( + options, + elmVersion, + pkg, + version, + 'elm.json' + ); + return fs.readFileSync(cachePath, 'utf8'); + } } /** Reset the cache of existing versions from scratch with a request to the package server. From ab2cd2cd0577d68356c30d1eea4ce18fe65c9c37 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 14:17:38 +0200 Subject: [PATCH 62/98] Remove duplicate writing of elm.json file during init --- lib/template-dependencies.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 516e8d16e..8cae23ab2 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -2,7 +2,6 @@ * Credit goes to @zwilias, from his PR here https://github.com/rtfeldman/node-test-runner/pull/356/files */ -const FS = require('./fs-wrapper'); const MinVersion = require('./min-version'); const DependencyProvider = require('./dependency-provider'); @@ -204,10 +203,6 @@ async function update(options, pathToElmJson, elmJson) { teardownDependenciesProvider(); } - if (options.subcommand === 'init') { - await FS.writeJson(pathToElmJson, elmJson, 4); - } - return elmJson; } From 072e0fb937cac42626a7d0098775b793c4433bd0 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 14:17:35 +0200 Subject: [PATCH 63/98] Always tear down after TemplateDependencies.update() --- lib/template-dependencies.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 8cae23ab2..94491778b 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -199,9 +199,7 @@ async function update(options, pathToElmJson, elmJson) { true ); - if (options.subcommand === 'init' || options.subcommand === 'new-package') { - teardownDependenciesProvider(); - } + teardownDependenciesProvider(); return elmJson; } From 8e4b8d02233a694479d9fa069245eaf198f887d8 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 15 Oct 2023 14:26:14 +0200 Subject: [PATCH 64/98] Pass elmVersion as a parameter --- lib/dependency-provider.js | 25 ++++++++++--------------- lib/template-dependencies.js | 10 +++++----- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 853989da1..a4e61bcb7 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -17,15 +17,9 @@ let syncGetWorker = null; class DependencyProvider { /** @type {OnlineVersionsCache} */ cache; - /** @type {string} */ - elmVersion; - /** - * @param {string} elmVersion - */ - constructor(elmVersion) { + constructor() { this.cache = new OnlineVersionsCache(); - this.elmVersion = elmVersion; if (!wasmWasInitialized) { wasm.init(); wasmWasInitialized = true; @@ -37,11 +31,12 @@ class DependencyProvider { /** Solve dependencies completely offline, without any http request. * * @param {Options} options + * @param {string} elmVersion * @param {string} elmJson * @param {Record} extra * @return {string} */ - solveOffline(options, elmJson, extra) { + solveOffline(options, elmVersion, elmJson, extra) { const lister = new OfflineAvailableVersionLister(); try { return wasm.solve_deps( @@ -49,8 +44,8 @@ class DependencyProvider { false, extra, (pkg, version) => - fetchElmJsonOffline(options, this.elmVersion, pkg, version), - (/** @type {string} */ pkg) => lister.list(this.elmVersion, pkg) + fetchElmJsonOffline(options, elmVersion, pkg, version), + (/** @type {string} */ pkg) => lister.list(elmVersion, pkg) ); } catch (errorMessage) { throw new Error(errorMessage); @@ -60,24 +55,24 @@ class DependencyProvider { /** Solve dependencies with http requests when required. * * @param {Options} options + * @param {string} elmVersion * @param {string} elmJson * @param {Record} extra * @return {string} */ - solveOnline(options, elmJson, extra) { + solveOnline(options, elmVersion, elmJson, extra) { const lister = new OnlineAvailableVersionLister( options, this.cache, - this.elmVersion + elmVersion ); try { return wasm.solve_deps( elmJson, false, extra, - (pkg, version) => - fetchElmJsonOnline(options, this.elmVersion, pkg, version), - (pkg) => lister.list(this.elmVersion, pkg) + (pkg, version) => fetchElmJsonOnline(options, elmVersion, pkg, version), + (pkg) => lister.list(elmVersion, pkg) ); } catch (errorMessage) { throw new Error(errorMessage); diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 94491778b..d71978e2c 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -83,13 +83,13 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { * @return {{direct: Record, indirect: Record}} */ function solve(options, elmVersion, elmJson, extra, onlineFirst) { - dependencyProvider = dependencyProvider || new DependencyProvider(elmVersion); + dependencyProvider = dependencyProvider || new DependencyProvider(); try { return JSON.parse( onlineFirst && !options.offline - ? dependencyProvider.solveOnline(options, elmJson, extra) - : dependencyProvider.solveOffline(options, elmJson, extra) + ? dependencyProvider.solveOnline(options, elmVersion, elmJson, extra) + : dependencyProvider.solveOffline(options, elmVersion, elmJson, extra) ); } catch (error) { if (options.offline) { @@ -98,8 +98,8 @@ function solve(options, elmVersion, elmJson, extra, onlineFirst) { return JSON.parse( onlineFirst - ? dependencyProvider.solveOffline(options, elmJson, extra) - : dependencyProvider.solveOnline(options, elmJson, extra) + ? dependencyProvider.solveOffline(options, elmVersion, elmJson, extra) + : dependencyProvider.solveOnline(options, elmVersion, elmJson, extra) ); } } From 6c7d9e6c9a7a3ded1423e6492e2706e9f891069c Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 14 Oct 2023 18:47:02 +0200 Subject: [PATCH 65/98] Add TODO --- lib/template-dependencies.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index d71978e2c..296d2370d 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -191,6 +191,7 @@ async function update(options, pathToElmJson, elmJson) { delete elmJson.dependencies.direct['jfmengels/elm-review']; delete elmJson.dependencies.direct['stil4m/elm-syntax']; + // TODO Upgrade test dependencies too for init or new-package elmJson.dependencies = solve( options, elmJson['elm-version'], From 96566b26626324df09356324ff0f339392cdf3b8 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 16 Oct 2023 00:00:12 +0200 Subject: [PATCH 66/98] Update CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e699be9..009c0920c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ ## [Unreleased] -- Fixed a crash when using `--template` from the same repository but from two different paths. +## [2.11.0] **BETA** - 2023-10-16 + +- Add an `offline` mode to prevent `elm-review` from making any HTTP requests. This is useful for CI environments that should not have access to the internet, where you only want to run `elm-review` without arguments. +- Replaced the internally used `elm-json` dependency with `elm-solve-deps-wasm`, which should be more reliable, re-enable support for old MacOS versions as well as improve performance. +- Fixed a crash when using `--template` from the same repository but from two different paths. + +This is a **BETA** release, so I expect things to break. Please report any issues you encounter on the #elm-review channel on the Elm Slack. ## [2.10.3] - 2023-09-26 From f93c2bfb280688ef3332a2a7efbe0ab6f5b3d1f0 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 16 Oct 2023 00:03:37 +0200 Subject: [PATCH 67/98] 2.11.0-beta.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6218beffa..12c15bc02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "elm-review", - "version": "2.10.3", + "version": "2.11.0-beta.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "elm-review", - "version": "2.10.3", + "version": "2.11.0-beta.1", "license": "BSD-3-Clause", "dependencies": { "chalk": "^4.0.0", diff --git a/package.json b/package.json index ef48098c1..5dfe2bf93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "elm-review", - "version": "2.10.3", + "version": "2.11.0-beta.1", "description": "Run elm-review from Node.js", "engines": { "node": ">=10.0.0" From c91aa085e7de757fa0189ba51be551c1515d3eff Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 16 Oct 2023 08:43:55 +0200 Subject: [PATCH 68/98] Fix illegal FS operation on versions cache file --- lib/dependency-provider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index a4e61bcb7..dbd36b34b 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -357,7 +357,7 @@ function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { } const onlineVersionsJson = syncGetWorker.get(remotePackagesUrl); - FS.mkdirpSync(cachePath); + FS.mkdirpSync(path.dirname(cachePath)); fs.writeFileSync(cachePath, onlineVersionsJson); const onlineVersions = JSON.parse(onlineVersionsJson); try { From 0169a2d84206be823893fa4f18942d52ffc2ca34 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 16 Oct 2023 09:20:06 +0200 Subject: [PATCH 69/98] 2.11.0-beta.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12c15bc02..6fd1192d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "elm-review", - "version": "2.11.0-beta.1", + "version": "2.11.0-beta.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "elm-review", - "version": "2.11.0-beta.1", + "version": "2.11.0-beta.2", "license": "BSD-3-Clause", "dependencies": { "chalk": "^4.0.0", diff --git a/package.json b/package.json index 5dfe2bf93..6dbde45a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "elm-review", - "version": "2.11.0-beta.1", + "version": "2.11.0-beta.2", "description": "Run elm-review from Node.js", "engines": { "node": ">=10.0.0" From b90c446a95cd8c0926f80dd3922e493a852241e7 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 16 Oct 2023 17:15:28 +0200 Subject: [PATCH 70/98] Remove test dependencies when compiling review/elm.json --- lib/build.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/build.js b/lib/build.js index 97c8b602c..d875559f3 100644 --- a/lib/build.js +++ b/lib/build.js @@ -309,7 +309,8 @@ async function createTemplateProject( ]); const finalElmJson = updateSourceDirectories(options, userSrc, { ...reviewElmJson, - dependencies + dependencies, + ['test-dependencies']: {direct: {}, indirect: {}} }); const finalElmJsonAsString = JSON.stringify(finalElmJson, null, 4); if (previousElmJson !== finalElmJsonAsString) { From bbf6531d836d8cd82a81ac9e34b3918bbb9d0ee9 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 16 Oct 2023 18:11:45 +0200 Subject: [PATCH 71/98] Update snapshots --- .../elm-review-something-for-new-rule/package.json | 2 +- .../elm-review-something-for-new-rule/review/elm.json | 2 +- test/run-snapshots/elm-review-something/package.json | 2 +- test/run-snapshots/elm-review-something/review/elm.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/run-snapshots/elm-review-something-for-new-rule/package.json b/test/run-snapshots/elm-review-something-for-new-rule/package.json index 29af827a7..257ea29f4 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/package.json +++ b/test/run-snapshots/elm-review-something-for-new-rule/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "elm-doc-preview": "^5.0.5", - "elm-review": "^2.10.3", + "elm-review": "^2.11.0-beta.2", "elm-test": "^0.19.1-revision10", "elm-tooling": "^1.13.1", "fs-extra": "^9.0.0", diff --git a/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json b/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json index e184a325e..e4e59ba61 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json +++ b/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json @@ -15,7 +15,7 @@ "jfmengels/elm-review-common": "1.3.3", "jfmengels/elm-review-debug": "1.0.8", "jfmengels/elm-review-documentation": "2.0.4", - "jfmengels/elm-review-simplify": "2.1.2", + "jfmengels/elm-review-simplify": "2.1.3", "jfmengels/elm-review-unused": "1.2.0", "sparksp/elm-review-forbidden-words": "1.0.1", "stil4m/elm-syntax": "7.3.2" diff --git a/test/run-snapshots/elm-review-something/package.json b/test/run-snapshots/elm-review-something/package.json index 29af827a7..257ea29f4 100644 --- a/test/run-snapshots/elm-review-something/package.json +++ b/test/run-snapshots/elm-review-something/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "elm-doc-preview": "^5.0.5", - "elm-review": "^2.10.3", + "elm-review": "^2.11.0-beta.2", "elm-test": "^0.19.1-revision10", "elm-tooling": "^1.13.1", "fs-extra": "^9.0.0", diff --git a/test/run-snapshots/elm-review-something/review/elm.json b/test/run-snapshots/elm-review-something/review/elm.json index e184a325e..e4e59ba61 100644 --- a/test/run-snapshots/elm-review-something/review/elm.json +++ b/test/run-snapshots/elm-review-something/review/elm.json @@ -15,7 +15,7 @@ "jfmengels/elm-review-common": "1.3.3", "jfmengels/elm-review-debug": "1.0.8", "jfmengels/elm-review-documentation": "2.0.4", - "jfmengels/elm-review-simplify": "2.1.2", + "jfmengels/elm-review-simplify": "2.1.3", "jfmengels/elm-review-unused": "1.2.0", "sparksp/elm-review-forbidden-words": "1.0.1", "stil4m/elm-syntax": "7.3.2" From eacf71501a1c1dd91a0a4e2bbd352a8afa07313c Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Mon, 16 Oct 2023 18:12:07 +0200 Subject: [PATCH 72/98] 2.11.0-beta.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6fd1192d8..8320b0bed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "elm-review", - "version": "2.11.0-beta.2", + "version": "2.11.0-beta.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "elm-review", - "version": "2.11.0-beta.2", + "version": "2.11.0-beta.3", "license": "BSD-3-Clause", "dependencies": { "chalk": "^4.0.0", diff --git a/package.json b/package.json index 6dbde45a2..ec3003e35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "elm-review", - "version": "2.11.0-beta.2", + "version": "2.11.0-beta.3", "description": "Run elm-review from Node.js", "engines": { "node": ">=10.0.0" From fa3eb610bf6c9ecaed3ccbc11050b07a7adc6d55 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Wed, 18 Oct 2023 00:17:18 +0200 Subject: [PATCH 73/98] Update snapshots --- .../elm-review-something-for-new-rule/package.json | 2 +- test/run-snapshots/elm-review-something/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/run-snapshots/elm-review-something-for-new-rule/package.json b/test/run-snapshots/elm-review-something-for-new-rule/package.json index 257ea29f4..c6cbe9da0 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/package.json +++ b/test/run-snapshots/elm-review-something-for-new-rule/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "elm-doc-preview": "^5.0.5", - "elm-review": "^2.11.0-beta.2", + "elm-review": "^2.11.0-beta.3", "elm-test": "^0.19.1-revision10", "elm-tooling": "^1.13.1", "fs-extra": "^9.0.0", diff --git a/test/run-snapshots/elm-review-something/package.json b/test/run-snapshots/elm-review-something/package.json index 257ea29f4..c6cbe9da0 100644 --- a/test/run-snapshots/elm-review-something/package.json +++ b/test/run-snapshots/elm-review-something/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "elm-doc-preview": "^5.0.5", - "elm-review": "^2.11.0-beta.2", + "elm-review": "^2.11.0-beta.3", "elm-test": "^0.19.1-revision10", "elm-tooling": "^1.13.1", "fs-extra": "^9.0.0", From 5300678b5161cb2deb6f6a3295b1fcb6ea23d3de Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Wed, 18 Oct 2023 00:15:48 +0200 Subject: [PATCH 74/98] Document the --offline flag --- lib/flags.js | 4 +++- test/snapshots/help/default.txt | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/flags.js b/lib/flags.js index 8b1bc9201..aa5fee961 100644 --- a/lib/flags.js +++ b/lib/flags.js @@ -383,7 +383,9 @@ const flags = [ { name: 'offline', boolean: true, - sections: null + color: chalk.cyan, + sections: ['regular'], + description: ['Prevent making network calls.'] }, gitHubAuthFlag, { diff --git a/test/snapshots/help/default.txt b/test/snapshots/help/default.txt index 880bfb383..876b757eb 100644 --- a/test/snapshots/help/default.txt +++ b/test/snapshots/help/default.txt @@ -91,6 +91,9 @@ You can customize the review command with the following flags: --no-details Hide the details from error reports for a more compact view. + --offline + Prevent making network calls. + --ignore-dirs Ignore the reports of all rules for the specified directories. From b2d6a960a288121220862bae5694ab31af5251de Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Wed, 18 Oct 2023 00:23:59 +0200 Subject: [PATCH 75/98] Avoid downloading docs.json in offline mode --- lib/project-json-files.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/project-json-files.js b/lib/project-json-files.js index c1868148d..df99c4227 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -111,9 +111,20 @@ function getDocsJson(options, elmVersion, name, packageVersion) { packageVersion, 'docs.json' ); - return FS.readJsonFile(cacheLocation).catch(() => - readFromPackagesWebsite(cacheLocation, name, packageVersion, 'docs.json') - ); + return FS.readJsonFile(cacheLocation).catch((error) => { + // Finally, try to download it from the packages website + if (options.offline) { + // Unless we're in offline mode + throw error; + } + + return readFromPackagesWebsite( + cacheLocation, + name, + packageVersion, + 'docs.json' + ); + }); }); } From 627e89527b5392dc90dc35b8869ff184eecacc02 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Wed, 18 Oct 2023 00:47:38 +0200 Subject: [PATCH 76/98] Report an error when using both --offline and --template --- lib/options.js | 23 +++++++++++++++++++ test/flags.test.js | 14 +++++++++++ .../flags/template-and-offline-init.txt | 13 +++++++++++ .../flags/template-and-offline-run.txt | 13 +++++++++++ 4 files changed, 63 insertions(+) create mode 100644 test/snapshots/flags/template-and-offline-init.txt create mode 100644 test/snapshots/flags/template-and-offline-run.txt diff --git a/lib/options.js b/lib/options.js index b22ea580b..9b7245414 100644 --- a/lib/options.js +++ b/lib/options.js @@ -1,5 +1,6 @@ const path = require('path'); const chalk = require('chalk'); +const wrap = require('wrap-ansi'); const findUp = require('find-up'); const minimist = require('minimist'); const levenshtein = require('fastest-levenshtein'); @@ -529,6 +530,28 @@ ${Flags.buildFlag(subcommand, Flags.reportFlag)}` ) ); } + + if (args.template && args.offline) { + reportErrorAndExit( + new ErrorMessage.CustomError( + 'COMMAND REQUIRES NETWORK ACCESS', + wrap( + `I can't use ${chalk.cyan('--template')} in ${chalk.cyan( + 'offline' + )} mode, as I need network access to download the external template. + +If you have the configuration locally on your computer, you can run it by pointing to it with ${chalk.yellow( + '--config' + )}. + +Otherwise, I recommend you try to gain network access and initialize your configuration to be able to run it offline afterwards: + +`, + 80 + ) + chalk.yellow(' elm-review init --template ' + args.template) + ) + ); + } } function parseGitHubAuth(subcommand, gitHubAuth) { diff --git a/test/flags.test.js b/test/flags.test.js index 8b6017b92..1d80b2c44 100644 --- a/test/flags.test.js +++ b/test/flags.test.js @@ -115,3 +115,17 @@ test('Using the same flag twice', async () => { const output = await TestCli.runAndExpectError('--config a/ --config b/'); expect(output).toMatchFile(testName('duplicate-flags')); }); + +test('Using both --template and --offline (regular run)', async () => { + const output = await TestCli.runAndExpectError( + '--template jfmengels/elm-review-unused/example --offline' + ); + expect(output).toMatchFile(testName('template-and-offline-run')); +}); + +test('Using both --template and --offline (init)', async () => { + const output = await TestCli.runAndExpectError( + 'init --template jfmengels/elm-review-unused/example --offline' + ); + expect(output).toMatchFile(testName('template-and-offline-init')); +}); diff --git a/test/snapshots/flags/template-and-offline-init.txt b/test/snapshots/flags/template-and-offline-init.txt new file mode 100644 index 000000000..4553285a0 --- /dev/null +++ b/test/snapshots/flags/template-and-offline-init.txt @@ -0,0 +1,13 @@ +-- COMMAND REQUIRES NETWORK ACCESS --------------------------------------------- + +I can't use --template in offline mode, as I need network access to download the +external template. + +If you have the configuration locally on your computer, you can run it by +pointing to it with --config. + +Otherwise, I recommend you try to gain network access and initialize your +configuration to be able to run it offline afterwards: + + elm-review init --template jfmengels/elm-review-unused/example + diff --git a/test/snapshots/flags/template-and-offline-run.txt b/test/snapshots/flags/template-and-offline-run.txt new file mode 100644 index 000000000..4553285a0 --- /dev/null +++ b/test/snapshots/flags/template-and-offline-run.txt @@ -0,0 +1,13 @@ +-- COMMAND REQUIRES NETWORK ACCESS --------------------------------------------- + +I can't use --template in offline mode, as I need network access to download the +external template. + +If you have the configuration locally on your computer, you can run it by +pointing to it with --config. + +Otherwise, I recommend you try to gain network access and initialize your +configuration to be able to run it offline afterwards: + + elm-review init --template jfmengels/elm-review-unused/example + From 17553907d025f10fe9c4139651c4f7b7947cae49 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Wed, 18 Oct 2023 00:53:21 +0200 Subject: [PATCH 77/98] Report an error when using both --offline and new-package --- lib/options.js | 16 ++++++++++++++++ test/flags.test.js | 5 +++++ test/snapshots/flags/offline-new-package.txt | 7 +++++++ 3 files changed, 28 insertions(+) create mode 100644 test/snapshots/flags/offline-new-package.txt diff --git a/lib/options.js b/lib/options.js index 9b7245414..2302e5f89 100644 --- a/lib/options.js +++ b/lib/options.js @@ -552,6 +552,22 @@ Otherwise, I recommend you try to gain network access and initialize your config ) ); } + + if (args.offline && subcommand === 'new-package') { + reportErrorAndExit( + new ErrorMessage.CustomError( + 'COMMAND REQUIRES NETWORK ACCESS', + wrap( + `I can't use ${chalk.yellow('new-package')} in ${chalk.cyan( + 'offline' + )} mode, as I need network access to perform a number of steps. + +I recommend you try to gain network access and try again.`, + 80 + ) + ) + ); + } } function parseGitHubAuth(subcommand, gitHubAuth) { diff --git a/test/flags.test.js b/test/flags.test.js index 1d80b2c44..aa3bc8d4f 100644 --- a/test/flags.test.js +++ b/test/flags.test.js @@ -129,3 +129,8 @@ test('Using both --template and --offline (init)', async () => { ); expect(output).toMatchFile(testName('template-and-offline-init')); }); + +test('Using both new-package and --offline', async () => { + const output = await TestCli.runAndExpectError('new-package --offline'); + expect(output).toMatchFile(testName('offline-new-package')); +}); diff --git a/test/snapshots/flags/offline-new-package.txt b/test/snapshots/flags/offline-new-package.txt new file mode 100644 index 000000000..3d1dfed36 --- /dev/null +++ b/test/snapshots/flags/offline-new-package.txt @@ -0,0 +1,7 @@ +-- COMMAND REQUIRES NETWORK ACCESS --------------------------------------------- + +I can't use new-package in offline mode, as I need network access to perform a +number of steps. + +I recommend you try to gain network access and try again. + From 5fa9ad6bbc9b6e4c5b3068bf7d91d3f68083bda7 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 11:26:04 +0200 Subject: [PATCH 78/98] Extract function --- lib/project-json-files.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/project-json-files.js b/lib/project-json-files.js index df99c4227..38efd3978 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -172,9 +172,7 @@ function elmReviewDependencyCache( file ) { return path.join( - elmRoot, - 'elm-review', - options.packageJsonVersion, + elmHomeCache(options.packageJsonVersion), 'packages', elmVersion, name, @@ -183,6 +181,14 @@ function elmReviewDependencyCache( ); } +function elmHomeCache(packageJsonVersion) { + return path.join( + elmRoot, + 'elm-review', + packageJsonVersion + ); +} + module.exports = { getElmJson, getPackagePathInElmHome, @@ -190,5 +196,5 @@ module.exports = { getElmJsonFromElmHome, elmReviewDependencyCache, getDocsJson, - elmRoot + elmHomeCache }; From 6d93a0ce035acf92d46b9f029df412149bb5657f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 11:34:13 +0200 Subject: [PATCH 79/98] Store downloaded info about packages into ELM_HOME --- lib/dependency-provider.js | 18 +++++++++++++++++- lib/options.js | 13 ------------- lib/types/options.d.ts | 1 - 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index dbd36b34b..a4aa05ad4 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -7,6 +7,7 @@ const ProjectJsonFiles = require('./project-json-files'); /** * @typedef { import("./types/options").Options } Options + * @typedef { import("./types/path").Path } Path */ /** @type {boolean} */ @@ -127,7 +128,7 @@ class OnlineVersionsCache { * @returns {void} */ update(options, elmVersion) { - const cachePath = options.dependenciesCachePath(elmVersion); + const cachePath = dependenciesCachePath(options.packageJsonVersion, elmVersion); const remotePackagesUrl = 'https://package.elm-lang.org/all-packages'; if (this.map.size === 0) { @@ -444,6 +445,21 @@ function parseVersions(key, json) { return json; } +/** Cache in which we'll store information related Elm dependencies, computed by elm-solve-deps-wasm. + * + * @param {string} packageJsonVersion + * @param {string} elmVersion + * @return {Path} + */ +function dependenciesCachePath(packageJsonVersion, elmVersion) { + return path.join( + ProjectJsonFiles.elmHomeCache(packageJsonVersion), + 'dependencies-cache', + elmVersion, + 'versions-cache.json' + ); +} + /** * @param {string} pkg * @param {string} version diff --git a/lib/options.js b/lib/options.js index 2302e5f89..404ed10a7 100644 --- a/lib/options.js +++ b/lib/options.js @@ -273,19 +273,6 @@ try re-running it with ${chalk.cyan('--elmjson ')}.`, ); }, - /** Cache in which we'll store information related Elm depencies, computed by elm-solve-deps-wasm. - * - * @param {string} elmVersion - * @return {string} - */ - dependenciesCachePath: (elmVersion) => - path.join( - elmStuffFolder(), - 'dependencies-cache', - elmVersion, - 'versions-cache.json' - ), - // PATHS - THINGS TO REVIEW elmJsonPath, elmJsonPathWasSpecified: Boolean(args.elmjson), diff --git a/lib/types/options.d.ts b/lib/types/options.d.ts index 6a6cd763b..aee715df0 100644 --- a/lib/types/options.d.ts +++ b/lib/types/options.d.ts @@ -48,7 +48,6 @@ export type Options = { generatedCodePackageJson: () => Path; templateElmModulePath: (string) => Path; pathToTemplateElmJson: (string) => Path; - dependenciesCachePath: (string) => Path; elmJsonPath: Path; elmJsonPathWasSpecified: boolean; readmePath: Path; From 82c1822faf659d27a945f07478a7e2c9ba4f4cc5 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 12:10:26 +0200 Subject: [PATCH 80/98] Update snapshots --- .../elm-review-something-for-new-rule/preview/elm.json | 4 ++-- .../elm-review-something-for-new-rule/review/elm.json | 2 +- test/run-snapshots/elm-review-something/preview/elm.json | 4 ++-- test/run-snapshots/elm-review-something/review/elm.json | 2 +- test/run-snapshots/init-project/review/elm.json | 4 ++-- test/run-snapshots/init-template-project/review/elm.json | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/run-snapshots/elm-review-something-for-new-rule/preview/elm.json b/test/run-snapshots/elm-review-something-for-new-rule/preview/elm.json index 9f9783aad..923f55ea8 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/preview/elm.json +++ b/test/run-snapshots/elm-review-something-for-new-rule/preview/elm.json @@ -20,7 +20,7 @@ "elm/random": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.3", - "elm-explorations/test": "2.1.1", + "elm-explorations/test": "2.1.2", "miniBill/elm-unicode": "1.0.3", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" @@ -28,7 +28,7 @@ }, "test-dependencies": { "direct": { - "elm-explorations/test": "2.1.1" + "elm-explorations/test": "2.1.2" }, "indirect": {} } diff --git a/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json b/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json index e4e59ba61..45e2c3c5e 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json +++ b/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json @@ -28,7 +28,7 @@ "elm/regex": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.3", - "elm-explorations/test": "2.1.1", + "elm-explorations/test": "2.1.2", "miniBill/elm-unicode": "1.0.3", "pzp1997/assoc-list": "1.0.0", "rtfeldman/elm-hex": "1.0.0", diff --git a/test/run-snapshots/elm-review-something/preview/elm.json b/test/run-snapshots/elm-review-something/preview/elm.json index 9f9783aad..923f55ea8 100644 --- a/test/run-snapshots/elm-review-something/preview/elm.json +++ b/test/run-snapshots/elm-review-something/preview/elm.json @@ -20,7 +20,7 @@ "elm/random": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.3", - "elm-explorations/test": "2.1.1", + "elm-explorations/test": "2.1.2", "miniBill/elm-unicode": "1.0.3", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" @@ -28,7 +28,7 @@ }, "test-dependencies": { "direct": { - "elm-explorations/test": "2.1.1" + "elm-explorations/test": "2.1.2" }, "indirect": {} } diff --git a/test/run-snapshots/elm-review-something/review/elm.json b/test/run-snapshots/elm-review-something/review/elm.json index e4e59ba61..45e2c3c5e 100644 --- a/test/run-snapshots/elm-review-something/review/elm.json +++ b/test/run-snapshots/elm-review-something/review/elm.json @@ -28,7 +28,7 @@ "elm/regex": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.3", - "elm-explorations/test": "2.1.1", + "elm-explorations/test": "2.1.2", "miniBill/elm-unicode": "1.0.3", "pzp1997/assoc-list": "1.0.0", "rtfeldman/elm-hex": "1.0.0", diff --git a/test/run-snapshots/init-project/review/elm.json b/test/run-snapshots/init-project/review/elm.json index 1acdd7ae0..c71176db0 100644 --- a/test/run-snapshots/init-project/review/elm.json +++ b/test/run-snapshots/init-project/review/elm.json @@ -19,7 +19,7 @@ "elm/random": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.3", - "elm-explorations/test": "2.1.1", + "elm-explorations/test": "2.1.2", "miniBill/elm-unicode": "1.0.3", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" @@ -27,7 +27,7 @@ }, "test-dependencies": { "direct": { - "elm-explorations/test": "2.1.1" + "elm-explorations/test": "2.1.2" }, "indirect": {} } diff --git a/test/run-snapshots/init-template-project/review/elm.json b/test/run-snapshots/init-template-project/review/elm.json index 574029ff5..c4e261de7 100644 --- a/test/run-snapshots/init-template-project/review/elm.json +++ b/test/run-snapshots/init-template-project/review/elm.json @@ -20,7 +20,7 @@ "elm/random": "1.0.0", "elm/time": "1.0.0", "elm/virtual-dom": "1.0.3", - "elm-explorations/test": "2.1.1", + "elm-explorations/test": "2.1.2", "miniBill/elm-unicode": "1.0.3", "rtfeldman/elm-hex": "1.0.0", "stil4m/structured-writer": "1.0.3" From b6ca921d8feed484c0105daf4b8d830c68bf5254 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 19 Oct 2023 17:04:33 +0200 Subject: [PATCH 81/98] Fix TypeScript errors --- lib/flags.js | 4 +++- lib/main.js | 8 ++++++++ lib/state.js | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/flags.js b/lib/flags.js index aa5fee961..904f60f56 100644 --- a/lib/flags.js +++ b/lib/flags.js @@ -491,7 +491,7 @@ ${description /** * @param {Subcommand | null} subcommand - * @returns {"initDescription" | "newPackageDescription" | "description" } + * @returns {"initDescription" | "newPackageDescription" | "description"} */ function preferredDescriptionFieldFor(subcommand) { switch (subcommand) { @@ -503,6 +503,8 @@ function preferredDescriptionFieldFor(subcommand) { return 'description'; case 'suppress': return 'description'; + case 'prepare-offline': + return 'description'; case null: return 'description'; } diff --git a/lib/main.js b/lib/main.js index c5b96349f..8560649c5 100644 --- a/lib/main.js +++ b/lib/main.js @@ -21,6 +21,14 @@ const ErrorMessage = require('./error-message'); const SuppressedErrors = require('./suppressed-errors'); const Watch = require('./watch'); + +/** + * @typedef { import("./types/options").Options } Options + */ + +/** + * @type {Options} + */ const options = AppState.getOptions(); process.on('uncaughtException', errorHandler); diff --git a/lib/state.js b/lib/state.js index 72ac32ee7..5f2a52b87 100644 --- a/lib/state.js +++ b/lib/state.js @@ -221,6 +221,7 @@ function updateFilesInCache(files) { // ACCESS +/** @returns {Options} */ function getOptions() { return options; } From 174e3a922da7b62f8101a63a74b718d8d4c4f833 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Wed, 18 Oct 2023 18:31:00 +0200 Subject: [PATCH 82/98] Add prepare-offline command --- lib/flags.js | 9 +++++++-- lib/help.js | 22 ++++++++++++++++++++- lib/main.js | 26 +++++++++++++++++++++++++ lib/options.js | 11 +++++++++-- lib/types/flag.d.ts | 3 ++- lib/types/options.d.ts | 7 ++++++- test/help.test.js | 5 +++++ test/snapshots/help/default.txt | 6 +++++- test/snapshots/help/prepare-offline.txt | 14 +++++++++++++ 9 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 test/snapshots/help/prepare-offline.txt diff --git a/lib/flags.js b/lib/flags.js index 904f60f56..94c088256 100644 --- a/lib/flags.js +++ b/lib/flags.js @@ -193,7 +193,7 @@ const flags = [ mayBeUsedSeveralTimes: false, usesEquals: false, color: chalk.cyan, - sections: ['regular', 'init', 'new-package'], + sections: ['regular', 'init', 'new-package', 'prepare-offline'], description: [ `Specify the path to the ${chalk.magentaBright('elm')} compiler.` ], @@ -385,7 +385,12 @@ const flags = [ boolean: true, color: chalk.cyan, sections: ['regular'], - description: ['Prevent making network calls.'] + description: [ + 'Prevent making network calls. You might need to run', + `${chalk.yellow( + 'elm-review prepare-offline' + )} beforehand to avoid problems.` + ] }, gitHubAuthFlag, { diff --git a/lib/help.js b/lib/help.js index 38ff8fffb..9dba33ba5 100644 --- a/lib/help.js +++ b/lib/help.js @@ -43,6 +43,9 @@ function review(options) { ${chalk.yellow('elm-review new-rule [RULE-NAME]')} Adds a new rule to your review configuration or review package. + ${chalk.yellow('elm-review prepare-offline')} + Prepares running ${chalk.greenBright('elm-review')} in offline mode using ${chalk.cyan('--offline')}. + You can customize the review command with the following flags: ${Flags.buildFlags('regular', null)} @@ -159,10 +162,27 @@ ${Flags.buildFlags('new-rule', 'new-rule')} `); } +function prepareOffline() { + console.log(`The prepare-offline command allows the tool to run in offline mode using +the ${chalk.cyan('--offline')} flag. + +This will build the review configuration application and download the +dependencies of the project to review. It requires network access. + +If you change your the review configuration, you might need to re-run this +command to work again in offline mode. + +You can customize the new-rule command with the following flags: + +${Flags.buildFlags('prepare-offline', 'prepare-offline')} +`); +} + module.exports = { review, suppress, init, newRule, - newPackage + newPackage, + prepareOffline }; diff --git a/lib/main.js b/lib/main.js index 8560649c5..306394f90 100644 --- a/lib/main.js +++ b/lib/main.js @@ -6,6 +6,7 @@ if (!process.argv.includes('--no-color')) { } const path = require('path'); +const chalk = require('chalk'); const Help = require('./help'); const Init = require('./init'); const Builder = require('./build'); @@ -106,6 +107,23 @@ async function runElmReviewInWatchMode() { await Runner.runReview(options, initialization.app).catch(errorHandler); } +async function prepareOffline() { + const {elmModulePath, reviewElmJson, appHash} = await Builder.build(options); + + if (!elmModulePath) { + AppState.exitRequested(1); + return; + } + + await Builder.buildElmParser(options, reviewElmJson); + + console.log(`${chalk.greenBright('elm-review')} is now ready to be run ${chalk.cyan('--offline')}. + +You will need to run ${chalk.yellow('elm-review prepare-offline')} to keep the offline mode working +if either your review configuration or your project's dependencies change.`); + process.exit(1); +} + module.exports = () => { if (options.version) { console.log(Anonymize.version(options)); @@ -146,6 +164,14 @@ module.exports = () => { } } + if (options.subcommand === 'prepare-offline') { + if (options.help) { + return Help.prepareOffline(); + } + + return prepareOffline(); + } + if (options.help) { return Help.review(options); } diff --git a/lib/options.js b/lib/options.js index 404ed10a7..da5e42a8b 100644 --- a/lib/options.js +++ b/lib/options.js @@ -28,7 +28,13 @@ course of action is and how to get forward. /** * @type {Subcommand[]} */ -const availableSubcommands = ['init', 'new-package', 'new-rule', 'suppress']; +const availableSubcommands = [ + 'init', + 'new-package', + 'new-rule', + 'suppress', + 'prepare-offline' +]; let containsHelp = false; @@ -347,7 +353,8 @@ function findElmJsonPath(args, subcommand) { subcommand && subcommand !== 'new-rule' && subcommand !== 'init' && - subcommand !== 'suppress' + subcommand !== 'suppress' && + subcommand !== 'prepare-offline' ) { return null; } diff --git a/lib/types/flag.d.ts b/lib/types/flag.d.ts index 0f2370982..91587fced 100644 --- a/lib/types/flag.d.ts +++ b/lib/types/flag.d.ts @@ -7,7 +7,8 @@ export type Section = | 'init' | 'new-rule' | 'new-package' - | 'suppress-subcommand'; + | 'suppress-subcommand' + | 'prepare-offline'; export type Flag = BaseFlag & SingleOrMulti & Display; diff --git a/lib/types/options.d.ts b/lib/types/options.d.ts index aee715df0..30b1ae867 100644 --- a/lib/types/options.d.ts +++ b/lib/types/options.d.ts @@ -78,4 +78,9 @@ export type Template = { reference: string | null; }; -export type Subcommand = 'init' | 'new-package' | 'new-rule' | 'suppress'; +export type Subcommand = + | 'init' + | 'new-package' + | 'new-rule' + | 'suppress' + | 'prepare-offline'; diff --git a/test/help.test.js b/test/help.test.js index 201c750f4..7426e7075 100644 --- a/test/help.test.js +++ b/test/help.test.js @@ -28,3 +28,8 @@ test('new-rule --help', async () => { const output = await TestCli.run('new-rule --help'); expect(output).toMatchFile(testName('new-rule')); }); + +test('prepare-offline --help', async () => { + const output = await TestCli.run('prepare-offline --help'); + expect(output).toMatchFile(testName('prepare-offline')); +}); diff --git a/test/snapshots/help/default.txt b/test/snapshots/help/default.txt index 876b757eb..02327a45c 100644 --- a/test/snapshots/help/default.txt +++ b/test/snapshots/help/default.txt @@ -26,6 +26,9 @@ You are using elm-review . elm-review new-rule [RULE-NAME] Adds a new rule to your review configuration or review package. + elm-review prepare-offline + Prepares running elm-review in offline mode using --offline. + You can customize the review command with the following flags: --unsuppress @@ -92,7 +95,8 @@ You can customize the review command with the following flags: Hide the details from error reports for a more compact view. --offline - Prevent making network calls. + Prevent making network calls. You might need to run + elm-review prepare-offline beforehand to avoid problems. --ignore-dirs Ignore the reports of all rules for the specified directories. diff --git a/test/snapshots/help/prepare-offline.txt b/test/snapshots/help/prepare-offline.txt new file mode 100644 index 000000000..f80ede464 --- /dev/null +++ b/test/snapshots/help/prepare-offline.txt @@ -0,0 +1,14 @@ +The prepare-offline command allows the tool to run in offline mode using +the --offline flag. + +This will build the review configuration application and download the +dependencies of the project to review. It requires network access. + +If you change your the review configuration, you might need to re-run this +command to work again in offline mode. + +You can customize the new-rule command with the following flags: + + --compiler + Specify the path to the elm compiler. + From 83f487241e6fb98c2d34333d5becb2a67f7fa99a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 19 Oct 2023 17:50:26 +0200 Subject: [PATCH 83/98] Define ELM_HOME in tests --- test/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/run.sh b/test/run.sh index 737c82615..f5ff7d45d 100755 --- a/test/run.sh +++ b/test/run.sh @@ -5,6 +5,7 @@ set -e CWD=$(pwd) CMD="elm-review --no-color" TMP="$CWD/temporary" +ELM_HOME="$CWD/elm-home" SNAPSHOTS="$CWD/run-snapshots" SUBCOMMAND="$1" REPLACE_SCRIPT="node $CWD/replace-local-path.js" @@ -137,6 +138,7 @@ function createAndGoIntoFolder { } rm -rf "$TMP" \ + "$ELM_HOME" \ "$CWD/config-empty/elm-stuff" \ "$CWD/config-error-debug/elm-stuff" \ "$CWD/config-error-unknown-module/elm-stuff" \ From b5b16fb8548f5da776584aaf64c23ac0fc4491cd Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 19 Oct 2023 17:56:22 +0200 Subject: [PATCH 84/98] Define ELM_HOME when running tests --- test/run.sh | 5 ++--- tsconfig.json | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/run.sh b/test/run.sh index f5ff7d45d..f344a6048 100755 --- a/test/run.sh +++ b/test/run.sh @@ -5,7 +5,7 @@ set -e CWD=$(pwd) CMD="elm-review --no-color" TMP="$CWD/temporary" -ELM_HOME="$CWD/elm-home" +ELM_HOME="$TMP/elm-home" SNAPSHOTS="$CWD/run-snapshots" SUBCOMMAND="$1" REPLACE_SCRIPT="node $CWD/replace-local-path.js" @@ -58,7 +58,7 @@ function runAndRecord { local ARGS=$3 local FILE=$4 echo -e "\x1B[33m- $TITLE\x1B[0m: \x1B[34m elm-review --FOR-TESTS $ARGS\x1B[0m" - eval "$LOCAL_COMMAND$AUTH --FOR-TESTS $ARGS" 2>&1 \ + eval "ELM_HOME=$ELM_HOME $LOCAL_COMMAND$AUTH --FOR-TESTS $ARGS" 2>&1 \ | $REPLACE_SCRIPT \ > "$SNAPSHOTS/$FILE" } @@ -138,7 +138,6 @@ function createAndGoIntoFolder { } rm -rf "$TMP" \ - "$ELM_HOME" \ "$CWD/config-empty/elm-stuff" \ "$CWD/config-error-debug/elm-stuff" \ "$CWD/config-error-unknown-module/elm-stuff" \ diff --git a/tsconfig.json b/tsconfig.json index e84783d5f..dfa804c03 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,6 +25,7 @@ "node_modules", "**/elm-stuff/", ".eslintrc.js", - "new-package/elm-review-package-tests/check-previews-compile.js" + "new-package/elm-review-package-tests/check-previews-compile.js", + "test/temporary" ] } From 8bf5abdbbf96ac0d6d51d029a55dba12ad053610 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 11:15:36 +0200 Subject: [PATCH 85/98] Clean up tmp folder --- test/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/run.sh b/test/run.sh index f344a6048..eb08ee1e9 100755 --- a/test/run.sh +++ b/test/run.sh @@ -411,3 +411,5 @@ createTestSuiteWithDifferentReportFormats "$CMD" \ "Using both --config and --template" \ "--config ../config-that-triggers-no-errors --template jfmengels/test-node-elm-review" \ "remote-configuration-with-config-flag" + +rm -rf "$TMP" From a941d0682bcec7e6927f09a97b4ede15e617fd3f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 13:50:06 +0200 Subject: [PATCH 86/98] Move package path --- lib/dependency-provider.js | 1 - lib/project-json-files.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index a4aa05ad4..0da0768d5 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -454,7 +454,6 @@ function parseVersions(key, json) { function dependenciesCachePath(packageJsonVersion, elmVersion) { return path.join( ProjectJsonFiles.elmHomeCache(packageJsonVersion), - 'dependencies-cache', elmVersion, 'versions-cache.json' ); diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 38efd3978..8e1415658 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -173,8 +173,8 @@ function elmReviewDependencyCache( ) { return path.join( elmHomeCache(options.packageJsonVersion), - 'packages', elmVersion, + 'packages', name, packageVersion, file From 93b777f58a79cf3593249c4ba538e33e8a334ec1 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 13:56:06 +0200 Subject: [PATCH 87/98] Add TypeScript types --- lib/build.js | 11 ++++++ lib/elm-binary.js | 19 +++++++++++ lib/remote-template.js | 2 +- lib/template-dependencies.js | 66 +++++++++++++++++++++++++++++++----- lib/types/build.d.ts | 11 +++++- 5 files changed, 98 insertions(+), 11 deletions(-) diff --git a/lib/build.js b/lib/build.js index d875559f3..34fffb941 100644 --- a/lib/build.js +++ b/lib/build.js @@ -22,6 +22,7 @@ const TemplateDependencies = require('./template-dependencies'); * @typedef { import("./types/build").BuildResult } BuildResult * @typedef { import("./types/build").AppHash } AppHash * @typedef { import("./types/build").ReviewElmJson } ReviewElmJson + * @typedef { import("./types/elm-version").ElmVersion } ElmVersion * @typedef { import("./types/path").Path } Path */ @@ -600,6 +601,16 @@ https://github.com/jfmengels/node-elm-review/issues/new ); } +/** Create `elm.json` file for the parser application, which will use the exact same version + * of `stil4m/elm-syntax` as the review application. + * + * @param {Options} options + * @param {Path} buildFolder + * @param {ElmVersion} elmVersion + * @param {ReviewElmJson} parseElmElmJson + * @param {ElmVersion} elmSyntaxVersion + * @return {Promise} + */ async function createParserElmJsonFile( options, buildFolder, diff --git a/lib/elm-binary.js b/lib/elm-binary.js index 752836dd5..ca43a8bb1 100644 --- a/lib/elm-binary.js +++ b/lib/elm-binary.js @@ -5,6 +5,16 @@ const which = require('which'); const spawn = require('cross-spawn'); const ErrorMessage = require('./error-message'); +/** + * @typedef { import("./types/options").Options } Options + * @typedef { import("./types/path").Path } Path + */ + +/** Get the path to the Elm binary + * + * @param {Options} options + * @return {Promise} + */ function getElmBinary(options) { const whichAsync = util.promisify(which); if (options.compiler === undefined) { @@ -35,6 +45,11 @@ A few options: }); } +/** Get the version of the Elm compiler + * + * @param {Path} elmBinary + * @return {Promise} + */ async function getElmVersion(elmBinary) { const result = spawn.sync(elmBinary, ['--version'], { silent: true, @@ -48,6 +63,10 @@ async function getElmVersion(elmBinary) { return trimVersion(result.stdout.toString()); } +/** + * @param {string} version + * @return {string} + */ function trimVersion(version) { const index = version.indexOf('-'); if (index === -1) { diff --git a/lib/remote-template.js b/lib/remote-template.js index 25c8aa722..6045f3999 100644 --- a/lib/remote-template.js +++ b/lib/remote-template.js @@ -101,7 +101,7 @@ async function getRemoteElmJson( elmJson['test-dependencies'].direct = {}; elmJson['test-dependencies'].indirect = {}; - return TemplateDependencies.update(options, reviewElmJsonPath, elmJson); + return TemplateDependencies.update(options, elmJson); } async function downloadTemplateElmJson(template, commit) { diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 296d2370d..390d1a305 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -5,10 +5,14 @@ const MinVersion = require('./min-version'); const DependencyProvider = require('./dependency-provider'); +/** @type {DependencyProvider | null} */ let dependencyProvider = null; /** * @typedef { import("./types/options").Options } Options + * @typedef { import("./types/elm-version").ElmVersion } ElmVersion + * @typedef { import("./types/build").ReviewElmJson } ReviewElmJson + * @typedef { import("./types/build").ApplicationDependencies } ApplicationDependencies * @typedef { import("./types/template-dependencies").TemplateDependenciesError } TemplateDependenciesError */ @@ -21,6 +25,12 @@ module.exports = { // GET +/** + * + * @param {Options} options + * @param {ReviewElmJson} elmJson + * @return {ApplicationDependencies} + */ function get(options, elmJson) { const extra = { 'elm/json': '1.0.0 <= v < 2.0.0', @@ -45,6 +55,13 @@ function get(options, elmJson) { // ADD ELM-SYNTAX +/** Compute the dependencies if we were to replace the version of `stil4m/elm-syntax` with the given one. + * + * @param {Options} options + * @param {ElmVersion} elmVersion + * @param {ElmVersion} elmSyntaxVersion + * @return {ApplicationDependencies} + */ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { const elmJson = `{ "type": "application", @@ -65,6 +82,8 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { "indirect": {} } }`; + + // We want to use this exact version of `stil4m/elm-syntax`. const extra = { 'stil4m/elm-syntax': `${elmSyntaxVersion} <= v < ${nextPatchVersion( elmSyntaxVersion @@ -76,11 +95,11 @@ function addElmSyntax(options, elmVersion, elmSyntaxVersion) { /** * @param {Options} options - * @param {string} elmVersion + * @param {ElmVersion} elmVersion * @param {string} elmJson * @param {Record}extra * @param {boolean} onlineFirst - * @return {{direct: Record, indirect: Record}} + * @return {ApplicationDependencies} */ function solve(options, elmVersion, elmJson, extra, onlineFirst) { dependencyProvider = dependencyProvider || new DependencyProvider(); @@ -104,19 +123,36 @@ function solve(options, elmVersion, elmJson, extra, onlineFirst) { } } +/** Returns the next major version. + * Ex: 2.13.0 -> 3.0.0 + * + * @param {ElmVersion} version + * @return {ElmVersion} + */ function nextMajorVersion(version) { const [major] = version.split('.'); return `${parseInt(major, 10) + 1}.0.0`; } +/** Returns the next patch version. + * Ex: 2.13.0 -> 2.13.1 + * + * @param {ElmVersion} version + * @return {ElmVersion} + */ function nextPatchVersion(version) { const [major, minor, patch] = version.split('.'); return `${major}.${minor}.${parseInt(patch, 10) + 1}`; } -// ADD - +/** Create a new elm.json with basic `elm-review` dependencies. + * + * @param {Options} options + * @param {ElmVersion} elmVersion + * @return {ReviewElmJson} + */ function createNewReviewElmJson(options, elmVersion) { + /** @type {ReviewElmJson} */ const elmJson = { type: 'application', 'source-directories': ['src'], @@ -168,6 +204,12 @@ function createNewReviewElmJson(options, elmVersion) { return elmJson; } +/** Filter out test-dependencies that are already in the regular dependencies. + * + * @param {Record} testDependencies + * @param {Record} regularDependencies + * @return {Record} + */ function filterOutDuplicateDependencies(testDependencies, regularDependencies) { return Object.fromEntries( Object.entries(testDependencies).filter( @@ -176,9 +218,13 @@ function filterOutDuplicateDependencies(testDependencies, regularDependencies) { ); } -// UPDATE - -async function update(options, pathToElmJson, elmJson) { +/** Update versions of dependencies to their latest (compatible) version. + * + * @param {Options} options + * @param {ReviewElmJson} elmJson + * @return {Promise} + */ +async function update(options, elmJson) { const extra = { 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', 'jfmengels/elm-review': MinVersion.supportedRange @@ -206,6 +252,8 @@ async function update(options, pathToElmJson, elmJson) { } function teardownDependenciesProvider() { - dependencyProvider.tearDown(); - dependencyProvider = null; + if (dependencyProvider) { + dependencyProvider.tearDown(); + dependencyProvider = null; + } } diff --git a/lib/types/build.d.ts b/lib/types/build.d.ts index fc4b2d595..016683420 100644 --- a/lib/types/build.d.ts +++ b/lib/types/build.d.ts @@ -1,4 +1,5 @@ import type {Path} from './types/path'; +import type {ElmVersion} from './types/elm-version'; export type BuildResult = { elmModulePath: Path | null; @@ -10,6 +11,14 @@ export type BuildResult = { export type AppHash = string; export type ReviewElmJson = { + type: 'application'; + 'elm-version': ElmVersion; 'source-directories': Array; - dependencies: Record; + dependencies: ApplicationDependencies; + 'test-dependencies': ApplicationDependencies; +}; + +export type ApplicationDependencies = { + direct: Record; + indirect: Record; }; From 6802835ed38817666c81c2c06de07146fa97d9bc Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 14:14:16 +0200 Subject: [PATCH 88/98] Formatting --- lib/build.js | 2 +- lib/dependency-provider.js | 5 ++++- lib/main.js | 9 ++++++--- lib/project-json-files.js | 6 +----- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/build.js b/lib/build.js index 34fffb941..23f9953a6 100644 --- a/lib/build.js +++ b/lib/build.js @@ -311,7 +311,7 @@ async function createTemplateProject( const finalElmJson = updateSourceDirectories(options, userSrc, { ...reviewElmJson, dependencies, - ['test-dependencies']: {direct: {}, indirect: {}} + 'test-dependencies': {direct: {}, indirect: {}} }); const finalElmJsonAsString = JSON.stringify(finalElmJson, null, 4); if (previousElmJson !== finalElmJsonAsString) { diff --git a/lib/dependency-provider.js b/lib/dependency-provider.js index 0da0768d5..3bcb26172 100644 --- a/lib/dependency-provider.js +++ b/lib/dependency-provider.js @@ -128,7 +128,10 @@ class OnlineVersionsCache { * @returns {void} */ update(options, elmVersion) { - const cachePath = dependenciesCachePath(options.packageJsonVersion, elmVersion); + const cachePath = dependenciesCachePath( + options.packageJsonVersion, + elmVersion + ); const remotePackagesUrl = 'https://package.elm-lang.org/all-packages'; if (this.map.size === 0) { diff --git a/lib/main.js b/lib/main.js index 306394f90..b43cd0e9f 100644 --- a/lib/main.js +++ b/lib/main.js @@ -22,7 +22,6 @@ const ErrorMessage = require('./error-message'); const SuppressedErrors = require('./suppressed-errors'); const Watch = require('./watch'); - /** * @typedef { import("./types/options").Options } Options */ @@ -117,9 +116,13 @@ async function prepareOffline() { await Builder.buildElmParser(options, reviewElmJson); - console.log(`${chalk.greenBright('elm-review')} is now ready to be run ${chalk.cyan('--offline')}. + console.log(`${chalk.greenBright( + 'elm-review' + )} is now ready to be run ${chalk.cyan('--offline')}. -You will need to run ${chalk.yellow('elm-review prepare-offline')} to keep the offline mode working +You will need to run ${chalk.yellow( + 'elm-review prepare-offline' + )} to keep the offline mode working if either your review configuration or your project's dependencies change.`); process.exit(1); } diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 8e1415658..1fe3b3a58 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -182,11 +182,7 @@ function elmReviewDependencyCache( } function elmHomeCache(packageJsonVersion) { - return path.join( - elmRoot, - 'elm-review', - packageJsonVersion - ); + return path.join(elmRoot, 'elm-review', packageJsonVersion); } module.exports = { From 753c8324a22f7c4370566be3b03625f6141041fe Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sat, 21 Oct 2023 15:26:53 +0200 Subject: [PATCH 89/98] Download dependencies of the target project --- lib/elm-binary.js | 26 +++++++++++++++++++++++++- lib/main.js | 6 +++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/elm-binary.js b/lib/elm-binary.js index ca43a8bb1..dd1cdcb96 100644 --- a/lib/elm-binary.js +++ b/lib/elm-binary.js @@ -63,6 +63,29 @@ async function getElmVersion(elmBinary) { return trimVersion(result.stdout.toString()); } +/** Download the dependencies of the project to analyze. + * + * @param {Path} elmBinary + * @param {Path} elmJsonPath + * @return {Promise} + */ +async function downloadDependenciesOfElmJson(elmBinary, elmJsonPath) { + const result = spawn.sync(elmBinary, ['make', '--report=json'], { + cwd: path.dirname(elmJsonPath), + silent: false, + env: process.env + }); + + if (result.status !== 0) { + const error = JSON.parse(result.stderr.toString()); + // TODO Check for other kinds of errors + if (error.title !== 'NO INPUT') { + // TODO Print error nicely + throw new Error(error); + } + } +} + /** * @param {string} version * @return {string} @@ -78,5 +101,6 @@ function trimVersion(version) { module.exports = { getElmBinary, - getElmVersion + getElmVersion, + downloadDependenciesOfElmJson }; diff --git a/lib/main.js b/lib/main.js index b43cd0e9f..e380afc00 100644 --- a/lib/main.js +++ b/lib/main.js @@ -17,6 +17,7 @@ const NewRule = require('./new-rule'); const Anonymize = require('./anonymize'); const newPackage = require('./new-package'); const AppWrapper = require('./app-wrapper'); +const ElmBinary = require('./elm-binary'); const ResultCache = require('./result-cache'); const ErrorMessage = require('./error-message'); const SuppressedErrors = require('./suppressed-errors'); @@ -107,7 +108,10 @@ async function runElmReviewInWatchMode() { } async function prepareOffline() { - const {elmModulePath, reviewElmJson, appHash} = await Builder.build(options); + const elmBinary = await ElmBinary.getElmBinary(options); + await ElmBinary.downloadDependenciesOfElmJson(elmBinary, options.elmJsonPath); + + const {elmModulePath, reviewElmJson} = await Builder.build(options); if (!elmModulePath) { AppState.exitRequested(1); From 2987a29fc5ee58a470a1ed14a81ee5eb667ec605 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 22 Oct 2023 11:13:52 +0200 Subject: [PATCH 90/98] Rename get to addRequiredDependencies --- lib/build.js | 2 +- lib/template-dependencies.js | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/build.js b/lib/build.js index 23f9953a6..20567ab0c 100644 --- a/lib/build.js +++ b/lib/build.js @@ -306,7 +306,7 @@ async function createTemplateProject( // Load review project's elm.json file contents const [previousElmJson, dependencies] = await Promise.all([ FS.readFile(elmJsonPath).catch(() => null), - TemplateDependencies.get(options, reviewElmJson) + TemplateDependencies.addRequiredDependencies(options, reviewElmJson) ]); const finalElmJson = updateSourceDirectories(options, userSrc, { ...reviewElmJson, diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 390d1a305..d2e50c0d1 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -17,21 +17,19 @@ let dependencyProvider = null; */ module.exports = { - get, + addRequiredDependencies, createNewReviewElmJson, update, addElmSyntax }; -// GET - -/** +/** Add required dependencies for the application elm.json file. * * @param {Options} options * @param {ReviewElmJson} elmJson * @return {ApplicationDependencies} */ -function get(options, elmJson) { +function addRequiredDependencies(options, elmJson) { const extra = { 'elm/json': '1.0.0 <= v < 2.0.0', 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', From 3d4c0b6a652b8d49b82336947beaa3136d56ec09 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 22 Oct 2023 11:43:34 +0200 Subject: [PATCH 91/98] Make a nicer error when dependencies can't be updated --- lib/template-dependencies.js | 39 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index d2e50c0d1..c8fb27225 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -1,8 +1,5 @@ -/* - * Credit goes to @zwilias, from his PR here https://github.com/rtfeldman/node-test-runner/pull/356/files - */ - const MinVersion = require('./min-version'); +const ErrorMessage = require('./error-message'); const DependencyProvider = require('./dependency-provider'); /** @type {DependencyProvider | null} */ @@ -36,19 +33,29 @@ function addRequiredDependencies(options, elmJson) { 'elm/project-metadata-utils': '1.0.0 <= v < 2.0.0' }; - const dependencies = solve( - options, - elmJson['elm-version'], - JSON.stringify(elmJson), - extra, - false - ); - if (options.localElmReviewSrc) { - delete dependencies.direct['jfmengels/elm-review']; - delete dependencies.indirect['jfmengels/elm-review']; - } + try { + const dependencies = solve( + options, + elmJson['elm-version'], + JSON.stringify(elmJson), + extra, + false + ); + if (options.localElmReviewSrc) { + delete dependencies.direct['jfmengels/elm-review']; + delete dependencies.indirect['jfmengels/elm-review']; + } - return dependencies; + return dependencies; + } catch (error) { + throw new ErrorMessage.CustomError( + 'CONFIGURATION COMPILATION ERROR', + `I encountered a problem when solving dependencies for creating the parser application: + +${error.toString().replace(/^Error: /, '')}`, + null + ); + } } // ADD ELM-SYNTAX From 4dc64d6ec3fe17173a348138b2c9ff81d017737e Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Sun, 22 Oct 2023 15:22:12 +0200 Subject: [PATCH 92/98] Move creation/loading of elm.json for the parser application --- lib/build.js | 46 ++++--------------------- lib/template-dependencies.js | 66 +++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 66 deletions(-) diff --git a/lib/build.js b/lib/build.js index 20567ab0c..2b563b934 100644 --- a/lib/build.js +++ b/lib/build.js @@ -547,29 +547,6 @@ async function buildElmParser(options, reviewElmJson) { Debug.log(`Building parser app for elm-syntax v${elmSyntaxVersion}`); - const parseElmElmJsonPath = path.resolve(parseElmFolder, 'elm.json'); - const parseElmElmJson = await FS.readJsonFile(parseElmElmJsonPath).catch( - (error) => { - if (error.code === 'ENOENT') { - return Promise.reject( - new ErrorMessage.CustomError( - // prettier-ignore - 'UNEXPECTED INTERNAL ERROR', - // prettier-ignore - `I was expecting to find the "parseElm" project at ${chalk.cyan(parseElmElmJsonPath)} but could not find it. - -Please open an issue at the following link: -https://github.com/jfmengels/node-elm-review/issues/new -`, - options.elmJsonPath - ) - ); - } - - return Promise.reject(error); - } - ); - const buildFolder = options.buildFolderForParserApp(); const [elmBinary] = await Promise.all([ @@ -578,7 +555,6 @@ https://github.com/jfmengels/node-elm-review/issues/new options, buildFolder, reviewElmJson['elm-version'], - parseElmElmJson, elmSyntaxVersion ), // Needed when the user has `"type": "module"` in their package.json. @@ -607,7 +583,6 @@ https://github.com/jfmengels/node-elm-review/issues/new * @param {Options} options * @param {Path} buildFolder * @param {ElmVersion} elmVersion - * @param {ReviewElmJson} parseElmElmJson * @param {ElmVersion} elmSyntaxVersion * @return {Promise} */ @@ -615,30 +590,23 @@ async function createParserElmJsonFile( options, buildFolder, elmVersion, - parseElmElmJson, elmSyntaxVersion ) { - const dependencies = TemplateDependencies.addElmSyntax( + /** @type {ReviewElmJson} */ + const parseElmElmJson = await TemplateDependencies.addElmSyntax( options, elmVersion, elmSyntaxVersion ); + parseElmElmJson['source-directories'] = parseElmElmJson[ + 'source-directories' + ].map((dir) => path.resolve(parseElmFolder, dir)); + await FS.mkdirp(buildFolder); return FS.writeFile( path.resolve(buildFolder, 'elm.json'), - JSON.stringify( - { - ...parseElmElmJson, - 'elm-version': elmVersion, - dependencies, - 'source-directories': parseElmElmJson['source-directories'].map((dir) => - path.resolve(parseElmFolder, dir) - ) - }, - null, - 2 - ) + JSON.stringify(parseElmElmJson, null, 2) ); } diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index c8fb27225..a19b3e432 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -1,10 +1,15 @@ +const path = require('path'); +const chalk = require('chalk'); const MinVersion = require('./min-version'); const ErrorMessage = require('./error-message'); const DependencyProvider = require('./dependency-provider'); +const FS = require('./fs-wrapper'); /** @type {DependencyProvider | null} */ let dependencyProvider = null; +const parseElmFolder = path.join(__dirname, '../parseElm'); + /** * @typedef { import("./types/options").Options } Options * @typedef { import("./types/elm-version").ElmVersion } ElmVersion @@ -65,37 +70,44 @@ ${error.toString().replace(/^Error: /, '')}`, * @param {Options} options * @param {ElmVersion} elmVersion * @param {ElmVersion} elmSyntaxVersion - * @return {ApplicationDependencies} + * @return {Promise} */ -function addElmSyntax(options, elmVersion, elmSyntaxVersion) { - const elmJson = `{ - "type": "application", - "source-directories": [ - "src", - "../ast-codec/src" - ], - "elm-version": "0.19.1", - "dependencies": { - "direct": { - "elm/core": "1.0.5", - "elm/json": "1.1.3" - }, - "indirect": {} - }, - "test-dependencies": { - "direct": {}, - "indirect": {} +async function addElmSyntax(options, elmVersion, elmSyntaxVersion) { + const elmJsonPath = path.resolve(parseElmFolder, 'elm.json'); + const elmJson = await FS.readJsonFile(elmJsonPath).catch((error) => { + if (error.code === 'ENOENT') { + return Promise.reject( + new ErrorMessage.CustomError( + // prettier-ignore + 'UNEXPECTED INTERNAL ERROR', + // prettier-ignore + `I was expecting to find the "parseElm" project at ${chalk.cyan(elmJsonPath)} but could not find it. + +Please open an issue at the following link: +https://github.com/jfmengels/node-elm-review/issues/new +`, + options.elmJsonPath + ) + ); } -}`; - // We want to use this exact version of `stil4m/elm-syntax`. - const extra = { - 'stil4m/elm-syntax': `${elmSyntaxVersion} <= v < ${nextPatchVersion( - elmSyntaxVersion - )}` - }; + return Promise.reject(error); + }); - return solve(options, elmVersion, elmJson, extra, false); + elmJson['elm-version'] = elmVersion; + delete elmJson.dependencies.direct['stil4m/elm-syntax']; + elmJson.dependencies = solve( + options, + elmVersion, + JSON.stringify(elmJson), + { + 'stil4m/elm-syntax': `${elmSyntaxVersion} <= v < ${nextPatchVersion( + elmSyntaxVersion + )}` + }, + false + ); + return elmJson; } /** From fb1c5537cc568e18e2748c0d589cfbdaf761aa04 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Tue, 24 Oct 2023 22:18:34 +0200 Subject: [PATCH 93/98] Extract function --- lib/template-dependencies.js | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index a19b3e432..5e9d893fd 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -207,13 +207,9 @@ function createNewReviewElmJson(options, elmVersion) { true ); - elmJson['test-dependencies'].direct = filterOutDuplicateDependencies( - testDependencies.direct, - elmJson.dependencies.direct - ); - elmJson['test-dependencies'].indirect = filterOutDuplicateDependencies( - testDependencies.indirect, - elmJson.dependencies.indirect + elmJson['test-dependencies'] = filterOutDuplicateDependencies( + testDependencies, + elmJson.dependencies ); teardownDependenciesProvider(); @@ -222,12 +218,34 @@ function createNewReviewElmJson(options, elmVersion) { } /** Filter out test-dependencies that are already in the regular dependencies. + * + * @param {{direct: Record, indirect: Record}} testDependencies + * @param {{direct: Record, indirect: Record}} regularDependencies + * @return {{direct: Record, indirect: Record}} + */ +function filterOutDuplicateDependencies(testDependencies, regularDependencies) { + return { + direct: filterOutDuplicateDependenciesForSection( + testDependencies.direct, + regularDependencies.direct + ), + indirect: filterOutDuplicateDependenciesForSection( + testDependencies.indirect, + regularDependencies.indirect + ) + }; +} + +/** Filter out test-dependencies that are already in the regular dependencies (only on a section of the dependencies). * * @param {Record} testDependencies * @param {Record} regularDependencies * @return {Record} */ -function filterOutDuplicateDependencies(testDependencies, regularDependencies) { +function filterOutDuplicateDependenciesForSection( + testDependencies, + regularDependencies +) { return Object.fromEntries( Object.entries(testDependencies).filter( ([pkg, _]) => !regularDependencies[pkg] From 58fb68cc11bcfc2d3920c7dac410b454f6a3ee90 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Tue, 24 Oct 2023 22:19:35 +0200 Subject: [PATCH 94/98] Remove unnecessary default value --- lib/template-dependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 5e9d893fd..29ebb9b2d 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -173,7 +173,7 @@ function createNewReviewElmJson(options, elmVersion) { const elmJson = { type: 'application', 'source-directories': ['src'], - 'elm-version': elmVersion || '0.19.1', + 'elm-version': elmVersion, dependencies: { direct: {}, indirect: {} From 3736f9b317e76807aa21d9f5ba562f953912eeb3 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Tue, 24 Oct 2023 22:20:42 +0200 Subject: [PATCH 95/98] Extract variable --- lib/template-dependencies.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 29ebb9b2d..412068466 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -183,11 +183,12 @@ function createNewReviewElmJson(options, elmVersion) { indirect: {} } }; + const stringifiedElmJson = JSON.stringify(elmJson); const testDependencies = solve( options, - elmJson['elm-version'], - JSON.stringify(elmJson), + elmVersion, + stringifiedElmJson, { 'elm/core': '1.0.0 <= v < 2.0.0', 'elm-explorations/test': '2.0.0 <= v < 3.0.0' @@ -197,8 +198,8 @@ function createNewReviewElmJson(options, elmVersion) { elmJson.dependencies = solve( options, - elmJson['elm-version'], - JSON.stringify(elmJson), + elmVersion, + stringifiedElmJson, { 'elm/core': '1.0.0 <= v < 2.0.0', 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', From ddc061415dee5e791161b4c0dce0a0501529d24b Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Tue, 24 Oct 2023 22:22:37 +0200 Subject: [PATCH 96/98] Move variable --- lib/template-dependencies.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 412068466..99c834482 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -185,27 +185,27 @@ function createNewReviewElmJson(options, elmVersion) { }; const stringifiedElmJson = JSON.stringify(elmJson); - const testDependencies = solve( + elmJson.dependencies = solve( options, elmVersion, stringifiedElmJson, { 'elm/core': '1.0.0 <= v < 2.0.0', - 'elm-explorations/test': '2.0.0 <= v < 3.0.0' + 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', + 'jfmengels/elm-review': MinVersion.supportedRange }, - false + true ); - elmJson.dependencies = solve( + const testDependencies = solve( options, elmVersion, stringifiedElmJson, { 'elm/core': '1.0.0 <= v < 2.0.0', - 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', - 'jfmengels/elm-review': MinVersion.supportedRange + 'elm-explorations/test': '2.0.0 <= v < 3.0.0' }, - true + false ); elmJson['test-dependencies'] = filterOutDuplicateDependencies( From 57575cc0c18a8df92bae2825c0b4b038e76a2489 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Tue, 24 Oct 2023 22:35:45 +0200 Subject: [PATCH 97/98] Also remove direct dependencies from indirect test-dependencies --- lib/template-dependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 99c834482..3772d7b67 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -232,7 +232,7 @@ function filterOutDuplicateDependencies(testDependencies, regularDependencies) { ), indirect: filterOutDuplicateDependenciesForSection( testDependencies.indirect, - regularDependencies.indirect + {...regularDependencies.direct, ...regularDependencies.indirect} ) }; } From 5137ae25d64b8bfaff2419a6ab592b3329fe49ce Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Tue, 24 Oct 2023 22:37:40 +0200 Subject: [PATCH 98/98] Upgrade test dependencies when running init or new-package --- lib/remote-template.js | 3 -- lib/template-dependencies.js | 33 +++++++++++++++++-- .../review/elm.json | 4 ++- .../elm-review-something/review/elm.json | 4 ++- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/remote-template.js b/lib/remote-template.js index 6045f3999..b34efe6e5 100644 --- a/lib/remote-template.js +++ b/lib/remote-template.js @@ -98,9 +98,6 @@ async function getRemoteElmJson( MinVersion.updateToAtLeastMinimalVersion(packageVersion); } - elmJson['test-dependencies'].direct = {}; - elmJson['test-dependencies'].indirect = {}; - return TemplateDependencies.update(options, elmJson); } diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 3772d7b67..ad90bb910 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -273,15 +273,44 @@ async function update(options, elmJson) { delete elmJson.dependencies.direct['jfmengels/elm-review']; delete elmJson.dependencies.direct['stil4m/elm-syntax']; - // TODO Upgrade test dependencies too for init or new-package + const stringifiedElmJson = JSON.stringify({ + ...elmJson, + dependencies: {direct: {}, indirect: {}}, + 'test-dependencies': {direct: {}, indirect: {}} + }); + elmJson.dependencies = solve( options, elmJson['elm-version'], - JSON.stringify({...elmJson, dependencies: {direct: {}, indirect: {}}}), + stringifiedElmJson, extra, true ); + const testDependenciesEntries = Object.entries( + elmJson['test-dependencies'].direct + ); + if (testDependenciesEntries.length !== 0) { + /** @type {Record} */ + const packagesToAdd = {}; + testDependenciesEntries.forEach(([pkg, version]) => { + packagesToAdd[pkg] = `${version} <= v < ${nextMajorVersion(version)}`; + }); + + const testDependencies = solve( + options, + elmJson['elm-version'], + stringifiedElmJson, + packagesToAdd, + true + ); + + elmJson['test-dependencies'] = filterOutDuplicateDependencies( + testDependencies, + elmJson.dependencies + ); + } + teardownDependenciesProvider(); return elmJson; diff --git a/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json b/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json index 45e2c3c5e..51cdf0de3 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json +++ b/test/run-snapshots/elm-review-something-for-new-rule/review/elm.json @@ -36,7 +36,9 @@ } }, "test-dependencies": { - "direct": {}, + "direct": { + "elm-explorations/test": "2.1.2" + }, "indirect": {} } } \ No newline at end of file diff --git a/test/run-snapshots/elm-review-something/review/elm.json b/test/run-snapshots/elm-review-something/review/elm.json index 45e2c3c5e..51cdf0de3 100644 --- a/test/run-snapshots/elm-review-something/review/elm.json +++ b/test/run-snapshots/elm-review-something/review/elm.json @@ -36,7 +36,9 @@ } }, "test-dependencies": { - "direct": {}, + "direct": { + "elm-explorations/test": "2.1.2" + }, "indirect": {} } } \ No newline at end of file