Skip to content

Commit f6d9612

Browse files
committed
feat(cli): add clie
1 parent b154ae2 commit f6d9612

File tree

8 files changed

+1344
-315
lines changed

8 files changed

+1344
-315
lines changed

almin-migration.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env node
2+
"use strict";
3+
const execa = require("execa");
4+
const meow = require("meow");
5+
const updateNotifier = require("update-notifier");
6+
const arrify = require("arrify");
7+
const globby = require("globby");
8+
const pkgConf = require("pkg-conf");
9+
const inquirer = require("inquirer");
10+
const npmRunPath = require("npm-run-path");
11+
const utils = require("./cli-utils");
12+
const codemods = require("./codemods");
13+
14+
function runScripts(scripts, files) {
15+
const spawnOptions = {
16+
env: Object.assign({}, process.env, { PATH: npmRunPath({ cwd: __dirname }) }),
17+
stdio: "inherit"
18+
};
19+
20+
scripts.forEach(script => {
21+
const result = execa.sync(require.resolve(".bin/jscodeshift"), ["-t", script].concat(files), spawnOptions);
22+
23+
if (result.error) {
24+
throw result.error;
25+
}
26+
});
27+
}
28+
29+
const cli = meow(
30+
`
31+
Usage
32+
$ ava-codemods [<file|glob> ...]
33+
34+
Options
35+
--force, -f Bypass safety checks and forcibly run codemods
36+
37+
Available upgrades
38+
- 0.16.x → 0.17.x
39+
- 0.13.x → 0.14.x
40+
`,
41+
{
42+
boolean: ["force"],
43+
string: ["_"],
44+
alias: {
45+
f: "force",
46+
h: "help"
47+
}
48+
}
49+
);
50+
51+
updateNotifier({ pkg: cli.pkg }).notify();
52+
53+
if (!utils.checkGitStatus(cli.flags.force)) {
54+
process.exit(1);
55+
}
56+
57+
codemods.sort(utils.sortByVersion);
58+
59+
const versions = utils.getVersions(codemods);
60+
61+
const avaConf = pkgConf.sync("ava");
62+
const defaultFiles = "test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js";
63+
64+
const questions = [
65+
{
66+
type: "list",
67+
name: "currentVersion",
68+
message: "What version of AVA are you currently using?",
69+
choices: versions.slice(0, -1)
70+
},
71+
{
72+
type: "list",
73+
name: "nextVersion",
74+
message: "What version of AVA are you moving to?",
75+
choices: versions.slice(1)
76+
},
77+
{
78+
type: "input",
79+
name: "files",
80+
message: "On which files should the codemods be applied?",
81+
default: (avaConf.files && arrify(avaConf.files).join(" ")) || defaultFiles,
82+
when: !cli.input.length,
83+
filter: files => files.trim().split(/\s+/).filter(v => v)
84+
}
85+
];
86+
87+
inquirer.prompt(questions, answers => {
88+
const files = answers.files || cli.input;
89+
90+
if (!files.length) {
91+
return;
92+
}
93+
94+
const scripts = utils.selectScripts(codemods, answers.currentVersion, answers.nextVersion);
95+
96+
runScripts(scripts, globby.sync(files));
97+
});

cli-utils.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use strict";
2+
const path = require("path");
3+
const semver = require("semver");
4+
const uniq = require("lodash.uniq");
5+
const flatten = require("lodash.flatten");
6+
const isGitClean = require("is-git-clean");
7+
8+
exports.sortByVersion = function sortByVersion(a, b) {
9+
if (a.version === b.version) {
10+
return 0;
11+
}
12+
13+
return semver.lt(a.version, b.version) ? -1 : 1;
14+
};
15+
16+
exports.getVersions = function getVersions(codemods) {
17+
const versionsFromCodemods = codemods.sort(exports.sortByVersion).map(codemod => codemod.version);
18+
const uniqueVersions = uniq(versionsFromCodemods);
19+
const firstVersion = {
20+
name: `older than ${uniqueVersions.sort(exports.sortByVersion)[0]}`,
21+
value: "0.0.0"
22+
};
23+
const lastVersion = {
24+
name: "latest",
25+
value: "9999.9999.9999"
26+
};
27+
28+
return [firstVersion].concat(versionsFromCodemods).concat(lastVersion);
29+
};
30+
31+
exports.selectScripts = function selectScripts(codemods, currentVersion, nextVersion) {
32+
const semverToRespect = `>${currentVersion} <=${nextVersion}`;
33+
34+
const scripts = codemods
35+
.filter(codemod => semver.satisfies(codemod.version, semverToRespect))
36+
.map(codemod => codemod.scripts);
37+
38+
return flatten(scripts).map(script => path.join(__dirname, script));
39+
};
40+
41+
exports.checkGitStatus = function checkGitStatus(force) {
42+
let clean = false;
43+
let errorMessage = "Unable to determine if git directory is clean";
44+
try {
45+
clean = isGitClean.sync(process.cwd(), { files: ["!package.json"] });
46+
errorMessage = "Git directory is not clean";
47+
} catch (err) {}
48+
49+
const ENSURE_BACKUP_MESSAGE =
50+
"Ensure you have a backup of your tests or commit the latest changes before continuing.";
51+
52+
if (!clean) {
53+
if (force) {
54+
console.log(`WARNING: ${errorMessage}. Forcibly continuing.`, ENSURE_BACKUP_MESSAGE);
55+
} else {
56+
console.log(
57+
`ERROR: ${errorMessage}. Refusing to continue.`,
58+
ENSURE_BACKUP_MESSAGE,
59+
"You may use the --force flag to override this safety check."
60+
);
61+
return false;
62+
}
63+
}
64+
65+
return true;
66+
};

codemods.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[
2+
{
3+
"version": "0.14.0",
4+
"scripts": [
5+
"lib/ok-to-truthy.js",
6+
"lib/same-to-deep-equal.js"
7+
]
8+
},
9+
{
10+
"version": "0.17.0",
11+
"scripts": [
12+
"lib/error-to-iferror.js"
13+
]
14+
}
15+
]

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
module.exports = {
44
storeGetStateReturnObjectToFlat: require("./scripts/store-get-state-return-object-to-flat"),
55
storeGroupArguments: require("./scripts/store-group-arguments")
6-
};
6+
};

package.json

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@
1212
"version": "1.0.3",
1313
"main": "index.js",
1414
"scripts": {
15+
"precommit": "lint-staged",
16+
"postcommit": "git reset",
1517
"test": "jest"
1618
},
19+
"lint-staged": {
20+
"scripts/*.js": [
21+
"prettier --tab-width 4 --print-width 120 --write",
22+
"git add"
23+
]
24+
},
1725
"jest": {
1826
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$"
1927
},
@@ -31,8 +39,25 @@
3139
"url": "https://github.com/almin/migration-tools/issues"
3240
},
3341
"homepage": "https://github.com/almin/migration-tools",
42+
"dependencies": {
43+
"arrify": "^1.0.1",
44+
"execa": "^0.7.0",
45+
"globby": "^6.0.0",
46+
"inquirer": "^3.2.0",
47+
"is-git-clean": "^1.1.0",
48+
"jscodeshift": "^0.3.32",
49+
"lodash.flatten": "^4.1.1",
50+
"lodash.uniq": "^4.2.1",
51+
"meow": "^3.7.0",
52+
"npm-run-path": "^2.0.2",
53+
"pkg-conf": "^2.0.0",
54+
"semver": "^5.1.0",
55+
"update-notifier": "^2.2.0"
56+
},
3457
"devDependencies": {
35-
"jest": "^19.0.2",
36-
"jscodeshift": "^0.3.30"
58+
"husky": "^0.14.3",
59+
"jest": "^20.0.4",
60+
"lint-staged": "^4.0.1",
61+
"prettier": "^1.5.2"
3762
}
3863
}

scripts/store-get-state-return-object-to-flat.js

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const path = require("path");
3030
const updateState = (outputJSONPath, stateName, storeFilePath) => {
3131
let output = {};
3232
try {
33-
output = require(outputJSONPath)
33+
output = require(outputJSONPath);
3434
} catch (error) {
3535
output = {};
3636
}
@@ -44,15 +44,13 @@ module.exports = function(file, api, options) {
4444
const hasOneProperty = ({ node }) => {
4545
return node.properties && node.properties.length === 1;
4646
};
47-
const isReturnedValue = (path) => {
47+
const isReturnedValue = path => {
4848
const parentNode = path.parent;
4949
return parentNode.value.type === "ReturnStatement";
5050
};
5151
// look up to parent
52-
const isInGetStateMethod = (targetPath) => {
53-
const results = j(targetPath)
54-
.closest(j.MethodDefinition)
55-
.filter(path => {
52+
const isInGetStateMethod = targetPath => {
53+
const results = j(targetPath).closest(j.MethodDefinition).filter(path => {
5654
return j(path).find(j.Identifier, {
5755
name: "getState"
5856
});
@@ -61,25 +59,25 @@ module.exports = function(file, api, options) {
6159
};
6260
const j = api.jscodeshift;
6361
const replaced = j(file.source)
64-
.find(j.ObjectExpression)
65-
.filter(path => {
66-
return hasOneProperty(path);
67-
})
68-
.filter(path => {
69-
return isReturnedValue(path)
70-
})
71-
.filter(path => {
72-
return isInGetStateMethod(path);
73-
})
74-
.replaceWith(path => {
75-
stateName = path.value.properties[0].key.name;
76-
// { stateName: state }
77-
// => state
78-
return path.value.properties[0].value;
79-
})
80-
.toSource();
62+
.find(j.ObjectExpression)
63+
.filter(path => {
64+
return hasOneProperty(path);
65+
})
66+
.filter(path => {
67+
return isReturnedValue(path);
68+
})
69+
.filter(path => {
70+
return isInGetStateMethod(path);
71+
})
72+
.replaceWith(path => {
73+
stateName = path.value.properties[0].key.name;
74+
// { stateName: state }
75+
// => state
76+
return path.value.properties[0].value;
77+
})
78+
.toSource();
8179
if (!options.dry && stateName && filePath) {
8280
updateState(outputJSONPath, stateName, filePath);
8381
}
8482
return replaced;
85-
};
83+
};

scripts/store-group-arguments.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = function(file, api, options) {
3333
: path.join(process.cwd(), "almin-store-state-mapping.json");
3434
let mapping = {};
3535
try {
36-
mapping = require(outputJSONPath)
36+
mapping = require(outputJSONPath);
3737
} catch (error) {
3838
mapping = {};
3939
}
@@ -46,7 +46,7 @@ module.exports = function(file, api, options) {
4646
return file.source;
4747
}
4848
// transform
49-
const isStoreGroupArguments = (path) => {
49+
const isStoreGroupArguments = path => {
5050
const parent = path.parent;
5151
if (!parent) {
5252
return false;
@@ -62,7 +62,7 @@ module.exports = function(file, api, options) {
6262
};
6363

6464
// get store name
65-
const getStoreNameFromElement = (element) => {
65+
const getStoreNameFromElement = element => {
6666
if (!element) {
6767
throw new Error(element + " is invalid");
6868
}
@@ -74,7 +74,7 @@ module.exports = function(file, api, options) {
7474
throw new Error(element + " is invalid");
7575
};
7676

77-
const getStateNameFromStoreName = (storeName) => {
77+
const getStateNameFromStoreName = storeName => {
7878
let result = null;
7979
const found = Object.keys(mapping).some(stateName => {
8080
const storeFilePath = mapping[stateName];
@@ -97,19 +97,19 @@ module.exports = function(file, api, options) {
9797
return result;
9898
};
9999
return j(file.source)
100-
.find(j.ArrayExpression)
101-
.filter(path => {
102-
return isStoreGroupArguments(path);
103-
})
104-
.replaceWith(path => {
105-
const elements = path.value.elements;
106-
const objects = elements.map(element => {
107-
const storeName = getStoreNameFromElement(element);
108-
const stateName = getStateNameFromStoreName(storeName);
109-
// { stateName: new Store() }
110-
return j.property("init", j.literal(stateName), element);
111-
});
112-
return j.objectExpression(objects)
113-
})
114-
.toSource();
115-
};
100+
.find(j.ArrayExpression)
101+
.filter(path => {
102+
return isStoreGroupArguments(path);
103+
})
104+
.replaceWith(path => {
105+
const elements = path.value.elements;
106+
const objects = elements.map(element => {
107+
const storeName = getStoreNameFromElement(element);
108+
const stateName = getStateNameFromStoreName(storeName);
109+
// { stateName: new Store() }
110+
return j.property("init", j.literal(stateName), element);
111+
});
112+
return j.objectExpression(objects);
113+
})
114+
.toSource();
115+
};

0 commit comments

Comments
 (0)