Writing Small Automation Script

Writing Small Automation Script

Background

The project just started, and the team decided to use nextjs with graphql, both graphql client and graphql server. But, we also have a separate backend that is written in Go. For me, it's not very common to use those stacks since I feel it feels redundant because we have both graphql server and a dedicated backend.

Then, within the graphql type definitions, we would like to define the definition the same as the proto file that we have from the backend. Here are the problems:

  • The message within the proto is using snake_case. For instance:

      message user {
          user_id: int32;
          first_name: string;
          last_name: string;
          phone_number: string;
          is_married: boolean;
          repeated friends: friend
      }
    
      message friend {
          user_id: int32
          username: string
          // ...
      }
    
  • But within the type definitions, we want it to use camelCase. So if the case like above, we want to transform it to:

      type user {
          userId: Int;
          firstName: String;
          lastName: String;
          phoneNumber: String;
          isMarried: Boolean;
          friends: [friend]
      }
    
      type friend {
          userId: Int
          username: String
          # ...
      }
    

At first, it did not feel tidy to change the type from user_id to userId, usually I just used ctrl+d command within the vscode to select all the same occurrences and change them, but as the feature added up, we needed to define more of type definition, and it felt tedious. So I initiated to create some script to run if there was an update or a new proto file came up and automate those steps instantly.

Solution

First I created a js file in the root project, so I could call it later by just node {filename} after that, I start by asking where is the file location, so I can get the whole data to process

const readline = require('readline');
const fs = require('fs').promises;
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

async function transformProto() {
    rl.question('Where is the file path?', async function (src){
        const data = await fs.readFile(src, 'utf8');
        let result = data
    })
}

after that, I will use regex to replace all the occurrences of word message with type

let result = data
    .replace(/message/g, 'type')

to change type from int32 or int64 to Number type for all occurrences

let result = data
    .replace(/message/g, 'type')
    .replace(/int32\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
    .replace(/int64\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')

and to change the snake_case to camelCase here's how I do it

let result = data
    .replace(/message/g, 'type')
    .replace(/int32\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
    .replace(/int64\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
    .replace(/([_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))

and to transform the string/bool/float is pretty much the same as int32

let result = data
    .replace(/message/g, 'type')
    .replace(/int32\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
    .replace(/int64\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
    .replace(/([_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))
    .replace(/string\s+(\w+)\s*=\s*(\d+);/g, '$1: String')
    .replace(/bool\s+(\w+)\s*=\s*(\d+);/g, '$1: Boolean')
    .replace(/float\s+(\w+)\s*=\s*(\d+);/g, '$1: Float')

to cover the case where there is repeated syntax, here's how I do it

let result = data
    .replace(/message/g, 'type')
    .replace(/int32\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
    .replace(/int64\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
    .replace(/([_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))
    .replace(/string\s+(\w+)\s*=\s*(\d+);/g, '$1: String')
    .replace(/bool\s+(\w+)\s*=\s*(\d+);/g, '$1: Boolean')
    .replace(/float\s+(\w+)\s*=\s*(\d+);/g, '$1: Float')
    .replace(/repeated\s+(\w+)\s+(\w+)\s*=\s*\d+;/g, '$2: [$1]')

after that process is finished, I write the result into a new generated file called generated.typeDef.ts, and close the process with process.exit(0)

await fs.writeFile(
'generated.typeDef.ts',
'export const typeDef `' + result + '`;\n',
'utf8');

console.log('✅ Success generate file to ./generated.typeDef.ts');
rl.close();
process.exit(0);

Full working code:

const readline = require('readline');
const fs = require('fs').promises;
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

async function transformProto() {
    rl.question('Where is the file path?', async function (src) {
        const data = await fs.readFile(src, 'utf8');
        let result = data
            .replace(/message/g, 'type')
            .replace(/int32\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
            .replace(/int64\s+(\w+)\s*=\s*(\d+);/g, '$1: Int')
            .replace(/([_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))
            .replace(/string\s+(\w+)\s*=\s*(\d+);/g, '$1: String')
            .replace(/bool\s+(\w+)\s*=\s*(\d+);/g, '$1: Boolean')
            .replace(/float\s+(\w+)\s*=\s*(\d+);/g, '$1: Float')
            .replace(/repeated\s+(\w+)\s+(\w+)\s*=\s*\d+;/g, '$2: [$1]')

        await fs.writeFile(
            'generated.typeDef.ts',
            'export const typeDef `' + result + '`;\n',
            'utf8'
        );

        console.log('✅ Success generate file to ./generated.typeDef.ts');
        rl.close();
        process.exit(0);
    })
}
transformProto()