diff --git a/README.md b/README.md index 955169d..bfce930 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ Typestep aims to simplify the migration process from JavaScript to TypeScript in existing projects by offering a gradual transition strategy. It allows developers to introduce TypeScript incrementally by leveraging the parsing of TypeScript compiler output (tsc) -**⚠️ Work in Progress ⚠️** - -This project is currently under active development and is not yet considered stable +> [!WARNING] +> This project is currently under active development and is not yet considered stable ## Usage @@ -12,8 +11,25 @@ This project is currently under active development and is not yet considered sta npm install -g typestep ``` +> [!WARNING] +> Do Not Use `--pretty` option with `tsc` +```bash +tsc > tsc-output.log +``` + ### Config file +#### Init config file + +> [!NOTE] +> Init command will create your Typestep config file with all files from the tsc output marked as ignored + +```bash +typestep init tsc-output.log +``` + +#### Or create your config file + ```ts // typestep.config.ts export default { @@ -21,11 +37,8 @@ export default { } ``` -⚠️ **Do Not Use `--pretty` option with `tsc`** -```bash -tsc > tsc-output.log -``` +### Run typestep ```bash -typestep tsc-output.log +typestep run tsc-output.log ``` diff --git a/package.json b/package.json index 8ab6f5e..4dd3d9f 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "typecheck": "tsc" }, "dependencies": { + "citty": "^0.1.6", "jiti": "^1.21.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 833cbd3..e5cb538 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + citty: + specifier: ^0.1.6 + version: 0.1.6 jiti: specifier: ^1.21.0 version: 1.21.0 diff --git a/src/cli.ts b/src/cli.ts index e5856e2..c284635 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,63 +1,17 @@ #!/usr/bin/env node -/* eslint-disable no-console */ -import { existsSync } from 'node:fs' -import { readFile } from 'node:fs/promises' -import { resolve } from 'node:path' -import process from 'node:process' -import type { TypestepConfig } from './types.js' -import { tryImport, tscDiagnosticToTscError } from './utils.js' -import { getFinalOutput, parseTscOutput } from './index.js' - -function getTscOutputPath() { - const args = process.argv.slice(2) - - if (args.length === 0) { - console.log('No tsc output file provided') - process.exit(1) - } - - const tscOutputPath = args[0] - - if (!existsSync(tscOutputPath)) { - console.log(`File ${tscOutputPath} does not exist`) - process.exit(1) - } - - return tscOutputPath -} - -async function readConfigFile() { - const configFile = resolve(process.cwd(), 'typestep.config.ts') - - if (!existsSync(configFile)) { - console.log('No config file found') - return - } - - const configModule = await tryImport(configFile) - - if (!configModule?.default) { - console.log('No default export found in config file') - return - } - - return configModule.default as TypestepConfig -} - -async function run() { - const tscOutput = await readFile(getTscOutputPath(), 'utf8') - const configFile = await readConfigFile() - const parsedTscOutput = parseTscOutput(tscOutput) - - const tscDiagnostics = getFinalOutput(parsedTscOutput, configFile) - - if (tscDiagnostics.length === 0) { - console.log('No tsc errors found') - return - } - - console.error(tscDiagnostics.map(tscDiagnosticToTscError).join('\n')) - process.exit(1) -} - -run() +import { defineCommand, runMain } from 'citty' +import { version } from '../package.json' + +const main = defineCommand({ + meta: { + name: 'typestep', + version, + description: 'CLI tool to filter tsc output', + }, + subCommands: { + run: () => import('./commands/run.js').then(mod => mod.default), + init: () => import('./commands/init.js').then(mod => mod.default), + }, +}) + +runMain(main) diff --git a/src/commands/init.ts b/src/commands/init.ts new file mode 100644 index 0000000..d8e615f --- /dev/null +++ b/src/commands/init.ts @@ -0,0 +1,42 @@ +#!/usr/bin/env node +import { existsSync } from 'node:fs' +import { readFile, writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import process from 'node:process' +import { defineCommand } from 'citty' +import { parseTscOutput } from '../index.js' +import { writeTypestepConfig } from '../utils.js' +import { CONFIG_FILE_NAME } from '../constants.js' + +async function init(tscOutputFile: string) { + const tscOutput = await readFile(tscOutputFile, 'utf8') + const parsedTscOutput = parseTscOutput(tscOutput) + + const ignoredFiles = [...new Set(parsedTscOutput.map(({ path }) => path))] + const configFileContent = writeTypestepConfig({ ignoredFiles }) + + return writeFile(CONFIG_FILE_NAME, configFileContent) +} + +export default defineCommand({ + meta: { + name: 'init', + description: 'Initialize the Typestep configuration file with all files from the tsc output marked as ignored', + }, + args: { + tsc_output_file: { + type: 'positional', + description: 'The tsc output file path to be processed', + required: true, + }, + }, + run({ args }) { + if (!existsSync(args.tsc_output_file)) + throw new Error('Tsc output file not found') + + if (existsSync(CONFIG_FILE_NAME)) + throw new Error('Typestep config file already exists') + + init(resolve(process.cwd(), args.tsc_output_file)) + }, +}) diff --git a/src/commands/run.ts b/src/commands/run.ts new file mode 100644 index 0000000..255faf0 --- /dev/null +++ b/src/commands/run.ts @@ -0,0 +1,66 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import process from 'node:process' +import { defineCommand } from 'citty' + +import type { TypestepConfig } from '../types.js' +import { tryImport, tscDiagnosticToTscError } from '../utils.js' +import { getFinalOutput, parseTscOutput } from '../index.js' +import { CONFIG_FILE_NAME } from '../constants.js' + +async function readConfigFile() { + const configFile = resolve(process.cwd(), CONFIG_FILE_NAME) + + if (!existsSync(configFile)) { + console.log('No config file found') + return + } + + const configModule = await tryImport(configFile) + + if (!configModule?.default) { + console.log('No default export found in config file') + return + } + + return configModule.default as TypestepConfig +} + +async function run(tscOutputFile: string) { + const tscOutput = await readFile(tscOutputFile, 'utf8') + const configFile = await readConfigFile() + const parsedTscOutput = parseTscOutput(tscOutput) + + const tscDiagnostics = getFinalOutput(parsedTscOutput, configFile) + + if (tscDiagnostics.length === 0) { + console.log('No tsc errors found') + return + } + + console.error(tscDiagnostics.map(tscDiagnosticToTscError).join('\n')) + process.exit(1) +} + +export default defineCommand({ + meta: { + name: 'run', + description: 'Run the Typestep CLI tool with the tsc output file as input', + }, + args: { + tsc_output_file: { + type: 'positional', + description: 'The tsc output file path to be processed', + required: true, + }, + }, + run({ args }) { + if (!existsSync(args.tsc_output_file)) + throw new Error('Tsc output file not found') + + run(resolve(process.cwd(), args.tsc_output_file)) + }, +}) diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..60603e3 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const CONFIG_FILE_NAME = 'typestep.config.ts' diff --git a/src/utils.ts b/src/utils.ts index f6499ad..2cc3522 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ import process from 'node:process' import jiti from 'jiti' -import type { TscDiagnostic } from './types.js' +import type { TscDiagnostic, TypestepConfig } from './types.js' export function tryImport(file: string, rootDir: string = process.cwd()) { // @ts-expect-error "This expression is not callable." but works fine @@ -20,3 +20,7 @@ export function tscDiagnosticToTscError(diag: TscDiagnostic) { return `${path}(${cursor.line},${cursor.column}): error ${tsCode}: ${error}` } + +export function writeTypestepConfig(config: TypestepConfig) { + return `export default ${JSON.stringify(config, null, 2)}` +} diff --git a/tsconfig.json b/tsconfig.json index 3acd095..9bcce9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "target": "ESNext", "module": "NodeNext", "moduleResolution": "NodeNext", + "resolveJsonModule": true, "strict": true, "noEmit": true, "esModuleInterop": true,