Skip to content

Commit dd79f0e

Browse files
committed
🔨 Refactors validate logic + adds validate subcommand to cli
1 parent ea10219 commit dd79f0e

File tree

9 files changed

+158
-47
lines changed

9 files changed

+158
-47
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"monorepo:fix": "manypkg fix && preconstruct fix",
2525
"init:codemods": "ts-node scripts/initialize.ts",
2626
"start:codemods": "node packages/cli/bin/codeshift-cli.js",
27-
"validate:codemods": "ts-node packages/validator/src/index.ts ./community",
27+
"validate:codemods": "ts-node scripts/validate.ts ./community",
2828
"release:codemods": "ts-node packages/publisher/src/index.ts ./community ./.tmp",
2929
"prerelease": "yarn validate && yarn test",
3030
"release": "yarn changeset publish"

packages/cli/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"start": "ts-node src/index.ts"
1414
},
1515
"dependencies": {
16+
"@codeshift/initializer": "0.1.0",
17+
"@codeshift/validator": "0.1.0",
1618
"chalk": "^4.1.0",
1719
"commander": "^7.2.0",
1820
"fs-extra": "^9.1.0",

packages/cli/src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import chalk from 'chalk';
22
import main from './main';
33
import list from './list';
44
import init from './init';
5+
import validate from './validate';
56
import {
67
ValidationError,
78
NoTransformsExistError,
@@ -81,6 +82,19 @@ Examples:
8182
`,
8283
);
8384

85+
program
86+
.command('validate [path]')
87+
.description('validates if a codemod package is publishable')
88+
.action(path => validate(path))
89+
.addHelpText(
90+
'after',
91+
`
92+
Examples:
93+
$ npx @codeshift/cli validate
94+
$ npx @codeshift/cli validate ./codemods/my-codemods
95+
`,
96+
);
97+
8498
program.exitOverride();
8599

86100
program.parseAsync(process.argv).catch(e => {

packages/cli/src/validate.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { isValidConfig, isValidPackageJson } from '@codeshift/validator';
2+
3+
export default async function validate(targetPath: string = '.') {
4+
try {
5+
await isValidConfig(targetPath);
6+
await isValidPackageJson(targetPath);
7+
} catch (error) {
8+
console.warn(error);
9+
process.exit(1);
10+
}
11+
12+
console.log('Valid ✅');
13+
}

packages/initializer/src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function initDirectory(
1616
const basePath = `${targetPath}/${packageName.replace('/', '__')}`;
1717
const codemodPath = `${basePath}/${version}`;
1818
const configPath = `${basePath}/codeshift.config.js`;
19+
const packagePath = `${basePath}/package.json`;
1920
const motionsPath = `${codemodPath}/motions`;
2021

2122
fs.mkdirSync(codemodPath, { recursive: true });
@@ -37,6 +38,22 @@ export function initDirectory(
3738

3839
fs.writeFileSync(`${codemodPath}/transform.spec.ts`, testFile);
3940

41+
fs.writeFileSync(
42+
packagePath,
43+
`{
44+
"name": "${packageName}",
45+
"version": "0.0.1",
46+
"license": "MIT",
47+
"main": "dist/${packageName}.cjs.js",
48+
"dependencies": {
49+
"@codeshift/utils": "^0.1.2"
50+
},
51+
"devDependencies": {
52+
"jscodeshift": "^0.12.0"
53+
}
54+
}`,
55+
);
56+
4057
if (!fs.existsSync(configPath)) {
4158
fs.writeFileSync(
4259
configPath,

packages/validator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"name": "@codeshift/validator",
3-
"private": true,
43
"version": "0.1.0",
54
"main": "dist/codeshift-validator.cjs.js",
65
"types": "dist/codeshift-validator.cjs.d.ts",
@@ -11,6 +10,7 @@
1110
},
1211
"dependencies": {
1312
"fs-extra": "^9.1.0",
13+
"recast": "^0.20.4",
1414
"semver": "^7.3.5",
1515
"ts-node": "^9.1.1"
1616
}

packages/validator/src/index.ts

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,63 @@
1-
import fs, { lstatSync, existsSync } from 'fs-extra';
1+
import fs from 'fs-extra';
22
import semver from 'semver';
3+
import * as recast from 'recast';
34

45
const packageNameRegex = /^(@[a-z0-9-~][a-z0-9-._~]*__)?[a-z0-9-~][a-z0-9-._~]*$/;
56

6-
async function main(path: string) {
7-
const directories = await fs.readdir(path);
8-
9-
directories.forEach(async dir => {
10-
if (!dir.match(packageNameRegex)) {
11-
throw new Error(
12-
`Invalid package name: ${dir}.
13-
If this is a scoped package, please make sure rename the folder to use the "__" characters to denote submodule.
14-
For example: @atlaskit/avatar => @atlaskit__avatar`,
15-
);
16-
}
17-
18-
const subDirectories = await fs.readdir(`${path}/${dir}`);
19-
let hasConfigFile = false;
20-
21-
subDirectories.forEach(subDir => {
22-
if (subDir === 'codeshift.config.js') {
23-
hasConfigFile = true;
24-
}
7+
export function isValidPackageName(dir: string) {
8+
return dir.match(packageNameRegex);
9+
}
2510

26-
if (
27-
lstatSync(`${path}/${dir}/${subDir}`).isDirectory() &&
28-
!semver.valid(subDir)
29-
) {
30-
throw new Error(
31-
`Codemod folder name "${subDir}" has an invalid version name. Please make sure the file name is valid semver. For example "18.0.0"`,
32-
);
11+
export async function isValidConfig(path: string) {
12+
const configPath = path + `/codeshift.config.js`;
13+
const source = await fs.readFile(configPath, 'utf8');
14+
const ast = recast.parse(source);
15+
16+
let hasTransforms = false;
17+
let invalidSemverIds = [];
18+
let transformCount = 0;
19+
20+
recast.visit(ast, {
21+
visitProperty(path) {
22+
// @ts-ignore
23+
if (path.node.key.name === 'transforms') {
24+
hasTransforms = true;
25+
// @ts-ignore
26+
const properties = path.node.value.properties;
27+
transformCount = properties.length;
28+
// @ts-ignore
29+
properties.forEach(property => {
30+
if (!semver.valid(property.key.value)) {
31+
invalidSemverIds.push(property.key.value);
32+
}
33+
});
3334
}
3435

35-
if (
36-
lstatSync(`${path}/${dir}/${subDir}`).isDirectory() &&
37-
!existsSync(`${path}/${dir}/${subDir}/transform.ts`) &&
38-
!existsSync(`${path}/${dir}/${subDir}/transform.js`)
39-
) {
40-
throw new Error(
41-
`Unable to find transform entry-point for directory "${path}/${dir}/${subDir}". Please ensure you have a valid transform.(ts|js) file containing the entry-point for your codemod`,
42-
);
43-
}
44-
});
45-
46-
if (!hasConfigFile) {
47-
throw new Error(
48-
`No config file found at: ${path}/${dir}.
49-
Please create a config file named "codeshift.config.js"`,
50-
);
51-
}
36+
return false;
37+
},
5238
});
39+
40+
if (!hasTransforms || !transformCount) {
41+
throw new Error(
42+
'At least one transform should be specified for config at "${configPath}"',
43+
);
44+
}
45+
46+
if (invalidSemverIds.length) {
47+
throw new Error(`Invalid transform ids found for config at "${configPath}".
48+
Please make sure all transforms are identified by a valid semver version. ie 10.0.0`);
49+
}
5350
}
5451

55-
main(process.argv[2]);
52+
export async function isValidPackageJson(path: string) {
53+
const packageJsonRaw = await fs.readFile(path + '/package.json', 'utf8');
54+
const packageJson = JSON.parse(packageJsonRaw);
55+
56+
if (!packageJson.name) {
57+
throw new Error('No package name provided in package.json');
58+
}
59+
60+
if (!packageJson.main) {
61+
throw new Error('No main entrypoint provided in package.json');
62+
}
63+
}

scripts/validate.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import fs, { lstatSync, existsSync } from 'fs-extra';
2+
import semver from 'semver';
3+
import { isValidPackageName, isValidConfig } from '@codeshift/validator';
4+
5+
async function main(path: string) {
6+
const directories = await fs.readdir(path);
7+
8+
directories.forEach(async dir => {
9+
if (!isValidPackageName(dir)) {
10+
throw new Error(
11+
`Invalid package name: ${dir}.
12+
If this is a scoped package, please make sure rename the folder to use the "__" characters to denote submodule.
13+
For example: @foo/bar => @foo__bar`,
14+
);
15+
}
16+
17+
await isValidConfig(`${path}/${dir}`);
18+
19+
const subDirectories = await fs.readdir(`${path}/${dir}`);
20+
subDirectories.forEach(async subDir => {
21+
if (
22+
lstatSync(`${path}/${dir}/${subDir}`).isDirectory() &&
23+
!semver.valid(subDir)
24+
) {
25+
throw new Error(
26+
`Codemod folder name "${subDir}" has an invalid version name. Please make sure the file name is valid semver. For example "18.0.0"`,
27+
);
28+
}
29+
30+
if (
31+
lstatSync(`${path}/${dir}/${subDir}`).isDirectory() &&
32+
!existsSync(`${path}/${dir}/${subDir}/transform.ts`) &&
33+
!existsSync(`${path}/${dir}/${subDir}/transform.js`)
34+
) {
35+
throw new Error(
36+
`Unable to find transform entry-point for directory "${path}/${dir}/${subDir}". Please ensure you have a valid transform.(ts|js) file containing the entry-point for your codemod`,
37+
);
38+
}
39+
});
40+
});
41+
}
42+
43+
main(process.argv[2]);

website/docs/api/codeshift-cli.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,17 @@ Create a new codemod package called foobar with a transform for version 10
128128
on the Desktop
129129

130130
- `@codeshift/cli init --packageName="foobar" --version"10.0.0" ~/Desktop`
131+
132+
### validate
133+
134+
Validates a codemod package at the desired path.
135+
136+
**example:**
137+
138+
Validate a codemod package called "my-codemods".
139+
140+
- `$ npx @codeshift/cli validate ./codemods/my-codemods`
141+
142+
Validate a codemod package from the current working directory
143+
144+
- `$ npx @codeshift/cli validate`

0 commit comments

Comments
 (0)