Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a command to use master version of elm-explorations/test #592

Merged
merged 23 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 6 additions & 43 deletions lib/DependencyProvider.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// @flow

const fs = require('fs');
const os = require('os');
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

Expand All @@ -17,7 +17,7 @@ class OnlineVersionsCache {
map /*: Map<string, Array<string>> */ = new Map();

update() {
const pubgrubHome = path.join(elmHome(), 'pubgrub');
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';
Expand Down Expand Up @@ -139,7 +139,7 @@ class OfflineAvailableVersionLister {
}

function readVersionsInElmHomeAndSort(pkg /*: string */) /*: Array<string> */ {
const pkgPath = homePkgPath(pkg);
const pkgPath = ElmHome.packagePath(pkg);
let offlineVersions;
try {
offlineVersions = fs.readdirSync(pkgPath);
Expand Down Expand Up @@ -316,9 +316,9 @@ function cacheElmJsonPath(
pkg /*: string */,
version /*: string */
) /*: string */ {
const parts = splitAuthorPkg(pkg);
const parts = ElmHome.splitAuthorPkg(pkg);
return path.join(
elmHome(),
ElmHome.elmHome(),
'pubgrub',
'elm_json_cache',
parts.author,
Expand All @@ -332,20 +332,7 @@ function homeElmJsonPath(
pkg /*: string */,
version /*: string */
) /*: string */ {
return path.join(homePkgPath(pkg), version, 'elm.json');
}

function homePkgPath(pkg /*: string */) /*: string */ {
const parts = splitAuthorPkg(pkg);
return path.join(elmHome(), '0.19.1', 'packages', parts.author, parts.pkg);
}

function splitAuthorPkg(pkgIdentifier /*: string */) /*: {
author: string,
pkg: string,
} */ {
const parts = pkgIdentifier.split('/');
return { author: parts[0], pkg: parts[1] };
return path.join(ElmHome.packagePath(pkg), version, 'elm.json');
}

function splitPkgVersion(str /*: string */) /*: {
Expand All @@ -356,28 +343,4 @@ function splitPkgVersion(str /*: string */) /*: {
return { pkg: parts[0], version: parts[1] };
}

function elmHome() /*: string */ {
const elmHomeEnv = process.env['ELM_HOME'];
return elmHomeEnv === undefined ? defaultElmHome() : elmHomeEnv;
}

function defaultElmHome() /*: string */ {
return process.platform === 'win32'
? defaultWindowsElmHome()
: defaultUnixElmHome();
}

function defaultUnixElmHome() /*: string */ {
return path.join(os.homedir(), '.elm');
}

function defaultWindowsElmHome() /*: string */ {
const appData = process.env.APPDATA;
const dir =
appData === undefined
? path.join(os.homedir(), 'AppData', 'Roaming')
: appData;
return path.join(dir, 'elm');
}

module.exports = DependencyProvider;
47 changes: 47 additions & 0 deletions lib/ElmHome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @flow

const path = require('path');
const os = require('os');

function elmHome() /*: string */ {
const elmHomeEnv = process.env['ELM_HOME'];
return elmHomeEnv === undefined ? defaultElmHome() : elmHomeEnv;
}

function defaultElmHome() /*: string */ {
return process.platform === 'win32'
? defaultWindowsElmHome()
: defaultUnixElmHome();
}

function defaultUnixElmHome() /*: string */ {
return path.join(os.homedir(), '.elm');
}

function defaultWindowsElmHome() /*: string */ {
const appData = process.env.APPDATA;
const dir =
appData === undefined
? path.join(os.homedir(), 'AppData', 'Roaming')
: appData;
return path.join(dir, 'elm');
}

function packagePath(pkg /*: string */) /*: string */ {
const parts = splitAuthorPkg(pkg);
return path.join(elmHome(), '0.19.1', 'packages', parts.author, parts.pkg);
}

function splitAuthorPkg(pkgIdentifier /*: string */) /*: {
author: string,
pkg: string,
} */ {
const parts = pkgIdentifier.split('/');
return { author: parts[0], pkg: parts[1] };
}

module.exports = {
elmHome,
packagePath,
splitAuthorPkg,
};
1 change: 1 addition & 0 deletions lib/ElmJson.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ function stringify(json /*: mixed */) /*: string */ {
}

module.exports = {
Dependencies,
DirectAndIndirectDependencies,
ElmJson,
getPath,
Expand Down
179 changes: 167 additions & 12 deletions lib/Install.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@

const spawn = require('cross-spawn');
const fs = require('fs');
const https = require('https');
const path = require('path');
const chalk = require('chalk');
const ElmHome = require('./ElmHome');
const ElmJson = require('./ElmJson');
const Project = require('./Project');

void Project;

function rmDirSync(dir /*: string */) /*: void */ {
// We can replace this with just `fs.rmSync(dir, { recursive: true, force: true })`
// when Node.js 12 is EOL 2022-04-30 and support for Node.js 12 is dropped.
// `fs.rmSync` was added in Node.js 14.14.0, which is also when the
// `recursive` option of `fs.rmdirSync` was deprecated. The `if` avoids
// printing a deprecation message.
// $FlowFixMe[prop-missing]: Flow does not know of `fs.rmSync` yet.
if (fs.rmSync !== undefined) {
fs.rmSync(dir, { recursive: true, force: true });
} else if (fs.existsSync(dir)) {
// $FlowFixMe[extra-arg]: Flow does not know of the options argument yet.
fs.rmdirSync(dir, { recursive: true });
}
}

function install(
project /*: typeof Project.Project */,
pathToElmBinary /*: string */,
Expand All @@ -19,18 +37,7 @@ function install(
// Recreate the directory to remove any artifacts from the last time
// someone ran `elm-test install`. We do not delete this directory after
// the installation finishes in case the user needs to debug the test run.
// We can replace this with just `fs.rmSync(installationScratchDir, { recursive: true, force: true })`
// when Node.js 12 is EOL 2022-04-30 and support for Node.js 12 is dropped.
// `fs.rmSync` was added in Node.js 14.14.0, which is also when the
// `recursive` option of `fs.rmdirSync` was deprecated. The `if` avoids
// printing a deprecation message.
// $FlowFixMe[prop-missing]: Flow does not know of `fs.rmSync` yet.
if (fs.rmSync !== undefined) {
fs.rmSync(installationScratchDir, { recursive: true, force: true });
} else if (fs.existsSync(installationScratchDir)) {
// $FlowFixMe[extra-arg]: Flow does not know of the options argument yet.
fs.rmdirSync(installationScratchDir, { recursive: true });
}
rmDirSync(installationScratchDir);
fs.mkdirSync(installationScratchDir, { recursive: true });
} catch (error) {
throw new Error(
Expand Down Expand Up @@ -117,6 +124,154 @@ function install(
return 'SuccessfullyInstalled';
}

async function downloadFileNative(
url /*: string */,
filePath /*: string */
) /*: Promise<void> */ {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(filePath);

const request = https.get(url, (response) => {
if (response.statusCode !== 200) {
fs.unlink(filePath, () => {
reject(new Error(`Failed to get '${url}' (${response.statusCode})`));
});
} else {
response.pipe(file);
}
});

// The destination stream is ended by the time it's called
file.on('finish', () => resolve());

request.on('error', (err) => {
fs.unlink(filePath, () => reject(err));
});

file.on('error', (err) => {
fs.unlink(filePath, () => reject(err));
});

request.end();
});
}

function getDirectTestDependencies(
project /*: typeof Project.Project */
) /*: typeof ElmJson.Dependencies */ {
switch (project.elmJson.type) {
case 'application':
return project.elmJson['test-dependencies'].direct;
case 'package':
return project.elmJson['test-dependencies'];
}
}

const PKG = 'elm-explorations/test';
const VERSION = '1.2.2';

async function installUnstableTestMaster(
project /*: typeof Project.Project */
) /*: Promise<void> */ {
const directTestDependencies = getDirectTestDependencies(project);
const actualVersion = directTestDependencies[PKG];
if (actualVersion !== VERSION) {
throw new Error(
`
Could not find ${JSON.stringify(PKG)}: ${JSON.stringify(
VERSION
)} in your elm.json file here:

${ElmJson.getPath(project.rootDir)}

This command only works if you have ${PKG} as a (direct) test-dependency,
and only if you use version ${VERSION}.

${
actualVersion === undefined
? 'I could not find it at all.'
: `You seem to be using version ${actualVersion}.`
}
`.trim()
);
}

console.log(
chalk.yellow(`Using the master version of ${PKG} in place of ${VERSION}.`)
);
console.log(
chalk.yellow(
`Note: You will need to use the \`elm-test uninstall-unstable-test-master\` command afterwards to get back to the ${VERSION} version.`
)
);

const pkgWithDash = PKG.replace('/', '-');

const tempPath = project.generatedCodeDir;
const zipballUrl = `https://codeload.github.com/${PKG}/zip/refs/heads/master`;
const zipballPath = path.join(tempPath, `${pkgWithDash}.zip`);

const packagePath = path.join(ElmHome.packagePath(PKG), VERSION);

console.log(chalk.dim.yellow(`Removing ${tempPath}`));
rmDirSync(tempPath);

console.log(chalk.dim.yellow(`Removing ${packagePath}`));
rmDirSync(packagePath);

fs.mkdirSync(tempPath, { recursive: true });
fs.mkdirSync(packagePath, { recursive: true });

console.log(chalk.dim.yellow(`Downloading ${zipballUrl}`));
await downloadFileNative(zipballUrl, zipballPath);

console.log(chalk.dim.yellow(`Unzipping ${zipballPath}`));
const unzipResult = spawn.sync('unzip', [
'-o', // overwrite
zipballPath, // file to unzip
'-d',
tempPath, // directory where to extract files
]);

if (unzipResult.status === 0) {
console.log(chalk.dim.yellow(`Moving to ELM_HOME: ${packagePath}`));
fs.renameSync(path.join(tempPath, 'test-master'), packagePath);
} else {
// Windows does not have `unzip`, but BSD `tar`. On Windows, we have to extract
// straight into `packagePath` (instead of `tempPath`), because `fs.renameSync`
// gives an EPERM error otherwise, which seems to be due to how antivirus works
// on Windows.
const tarResult = spawn.sync('tar', [
'zxf', // eXtract Zipped File
zipballPath, // file to unzip
'-C',
packagePath, // directory where to extract files
'--strip-components=1', // strip the inner 'test-master' folder
]);
if (tarResult.status !== 0) {
throw new Error('Failed to unzip the elm-explorations/test repo zipfile');
}
}

console.log(chalk.dim.yellow(`Removing ${zipballPath}`));
fs.unlinkSync(zipballPath);
}

function uninstallUnstableTestMaster(
project /*: typeof Project.Project */
) /*: void */ {
const { generatedCodeDir } = project;
const packagePath = path.join(ElmHome.packagePath(PKG), VERSION);

console.log(chalk.dim.yellow(`Removing ${generatedCodeDir}`));
rmDirSync(generatedCodeDir);

console.log(chalk.dim.yellow(`Removing ${packagePath}`));
rmDirSync(packagePath);
}

module.exports = {
install,
installUnstableTestMaster,
uninstallUnstableTestMaster,
};
Loading