-1

Im executing a ts file with:

npx ts-node ./tinker.ts

The file reads and parses the AST of another file sample.ts containing a single line:

console.log(123)

Next it should execute that code, but manipulate it before doing so - for example I want to change 123 to 1337.

So, the final result of running npx ts-node ./tinker.ts should be that 1337 is printed in my terminal. Please see my draft below. The code comments is the part that I could not understand how to do.

sample.ts

console.log(123);

tinker.ts

import * as fs from "fs";
const ts = require("typescript");
const path = "./sample.ts";
const code = fs.readFileSync(path, "utf-8"); // "console.log(123)"

const node = ts.createSourceFile("TEMP.ts", code, ts.ScriptTarget.Latest);

let logStatement = node.statements.at(0);
logStatement.expression.arguments.at(0).text = "1337";

// execute the manipulated code!
// expect to see 1337 logged!

To reiterate, running npx ts-node ./tinker.ts should log 1337. How can I achieve this?

ajthinking
  • 3,386
  • 8
  • 45
  • 75

1 Answers1

3

Possible solution

The idea of the solution is to perform the following actions:

  1. Create a modified copy of the input TypeScript module.
  2. Dynamically import the created TypeScript module to «evaluate» it.

Draft example program

package.json file

{
  "name": "question-73432934",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "BSD",
  "dependencies": {
    "ts-morph": "15.1.0",
    "typescript": "4.7.4"
  },
  "devDependencies": {
    "ts-node": "10.9.1"
  }
}

After updating the file content, please, execute the command:

$ npm clean-install

tsconfig.json file

{
  "compilerOptions": {
    "target": "es2017",
    "module": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true
  },
  "ts-node": {
    "esm": true
  }
}

sample.ts file

console.log(123);

tinker.ts file

import * as fs from "fs";
import { Project, SyntaxKind, CallExpression } from "ts-morph";

async function createModifiedFile(inputFilePath: string, outputFilePath: string) {
    const code = await fs.promises.readFile(inputFilePath, "utf8");

    const project = new Project();
    const sourceFile = project.createSourceFile(
        outputFilePath,
        code,
        { overwrite: true }
    );

    const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
    const callExpression = callExpressions[0] as CallExpression;

    callExpression.removeArgument(0);
    callExpression.addArgument("1337");

    await sourceFile.save();
};

async function importModuleDynamically(filePath: string) {
    await import(filePath);
};

await createModifiedFile("sample.ts", "temp.ts");
await importModuleDynamically("./temp.ts");

Run

$ npx ts-node tinker.ts

Program output:

1337

The created TypeScript module:

$ cat temp.ts 
console.log(1337);

Additional references

TypeScript: Source code transformation

TypeScript: Dynamic module import