Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm run build
- name: Run lint
run: pnpm run lint

- name: Run tests
run: pnpm test

- name: Run lint
run: pnpm run lint
- name: Build
run: pnpm run build

- name: Publish to NPM
env:
Expand Down
102 changes: 33 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ Automated code transformations for Suites projects. Built on [jscodeshift](https
## Usage

```bash
npx @suites/codemod <transform> <path> [options]
npx @suites/codemod <codemod> <source> [options]
```

**Example:**
```bash
npx @suites/codemod automock/2/to-suites-v3 src/**/*.spec.ts
```

Run with `--dry-run` to preview changes without modifying files.
Run with `--dry` or `-d` to preview changes without modifying files.

## Available Transforms
## Available Codemods

- **`automock/2/to-suites-v3`** - Migrate test files from Automock v2 to Suites v3 testing framework

Expand Down Expand Up @@ -62,30 +62,40 @@ describe('UserService', () => {
});
```

## CLI Options
## CLI Reference

### Arguments

| Argument | Description | Default |
|----------|-------------|---------|
| `codemod` | Codemod slug to run. See available transforms below. | - |
| `source` | Path to source files or directory to transform including glob patterns. | `.` |

### Options

| Option | Description | Default |
|--------|-------------|---------|
| `-d, --dry-run` | Preview changes without writing files | `false` |
| `-f, --force` | Bypass git safety checks | `false` |
| `-p, --parser <parser>` | Parser: `tsx`, `ts`, `babel` | `tsx` |
| `-e, --extensions <exts>` | File extensions to transform | `.ts,.tsx` |
| `-i, --ignore <patterns>` | Ignore file patterns (comma-separated) | - |
| `--print` | Print output to stdout | `false` |
| `-v, --verbose` | Show detailed logs | `false` |
| `--skip-validation` | Skip validation checks | `false` |
| `--list-transforms` | List all available transforms | - |

**More examples:**
| `-v, --version` | Output the current version | - |
| `-d, --dry` | Dry run (no changes are made to files) | `false` |
| `-f, --force` | Bypass Git safety checks and forcibly run codemods | `false` |
| `-p, --print` | Print transformed files to stdout, useful for development | `false` |
| `--verbose` | Show more information about the transform process | `false` |
| `--parser <parser>` | Parser to use: `tsx`, `ts`, `babel` | `tsx` |
| `-h, --help` | Display help message | - |

**Examples:**
```bash
# Preview changes
npx @suites/codemod automock/2/to-suites-v3 src --dry-run
# Preview changes (dry run)
npx @suites/codemod automock/2/to-suites-v3 src --dry

# Print output to stdout
npx @suites/codemod automock/2/to-suites-v3 src/file.ts -p

# Ignore certain files
npx @suites/codemod automock/2/to-suites-v3 src --ignore "**/*.integration.ts"
# Verbose output
npx @suites/codemod automock/2/to-suites-v3 src --verbose

# List all transforms
npx @suites/codemod --list-transforms
# Use different parser
npx @suites/codemod automock/2/to-suites-v3 src --parser babel
```

## Transform Details
Expand Down Expand Up @@ -132,15 +142,15 @@ Built-in validation ensures:
- Commit your changes or use `--force` to bypass

**"No files found"**
- Check your path and file extensions: `--extensions .spec.ts,.test.ts`
- Check your source path and ensure it contains `.ts` or `.tsx` files

**Parser errors**
- Try the babel parser: `--parser babel`

**Validation failed**
- Run with `--verbose` for detailed logs
- Review validation errors in the output
- Use `--skip-validation` to bypass (not recommended)
- Fix the issues reported by validators

For more help, see [troubleshooting guide](https://github.com/suites-dev/codemod/issues) or open an issue.

Expand Down Expand Up @@ -235,52 +245,6 @@ test/
fixtures/ # Test fixtures (before/after)
```

## Local Development

### Running Locally

From within the codemod repository:

```bash
# Build first
pnpm build

# Run on a target repository
node dist/cli.js automock/2/to-suites-v3 /path/to/repo --dry-run

# Run on test fixtures
node dist/cli.js automock/2/to-suites-v3 fixtures/simple-final --dry-run

# Verbose output for debugging
node dist/cli.js automock/2/to-suites-v3 /path/to/repo --dry-run --verbose
```

### Using npm link for Testing

```bash
# In the codemod repo
npm link

# Now use it anywhere like npx
codemod automock/2/to-suites-v3 /path/to/repo --dry-run

# Unlink when done
npm unlink -g @suites/codemod
```

### Running Tests

```bash
# All tests
pnpm test

# Specific test file
pnpm test path/to/test.spec.ts

# With coverage
pnpm test --coverage
```

## License

MIT (c) [Omer Morad](https://github.com/omermorad)
Expand Down
8 changes: 4 additions & 4 deletions fixtures/simple-final/input.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { TestBed } from '@automock/jest';
import { TestBed } from '@suites/unit';

describe('UserService', () => {
let service: UserService;

beforeAll(() => {
const { unit } = TestBed.create(UserService)
beforeAll(async () => {
const { unit } = await TestBed.solitary(UserService)
.mock(UserRepository)
.using({
.final({
findById: (id: string) => Promise.resolve({ id, name: 'Test' })
})
.compile();
Expand Down
73 changes: 32 additions & 41 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,30 @@ const program = new Command();
program
.name('@suites/codemod')
.description('Code transformation tool for the Suites testing framework')
.version('0.1.0')
.version('0.1.0', '-v, --version', 'Output the current version')
.helpOption('-h, --help', 'Display help message')
.argument(
'[transform]',
'Transform to apply (e.g., automock/2/to-suites-v3)'
'[codemod]',
'Codemod slug to run. See "https://github.com/suites-dev/codemod".'
)
.argument('[path]', 'Path to transform (file or directory)', '.')
.option('-d, --dry-run', 'Preview changes without writing files', false)
.option('-f, --force', 'Bypass git safety checks', false)
.option('-p, --parser <parser>', 'Parser to use (tsx, ts, babel)', 'tsx')
.option('-e, --extensions <exts>', 'File extensions to transform', '.ts,.tsx')
.option('-i, --ignore <patterns>', 'Ignore file patterns (comma-separated)')
.option('--print', 'Print transformed output to stdout', false)
.option('-v, --verbose', 'Show detailed transformation logs', false)
.option('--skip-validation', 'Skip post-transformation validation checks', false)
.option('--list-transforms', 'List all available transforms', false)
.argument('[source]', 'Path to source files or directory to transform including glob patterns.', '.')
.option('-d, --dry', 'Dry run (no changes are made to files)', false)
.option('-f, --force', 'Bypass Git safety checks and forcibly run codemods', false)
.option('-p, --print', 'Print transformed files to stdout, useful for development', false)
.option('--verbose', 'Show more information about the transform process', false)
.option('--parser <parser>', 'Parser to use (tsx, ts, babel)', 'tsx')
.action(
async (
transformArg: string | undefined,
pathArg: string | undefined,
options: CliOptions & { listTransforms?: boolean }
codemodArg: string | undefined,
sourceArg: string | undefined,
options: CliOptions
) => {
const logger = createLogger(options.verbose);

// Handle --list-transforms
if (options.listTransforms) {
console.log('Available transforms:\n');
AVAILABLE_TRANSFORMS.forEach((t) => {
console.log(` ${t.name}`);
console.log(` ${t.description}\n`);
});
return;
}

// Validate transform is provided
if (!transformArg) {
logger.error('Transform argument required.');
logger.info('\nAvailable transforms:');
// Validate codemod is provided
if (!codemodArg) {
logger.error('Codemod argument required.');
logger.info('\nAvailable codemods:');
AVAILABLE_TRANSFORMS.forEach((t) => {
console.log(` ${t.name}`);
console.log(` ${t.description}\n`);
Expand All @@ -59,37 +46,41 @@ program
process.exit(1);
}

const transformName = transformArg;
const targetPath = pathArg || '.';
const codemodName = codemodArg;
const sourcePath = sourceArg || '.';

const transformInfo = getTransform(transformName);
const transformInfo = getTransform(codemodName);
if (!transformInfo) {
logger.error(`Unknown transform: ${transformName}`);
logger.error('Run with --list-transforms to see available transforms');
logger.error(`Unknown codemod: ${codemodName}`);
logger.info('\nAvailable codemods:');
AVAILABLE_TRANSFORMS.forEach((t) => {
console.log(` ${t.name}`);
console.log(` ${t.description}\n`);
});
process.exit(1);
}

try {
// Git safety check (unless in dry-run or force mode)
if (!options.dryRun && !options.force) {
// Git safety check (unless in dry or force mode)
if (!options.dry && !options.force) {
checkGitStatus(logger);
}

// Show header with dynamic transform name
logger.section(`🔄 Suites Codemod - ${transformInfo.description}`);

if (options.dryRun) {
logger.info('Running in dry-run mode (no files will be modified)');
if (options.dry) {
logger.info('Running in dry mode (no changes are made to files)');
}

if (options.force && !options.dryRun) {
if (options.force && !options.dry) {
logger.warn('Bypassing git safety checks (--force enabled)');
}

logger.newline();

// Run the transformation
await runTransform(targetPath, transformInfo, options, logger);
await runTransform(sourcePath, transformInfo, options, logger);
} catch (error) {
logger.newline();
logger.error('Transformation failed:');
Expand Down
43 changes: 22 additions & 21 deletions src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ export async function runTransform(
logger.startSpinner('Discovering files..');

const fileProcessor = createFileProcessor({
extensions: options.extensions.split(',').map((ext) => ext.trim()),
ignorePatterns: options.ignore?.split(',').map((pattern) => pattern.trim()) || [],
extensions: ['.ts', '.tsx'],
ignorePatterns: [
'**/node_modules/**',
'**/dist/**',
'**/*.d.ts',
],
});

const allFiles = await fileProcessor.discoverFiles(targetPath);
Expand Down Expand Up @@ -154,7 +158,6 @@ async function transformFile(

// Apply transformation
const transformOutput = applyTransform(source, {
skipValidation: options.skipValidation,
parser: options.parser,
});

Expand All @@ -167,19 +170,17 @@ async function transformFile(
result.transformed = true;

// Collect validation errors and warnings
if (!options.skipValidation) {
transformOutput.validation.errors.forEach((err: ValidationError) => {
result.errors.push(`${err.rule}: ${err.message}`);
});

transformOutput.validation.warnings.forEach((warn: ValidationError) => {
result.warnings.push(`${warn.rule}: ${warn.message}`);
});

transformOutput.validation.criticalErrors.forEach((err: ValidationError) => {
result.errors.push(`[CRITICAL] ${err.rule}: ${err.message}`);
});
}
transformOutput.validation.errors.forEach((err: ValidationError) => {
result.errors.push(`${err.rule}: ${err.message}`);
});

transformOutput.validation.warnings.forEach((warn: ValidationError) => {
result.warnings.push(`${warn.rule}: ${warn.message}`);
});

transformOutput.validation.criticalErrors.forEach((err: ValidationError) => {
result.errors.push(`[CRITICAL] ${err.rule}: ${err.message}`);
});

// Handle --print flag (output to stdout instead of writing)
if (options.print) {
Expand All @@ -189,21 +190,21 @@ async function transformFile(
console.log(transformOutput.code);
logger.info('='.repeat(60));
result.changes.push('Printed to stdout');
} else if (!options.dryRun) {
} else if (!options.dry) {
// Write transformed file
await fs.writeFile(filePath, transformOutput.code, 'utf-8');
result.changes.push('File updated');
logger.success(` ${path.relative(process.cwd(), filePath)}`);
logger.success(` ${path.relative(process.cwd(), filePath)}`);
} else {
// Dry run - just report what would change
result.changes.push('Would be updated (dry-run)');
logger.info(` ~ ${path.relative(process.cwd(), filePath)} (dry-run)`);
result.changes.push('Would be updated (dry)');
logger.info(` ~ ${path.relative(process.cwd(), filePath)} (dry)`);
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Unknown error occurred';
result.errors.push(errorMessage);
logger.error(` ${path.relative(process.cwd(), filePath)}: ${errorMessage}`);
logger.error(` ${path.relative(process.cwd(), filePath)}: ${errorMessage}`);
}

return result;
Expand Down
5 changes: 1 addition & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ import type { Collection, JSCodeshift } from 'jscodeshift';
* CLI options
*/
export interface CliOptions {
dryRun: boolean;
dry: boolean;
force: boolean;
parser: string;
extensions: string;
ignore?: string;
print: boolean;
verbose: boolean;
skipValidation: boolean;
}

/**
Expand Down
Loading