diff --git a/README.md b/README.md index a8172ee..5b160a5 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,62 @@ Returns `{ code, map }`. `map` will be undefined if no sourcemap was supplied. - `moduleType` - The type of module being transformed. - `sourcemap` - Optional existing source map for the code. +## CLI Tool + +The package includes a CLI tool for applying transformations to source files: + +```bash +npx @apm-js-collab/code-transformer transformer.js source-file.js +``` + +### CLI Usage + +The CLI tool takes two arguments: +1. `transformer.js` - A file that exports instrumentation configuration(s) +2. `source-file.js` - The source file to transform (can be any path, including node_modules) + +The transformed code is written to stdout, which you can redirect to a file or pipe to other commands. + +### CLI Example + +Create a transformer configuration file: + +```javascript +// my-transformer.js +module.exports = [{ + channelName: 'my-fetch-channel', + module: { + name: 'my-module', + versionRange: '>=1.0.0', + filePath: 'index.js' + }, + functionQuery: { + functionName: 'fetch', + kind: 'Async' + } +}] +``` + +Apply the transformation: + +```bash +npx @apm-js-collab/code-transformer my-transformer.js lib/index.js > instrumented.js +``` + +The transformer configuration file can also export an object with additional options: + +```javascript +module.exports = { + configs: [/* array of configs */], + dcModule: './custom-diagnostics-channel.js', // optional custom dc module + customTransforms: { // optional custom transform functions + myCustomTransform: (state, node) => { + // custom AST transformation logic + } + } +} +``` + ## License See LICENSE diff --git a/cli.js b/cli.js new file mode 100755 index 0000000..513c1a5 --- /dev/null +++ b/cli.js @@ -0,0 +1,118 @@ +#!/usr/bin/env node +'use strict' + +const { readFileSync } = require('fs') +const { resolve, basename } = require('path') +const { Transformer } = require('./lib/transformer.js') + +function printUsage () { + console.error(`Usage: npx @apm-js-collab/code-transformer + +Arguments: + transformer.js Path to transformer configuration file + source-file Path to source file to transform (can be a node module path) + +The transformer.js file should export one of: + - An array of instrumentation configs + - An object with { configs, dcModule?, customTransforms? } + +Example transformer.js: + module.exports = [{ + channelName: 'my-channel', + module: { name: 'my-module', versionRange: '>=1.0.0', filePath: 'index.js' }, + functionQuery: { functionName: 'myFunction', kind: 'Async' } + }] + +The transformed code will be written to stdout. +`) + process.exit(1) +} + +function main () { + const args = process.argv.slice(2) + + if (args.length !== 2) { + printUsage() + } + + const [transformerPath, sourceFilePath] = args + + // Resolve paths + const absoluteTransformerPath = resolve(process.cwd(), transformerPath) + const absoluteSourcePath = resolve(process.cwd(), sourceFilePath) + + // Load transformer configuration + let transformerConfig + try { + transformerConfig = require(absoluteTransformerPath) + } catch (error) { + console.error(`Error loading transformer file: ${error.message}`) + process.exit(1) + } + + // Parse transformer config + let configs + let dcModule + let customTransforms = {} + + if (Array.isArray(transformerConfig)) { + configs = transformerConfig + } else if (typeof transformerConfig === 'object' && transformerConfig.configs) { + configs = transformerConfig.configs + dcModule = transformerConfig.dcModule + customTransforms = transformerConfig.customTransforms || {} + } else { + console.error('Transformer file must export an array of configs or an object with { configs, dcModule?, customTransforms? }') + process.exit(1) + } + + if (!Array.isArray(configs) || configs.length === 0) { + console.error('Transformer must provide at least one config') + process.exit(1) + } + + // Read source file + let sourceCode + try { + sourceCode = readFileSync(absoluteSourcePath, 'utf-8') + } catch (error) { + console.error(`Error reading source file: ${error.message}`) + process.exit(1) + } + + // Determine module type from file extension + const moduleType = absoluteSourcePath.endsWith('.mjs') || absoluteSourcePath.endsWith('.mts') + ? 'esm' + : absoluteSourcePath.endsWith('.cjs') || absoluteSourcePath.endsWith('.cts') + ? 'cjs' + : 'unknown' + + // Extract module information from first config or use defaults + const firstConfig = configs[0] + const moduleName = firstConfig.module?.name || 'cli-module' + const version = '1.0.0' + const filePath = basename(absoluteSourcePath) + + // Create transformer and apply transformations + try { + // Create transformer directly with the configs + const transformer = new Transformer( + moduleName, + version, + filePath, + configs, + dcModule || 'diagnostics_channel', + customTransforms + ) + + const result = transformer.transform(sourceCode, moduleType) + + // Output transformed code to stdout + process.stdout.write(result.code) + } catch (error) { + console.error(`Error transforming code: ${error.message}`) + process.exit(1) + } +} + +main() diff --git a/package.json b/package.json index 7d2d7b5..f575d20 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,14 @@ "files": [ "./index.js", "./index.d.ts", + "./cli.js", "./lib/**/*.js", "LICENSE", "NOTICE" ], + "bin": { + "code-transformer": "./cli.js" + }, "main": "./index.js", "types": "./index.d.ts", "scripts": {