-
Notifications
You must be signed in to change notification settings - Fork 52
@channel.io/bezier-codemod npm package #1382
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
Changes from 19 commits
b42ec42
1709a7e
16447a3
36d8bae
e5621ca
4ae73dc
e7bafd7
401780b
fcb488f
8c7f18f
051de78
e543b33
5ac6afb
a395413
a2209df
d7ed76e
68b43c4
5082df1
5f52ca6
cdc1367
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@channel.io/bezier-codemod": minor | ||
--- | ||
|
||
Add icons-to-bezier-icons transformer |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
dist | ||
|
||
**/tests/**/fixtures/**/* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
module.exports = { | ||
root: true, | ||
extends: ['bezier'], | ||
parserOptions: { | ||
tsconfigRootDir: __dirname, | ||
project: './tsconfig.eslint.json', | ||
}, | ||
rules: { | ||
'no-restricted-imports': 'off', | ||
'sort-imports': [ | ||
'error', | ||
{ | ||
ignoreDeclarationSort: true, | ||
}, | ||
], | ||
'import/order': [ | ||
'error', | ||
{ | ||
'newlines-between': 'always', | ||
alphabetize: { order: 'asc' }, | ||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], | ||
pathGroupsExcludedImportTypes: ['react'], | ||
pathGroups: [ | ||
{ | ||
pattern: 'react', | ||
group: 'external', | ||
position: 'before', | ||
}, | ||
], | ||
}, | ||
], | ||
'react/react-in-jsx-scope': 'off', | ||
'react/jsx-props-no-spreading': 'off', | ||
'@typescript-eslint/naming-convention': 'off', | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"**/*.(js|ts)?(x)": "yarn lint" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Bezier Codemod | ||
|
||
Codemod transformations to help upgrade app using Bezier design system. | ||
|
||
## Usage | ||
|
||
In your terminal, navigate into your project's folder, then run: | ||
|
||
```bash | ||
npx @channel.io/bezier-codemod | ||
``` | ||
|
||
## Transformations | ||
|
||
### Icons to Bezier icons | ||
|
||
**`icons-to-bezier-icons`** | ||
|
||
Update the import syntax for the icon source moved from `@channel.io/bezier-react` to `@channel.io/bezier-icons`. | ||
|
||
For example: | ||
|
||
```tsx | ||
import React from 'react' | ||
import { AllIcon, Button, CheckIcon as CheckIconSource, Icon, type IconName, IconSize } from '@channel.io/bezier-react' | ||
|
||
import Foo from './foo' | ||
``` | ||
|
||
Transforms into: | ||
|
||
```tsx | ||
import React from 'react' | ||
import { AllIcon, CheckIcon as CheckIconSource, type IconName } from '@channel.io/bezier-icons' | ||
import { Button, Icon, IconSize } from '@channel.io/bezier-react' | ||
|
||
import Foo from './foo' | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** @type {import('ts-jest').JestConfigWithTsJest} */ | ||
module.exports = { | ||
preset: 'ts-jest/presets/default-esm', | ||
moduleNameMapper: { | ||
'^(\\.{1,2}/.*)\\.js$': '$1', | ||
}, | ||
transform: { | ||
// '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` | ||
// '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` | ||
'^.+\\.tsx?$': [ | ||
'ts-jest', | ||
{ | ||
useESM: true, | ||
}, | ||
], | ||
}, | ||
testEnvironment: 'node', | ||
testRegex: '\\.test\\.ts$', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
{ | ||
"name": "@channel.io/bezier-codemod", | ||
"version": "0.0.0", | ||
"description": "Codemod transformations to help upgrade app using Bezier design system.", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/channel-io/bezier-react", | ||
"directory": "packages/bezier-codemod" | ||
}, | ||
"author": "Channel Corp.", | ||
"license": "Apache-2.0", | ||
"bin": "dist/cli.js", | ||
"type": "module", | ||
"engines": { | ||
"node": ">=16" | ||
}, | ||
"scripts": { | ||
"build": "tsc --build --verbose", | ||
"dev": "tsc --watch", | ||
"lint": "TIMING=1 eslint --cache .", | ||
"typecheck": "tsc --noEmit", | ||
"test": "jest --onlyChanged", | ||
"clean": "rm -rf dist node_modules .turbo .eslintcache" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"dependencies": { | ||
"@inkjs/ui": "^1.0.0", | ||
"ink": "^4.1.0", | ||
"meow": "^11.0.0", | ||
"react": "^18.2.0", | ||
"ts-morph": "^18.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^29.5.1", | ||
"@types/node": "^20.2.5", | ||
"@types/react": "^18.0.32", | ||
"eslint-config-bezier": "workspace:*", | ||
"jest": "^29.5.0", | ||
"ts-jest": "^29.1.0", | ||
"ts-node": "^10.9.1", | ||
"tsconfig": "workspace:*" | ||
}, | ||
"keywords": [ | ||
"codemod", | ||
"channel" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import React, { | ||
useCallback, | ||
useEffect, | ||
useState, | ||
} from 'react' | ||
|
||
import { | ||
Select, | ||
type SelectProps, | ||
Spinner, | ||
StatusMessage, | ||
TextInput, | ||
} from '@inkjs/ui' | ||
import { | ||
Box, | ||
Text, | ||
useApp, | ||
} from 'ink' | ||
import { | ||
IndentationText, | ||
NewLineKind, | ||
Project, | ||
QuoteKind, | ||
} from 'ts-morph' | ||
|
||
import iconsToBezierIcons from './transforms/icons-to-bezier-icons.js' | ||
|
||
const project = new Project({ | ||
manipulationSettings: { | ||
indentationText: IndentationText.TwoSpaces, | ||
newLineKind: NewLineKind.LineFeed, | ||
quoteKind: QuoteKind.Single, | ||
usePrefixAndSuffixTextForRename: true, | ||
useTrailingCommas: false, | ||
}, | ||
}) | ||
|
||
enum Step { | ||
SelectTransformer, | ||
InputFiles, | ||
Transforming, | ||
Done, | ||
} | ||
|
||
enum Option { | ||
IconsToBezierIcons = 'icons-to-bezier-icons', | ||
Exit = 'Exit', | ||
} | ||
|
||
type TransformName = Exclude<Option, Option.Exit> | ||
|
||
const transformMap = { | ||
[Option.IconsToBezierIcons]: iconsToBezierIcons, | ||
} | ||
|
||
const options = (Object.keys(transformMap) as Option[]).map((transformName) => ({ | ||
label: transformName, | ||
value: transformName, | ||
})).concat({ | ||
label: Option.Exit, | ||
value: Option.Exit, | ||
}) | ||
|
||
function formatExecutionTime(executionTime: number) { | ||
const seconds = Math.round(executionTime / 1000 * 1000) / 1000 | ||
return `${seconds.toFixed(3)}s` | ||
} | ||
|
||
function App() { | ||
const { exit } = useApp() | ||
|
||
const [step, setStep] = useState(Step.SelectTransformer) | ||
const [transformName, setTransformName] = useState<TransformName | null>(null) | ||
const [filePath, setFilePath] = useState('') | ||
const [executionTime, setExecutionTime] = useState(0) | ||
const [transformedFileNum, setTransformedFileNum] = useState(0) | ||
|
||
const onSelectTransform = useCallback((value: Option) => { | ||
if (value === Option.Exit) { | ||
exit() | ||
return | ||
} | ||
setTransformName(value) | ||
setStep(Step.InputFiles) | ||
}, [exit]) | ||
|
||
const onSubmitFilePath = useCallback((value: string) => { | ||
if (!transformName) { return } | ||
setFilePath(value) | ||
setStep(Step.Transforming) | ||
}, [transformName]) | ||
|
||
useEffect(function main() { | ||
if (step !== Step.Transforming) { return } | ||
|
||
/** | ||
* FIXME: This timeout is a hack to make sure the UI is updated before the transform starts. | ||
* Otherwise, the UI will be stuck on the previous step. | ||
*/ | ||
setTimeout(() => { | ||
const startTime = performance.now() | ||
|
||
async function transformSourceFiles() { | ||
const sourceFiles = project.addSourceFilesAtPaths(filePath) | ||
|
||
await Promise.all( | ||
sourceFiles.map(async (sourceFile) => { | ||
if (!transformName) { return } | ||
const transform = transformMap[transformName] | ||
const isTransformed = transform(sourceFile) | ||
if (isTransformed) { | ||
setTransformedFileNum(prev => prev + 1) | ||
} | ||
await sourceFile.save() | ||
}), | ||
) | ||
|
||
const endTime = performance.now() | ||
const totalExecutionTime = endTime - startTime | ||
setExecutionTime(totalExecutionTime) | ||
setStep(Step.Done) | ||
} | ||
|
||
transformSourceFiles() | ||
}, 100) | ||
}, [ | ||
step, | ||
transformName, | ||
filePath, | ||
]) | ||
|
||
return ( | ||
<Box flexDirection="column"> | ||
{ step === Step.SelectTransformer && ( | ||
<> | ||
<Text bold> | ||
💬 Please select the transformer: | ||
</Text> | ||
<Select | ||
options={options} | ||
onChange={onSelectTransform as SelectProps['onChange']} | ||
/> | ||
</> | ||
) } | ||
|
||
{ step === Step.InputFiles && ( | ||
<> | ||
<Text bold> | ||
💬 Please input the file path. You can use a glob pattern: | ||
</Text> | ||
<TextInput | ||
placeholder="**/*{.ts,.tsx}" | ||
onSubmit={onSubmitFilePath} | ||
/> | ||
</> | ||
) } | ||
|
||
{ step === Step.Transforming && ( | ||
<Box marginTop={1}> | ||
<Spinner label="Transforming" /> | ||
</Box> | ||
) } | ||
|
||
{ step === Step.Done && ( | ||
<Box | ||
marginTop={1} | ||
paddingLeft={1} | ||
borderStyle="round" | ||
borderColor="green" | ||
flexDirection="column" | ||
> | ||
<StatusMessage variant="success"> | ||
<Text bold> | ||
Transformation complete | ||
</Text> | ||
</StatusMessage> | ||
<Box | ||
paddingLeft={2} | ||
flexDirection="column" | ||
> | ||
<Text> | ||
Number of transformed files: { transformedFileNum } | ||
</Text> | ||
<Text> | ||
Execution time: { formatExecutionTime(executionTime) } | ||
</Text> | ||
</Box> | ||
</Box> | ||
) } | ||
</Box> | ||
) | ||
} | ||
|
||
export default App |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/usr/bin/env node | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
import React from 'react' | ||
|
||
import { render } from 'ink' | ||
import meow from 'meow' | ||
|
||
import App from './App.js' | ||
|
||
meow( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
` | ||
Usage: | ||
$ npx @channel.io/bezier-codemod | ||
|
||
More info: | ||
https://github.com/channel-io/bezier-react | ||
`, | ||
{ | ||
importMeta: import.meta, | ||
flags: { | ||
name: { | ||
type: 'string', | ||
}, | ||
}, | ||
}, | ||
) | ||
|
||
render(<App />) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.