Skip to content

Latest commit

 

History

History
345 lines (310 loc) · 9.11 KB

transform.md

File metadata and controls

345 lines (310 loc) · 9.11 KB

Transforming an Idea

The following will describe how to transform an idea to reality. As an example, we will create a plugin that will process all the enums in my.schema to a JavaScript or TypeScript file that can be included in the same project.

"Ideas are worthless without execution" - Many People

In a new project folder, install @ossph/idea and ts-morph with the following command.

$ npm i @ossph/idea ts-morph

ts-morph provides an easier way to programmatically navigate and manipulate TypeScript and JavaScript code.

You don't have to use ts-morph, but in this example makes it easier to digest what's going on. Create a new idea file called my.idea with the following code.

// my.idea
plugin "./make-enums" {
  ts true
  output "./enums.ts"
}
// sample enum
enum Roles {
  ADMIN "Admin"
  MANAGER "Manager"
  USER "User"
}
// sample enum
enum Sizes {
  SM "Small"
  MD "Medium"
  LG "Large"
}

Next, create a tsconfig.json file and enter the following configuration. This config will be consumed in ts-morph later.

// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "esModuleInterop": true,
    "lib": [ "es2021", "es7", "es6", "dom" ],
    "module": "commonjs",
    "noUnusedLocals": true,
    "outDir": "./dist/",
    "preserveConstEnums": true,
    "resolveJsonModule": true,
    "removeComments": true,
    "sourceMap": false,
    "strict": true,
    "target": "es6",
    "skipLibCheck": true
  },
  "include": [ "src/**/*.ts" ],
  "exclude": [ "dist", "docs", "node_modules", "test"]
}

Create a file called make-enums.js and paste in the following code. The exported function in this file will be called by idea when the transform command is called via $ npx idea and configured in your idea file.

// make-enums.js
exports.default = function transform({ config, schema, cli }) {
  //your code ...
}

By default idea will pass the plugin configuration from the schema, the parsed schema data and a cli interface to the transform function. The following shows ways to consume the config and cli.

// make-enums.js
exports.default = function transform({ config, schema, cli }) {
  // 1. Config
  //we need to know where to put this code...
  if (!config.output) {
    return cli.terminal.error('No output directory specified');
  }
  //code in typescript or javascript?
  const lang = config.lang || 'ts';
  //your code ...
}

The above example assumes there will be an output and lang setting in the config object. These correlate to the configuration provided in my.idea. The next thing to do is set up a ts-morph project like below.

// make-enums.js
const path = require('path');
const { Project, IndentationText } = require('ts-morph');
const { Loader } = require('@ossph/idea');

exports.default = function transform({ config, schema, cli }) {
  // 1. Config
  // ... (see above example)
  // 2. Project
  //find the absolute path from the output config
  const destination = Loader.absolute(config.output);
  //output directory from the destination
  const dirname = path.dirname(destination);
  //file name from the destination
  const filename = path.basename(destination);
  //start a ts-morph project
  const project = new Project({
    tsConfigFilePath: path.resolve(__dirname, './tsconfig.json'),
    skipAddingFilesFromTsConfig: true,
    compilerOptions: {
      outDir: dirname,
      // Generates corresponding '.d.ts' file.
      declaration: true, 
      // Generates a sourcemap for each corresponding '.d.ts' file.
      declarationMap: true, 
      // Generates corresponding '.map' file.
      sourceMap: true
    },
    manipulationSettings: {
      indentationText: IndentationText.TwoSpaces
    }
  });
  //create the directory if it does not exist
  const directory = project.createDirectory(dirname);
  //create a source file to manually populate later...
  const source = directory.createSourceFile(filename, '', { overwrite: true });

  //your code ...
}

After the ts-morph project is setup we can start processing the enums with the following code.

// make-enums.js
const path = require('path');
const { Project, IndentationText } = require('ts-morph');
const { Loader } = require('@ossph/idea');

exports.default = function transform({ config, schema, cli }) {
  // 1. Config
  // ... (see above example)
  // 2. Project
  // ... (see above example)
  // 3. Enum
  //if there are enums...
  if (typeof schema.enum === 'object') {
    // Enums in schema object will look like this...
    // { 
    //   "plugin": { ... },
    //   "enum": {
    //     "Roles": {
    //       "ADMIN": "Admin",
    //       "MANAGER": "Manager",
    //       "USER": "User"
    //     }
    //   },
    //   "type": { ... },
    //   "model": { ... }
    // }
    //loop through enums
    for (const name in schema.enum) {
      //get all the possible enum members ("ADMIN", "MANAGER", "USER")
      const members = Object.keys(schema.enum[name]);
      //add enum using ts-morph
      source.addEnum({
        name: name,
        isExported: true,
        // { name: "ADMIN", value: "Admin" }
        members: members.map(key => ({ 
          name: key, 
          value: schema.enums[name][key]
        }))
      });
    }
  }

  //your code ...
}

The last thing to do is save the file with the following code.

// make-enums.js
const path = require('path');
const { Project, IndentationText } = require('ts-morph');
const { Loader } = require('@ossph/idea');

exports.default = function transform({ config, schema, cli }) {
  // 1. Config
  // ... (see above example)
  // 2. Project
  // ... (see above example)
  // 3. Enum
  // ... (see above example)
  // 4. Save
  source.formatText();
  //if you want ts, tsx files
  if (lang == 'ts') {
    project.saveSync();
  //if you want js, d.ts files
  } else {
    project.emit();
  }
}

To run everything use the following command.

$ npx idea -i my.idea

This will create a new file in your project folder called enums.ts with the following content.

// enums.ts
export enum Roles {
  ADMIN = "Admin"
  MANAGER = "Manager"
  USER = "User"
}

export enum Sizes {
  SM = "Small"
  MD = "Medium"
  LG = "Large"
}

The above example in TypeScript would look like the following.

// make-enums.ts
import type { PluginWithCLIProps, EnumConfig } from '@ossph/idea';

import path from 'path';
import { Project, IndentationText } from 'ts-morph';
import { Loader } from '@ossph/idea';

export default function generate({ config, schema, cli }: PluginWithCLIProps) {
  // 1. Config
  //we need to know where to put this code...
  if (!config.output) {
    return cli.terminal.error('No output directory specified');
  }
  //code in typescript or javascript?
  const lang = config.lang || 'ts';
  // 2. Project
  //find the absolute path from the output config
  const destination = Loader.absolute(config.output as string);
  //output directory from the destination
  const dirname = path.dirname(destination);
  //file name from the destination
  const filename = path.basename(destination);
  //start a ts-morph project
  const project = new Project({
    tsConfigFilePath: path.resolve(__dirname, './tsconfig.json'),
    skipAddingFilesFromTsConfig: true,
    compilerOptions: {
      outDir: dirname,
      // Generates corresponding '.d.ts' file.
      declaration: true, 
      // Generates a sourcemap for each corresponding '.d.ts' file.
      declarationMap: true, 
      // Generates corresponding '.map' file.
      sourceMap: true, 
    },
    manipulationSettings: {
      indentationText: IndentationText.TwoSpaces
    }
  });
  //create the directory if it does not exist
  const directory = project.createDirectory(dirname);
  //create a source file to manually populate later...
  const source = directory.createSourceFile(filename, '', { overwrite: true });
  // 3. Enum
  //if there are enums...
  if (typeof schema.enum === 'object') {
    // Enums in schema object will look like this...
    // { 
    //   "plugin": { ... },
    //   "enum": {
    //     "Roles": {
    //       "ADMIN": "Admin",
    //       "MANAGER": "Manager",
    //       "USER": "User"
    //     }
    //   },
    //   "type": { ... },
    //   "model": { ... }
    // }
    //loop through enums
    for (const name in schema.enum) {
      //get enum
      const enums = schema.enum as Record<string, EnumConfig>;
      //get all the possible enum members ("ADMIN", "MANAGER", "USER")
      const members = Object.keys(enums[name]);
      //add enum using ts-morph
      source.addEnum({
        name: name,
        isExported: true,
        // { name: "ADMIN", value: "Admin" }
        members: members.map(key => ({ 
          name: key, 
          value: enums[name][key] as string
        }))
      });
    }
  }
  // 4. Save
  source.formatText();
  //if you want ts, tsx files
  if (lang == 'ts') {
    project.saveSync();
  //if you want js, d.ts files
  } else {
    project.emit();
  }
};

Learn More:

  • Mapping - Now that you understand what plugins do and how they related to ideas, you can learn more about the specific schema mappings here.
  • TS-Morph - Learn more about ts-morph here.