Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
persist-credentials: false

- name: Install dependencies
run: npm install --ignore-scripts --only=dev
Expand All @@ -39,11 +38,11 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 19, 20, 21, 22, 23]
# Node.js release schedule: https://nodejs.org/en/about/releases/

name: Node.js ${{ matrix.node-version }}
name: Node.js ${{ matrix.node-version }} - ${{matrix.os}}

runs-on: ${{ matrix.os }}
steps:
Expand Down Expand Up @@ -72,4 +71,4 @@ jobs:
- name: Run tests
shell: bash
run: |
npm run test:ci
cd build && npm run test:ci
112 changes: 112 additions & 0 deletions commands/__test__/transform.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { join } from 'node:path'
import { run } from 'jscodeshift/src/Runner'
import prompts from 'prompts'
import { transform } from '../transform'

jest.mock('jscodeshift/src/Runner', () => ({
run: jest.fn(),
}))

describe('interactive mode', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('runs without codemodName and source params provided', async () => {
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()

prompts.inject(['magic-redirect'])
prompts.inject(['./transforms/__testfixtures__'])

await transform(undefined, undefined, { dry: true, silent: true })

expect(spyOnConsole).not.toHaveBeenCalled()
expect(run).toHaveBeenCalledTimes(1)
expect(run).toHaveBeenCalledWith(
join(__dirname, '../../', 'transforms/magic-redirect.js'),
['./transforms/__testfixtures__'],
{
babel: false,
dry: true,
extensions: 'cts,mts,ts,js,mjs,cjs',
ignorePattern: '**/node_modules/**',
silent: true,
verbose: 0,
},
)
})

it('runs properly on incorrect user input', async () => {
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()

prompts.inject(['magic-redirect'])

await transform('bad-codemod', './transforms/__testfixtures__', {
dry: true,
silent: true,
})

expect(spyOnConsole).not.toHaveBeenCalled()
expect(run).toHaveBeenCalledTimes(1)
expect(run).toHaveBeenCalledWith(
join(__dirname, '../../', 'transforms/magic-redirect.js'),
['./transforms/__testfixtures__'],
{
babel: false,
dry: true,
extensions: 'cts,mts,ts,js,mjs,cjs',
ignorePattern: '**/node_modules/**',
silent: true,
verbose: 0,
},
)
})

it('runs with codemodName and without source param provided', async () => {
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()

prompts.inject(['__testfixtures__'])

await transform('magic-redirect', undefined, {
dry: true,
silent: true,
})

expect(spyOnConsole).not.toHaveBeenCalled()
expect(run).toHaveBeenCalledTimes(1)
expect(run).toHaveBeenCalledWith(join(__dirname, '../../', 'transforms/magic-redirect.js'), ['__testfixtures__'], {
babel: false,
dry: true,
extensions: 'cts,mts,ts,js,mjs,cjs',
ignorePattern: '**/node_modules/**',
silent: true,
verbose: 0,
})
})
})

describe('Non-Interactive Mode', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('Transforms code with codemodName and source params provided', async () => {
const spyOnConsole = jest.spyOn(console, 'log').mockImplementation()

await transform('magic-redirect', '__testfixtures__', {
dry: true,
silent: true,
})

expect(spyOnConsole).not.toHaveBeenCalled()
expect(run).toHaveBeenCalledTimes(1)
expect(run).toHaveBeenCalledWith(join(__dirname, '../../', 'transforms/magic-redirect.js'), ['__testfixtures__'], {
babel: false,
dry: true,
extensions: 'cts,mts,ts,js,mjs,cjs',
ignorePattern: '**/node_modules/**',
silent: true,
verbose: 0,
})
})
})
55 changes: 18 additions & 37 deletions commands/transform.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { join } from 'node:path'
import execa from 'execa'
import { bold, green } from 'picocolors'
import type { Options } from 'jscodeshift'
import { run as jscodeshift } from 'jscodeshift/src/Runner'
import { bold } from 'picocolors'
import prompts from 'prompts'
import { TRANSFORM_OPTIONS } from '../config'
import { getAllFiles } from '../utils/file'

export function onCancel() {
console.info('> Cancelled process. Program will stop now without any actions. \n')
process.exit(1)
}

const jscodeshiftExecutable = require.resolve('.bin/jscodeshift')
const transformerDirectory = join(__dirname, '../', 'transforms')

// biome-ignore lint/suspicious/noExplicitAny: 'Any' is used because options can be anything.
export async function transform(codemodName: string, source: string, options: any): Promise<void> {
export async function transform(codemodName?: string, source?: string, options?: Record<string, unknown>) {
let codemodSelected = codemodName
let sourceSelected = source

const { dry, print, verbose } = options

let existCodemod = TRANSFORM_OPTIONS.find(({ value }) => value === codemodSelected)
const existCodemod = TRANSFORM_OPTIONS.find(({ value }) => value === codemodSelected)

if (!codemodSelected || (codemodSelected && !existCodemod)) {
const res = await prompts(
Expand All @@ -39,7 +36,6 @@ export async function transform(codemodName: string, source: string, options: an
)

codemodSelected = res.transformer
existCodemod = TRANSFORM_OPTIONS.find(({ value }) => value === codemodSelected)
}

if (!sourceSelected) {
Expand All @@ -56,37 +52,22 @@ export async function transform(codemodName: string, source: string, options: an
sourceSelected = res.path
}

const transformerPath = join(transformerDirectory, `${codemodSelected}.js`)

const args: string[] = []

if (dry) {
args.push('--dry')
if (!codemodSelected) {
console.info('> Codemod is not selected. Exist the program. \n')
process.exit(1)
}

if (print) {
args.push('--print')
}
const transformerPath = join(transformerDirectory, `${codemodSelected}.js`)

if (verbose) {
args.push('--verbose=2')
const args: Options = {
...options,
verbose: options?.verbose ? 2 : 0,
babel: false,
ignorePattern: '**/node_modules/**',
extensions: 'cts,mts,ts,js,mjs,cjs',
}

args.push('--no-babel')
args.push('--ignore-pattern=**/node_modules/**')
args.push('--extensions=cts,mts,ts,js,mjs,cjs')

const files = await getAllFiles(sourceSelected)

args.push('--transform', transformerPath, ...files.map((file) => file.toString()))

console.log(`Executing command: ${green('jscodeshift')} ${args.join(' ')}`)

const jscodeshiftProcess = execa(jscodeshiftExecutable, args, {
// include ANSI color codes
env: process.stdout.isTTY ? { FORCE_COLOR: 'true' } : {},
})
const jscodeshiftProcess = await jscodeshift(transformerPath, [sourceSelected || ''], args)

jscodeshiftProcess.stdout?.pipe(process.stdout)
jscodeshiftProcess.stderr?.pipe(process.stderr)
return jscodeshiftProcess
}
5 changes: 4 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ const program = new Command(packageJson.name)
.option('-d, --dry', 'Dry run (no changes are made to files)')
.option('-p, --print', 'Print transformed files to stdout')
.option('--verbose', 'Show more information about the transform process')
.option('--silent', "Don't print anything to stdout")
.usage('[codemod] [source] [options]')
.action(transform)
.action((codemodName, source, options) => {
transform(codemodName, source, options)
})
// Why this option is necessary is explained here: https://github.com/tj/commander.js/pull/1427
.enablePositionalOptions()

Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"private": true,
"version": "0.0.1",
"description": "Codemods for updating express servers.",
"main": "index.js",
"main": "build/index.js",
"contributors": ["Sebastian Beltran <bjohansebas@gmail.com>", "Filip Kudla <filip.kudla.dev@gmail.com>"],
"license": "MIT",
"bin": "./index.js",
"files": ["transforms/*.js", "commands/*.js", "utils/*.js", "config.js", "index.js"],
"bin": "build/index.js",
"files": ["build/transforms/*.js", "build/commands/*.js", "build/utils/*.js", "build/config.js", "build/index.js"],
"scripts": {
"dev": "tsc -d -w -p tsconfig.json",
"build": "tsc -d -p tsconfig.json",
Expand All @@ -18,7 +18,6 @@
},
"dependencies": {
"commander": "^12.1.0",
"execa": "^5.1.1",
"fast-glob": "^3.3.2",
"jscodeshift": "^17.1.1",
"picocolors": "^1.1.1",
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"downlevelIteration": true,
"preserveWatchOutput": true,
"resolveJsonModule": true,
"strictNullChecks": true
"strictNullChecks": true,
"outDir": "build"
},
"include": ["**/*.ts"],
"exclude": ["node_modules", "transforms/__testfixtures__/**"]
Expand Down
Loading