text: import { existsSync, readFileSync, writeFileSync } from 'fs';
  import * as parser from '@babel/parser';
  import traverse from '@babel/traverse';
  import generate from '@babel/generator';
  import template from '@babel/template';
  import * as t from '@babel/types';
  import assert from 'assert';
  import prettier from 'prettier';
  
  export default function({ key, value, file, plugin }) {
    let content = `export default {}`;
    if (existsSync(file)) {
      content = readFileSync(file, 'utf-8');
    }
    const newContent = update(content, key, value, plugin);
    writeFileSync(file, `${newContent}\n`, 'utf-8');
  }
  
  function buildExpression(keys, str) {
    if (str.startsWith('{') || str.startsWith('[')) {
      // do nothing
    } else if (str === 'true' || str === 'false') {
      // do nothing
    } else {
      str = `'${str}'`;
    }
    let exp = template(`(${str})`, { placeholderPattern: false })().expression;
  
    let i = 0;
    keys = keys.reverse();
    while (i < keys.length) {
      exp = t.objectExpression([t.objectProperty(t.identifier(keys[i]), exp)]);
      i += 1;
    }
  
    return exp;
  }
  
  function getVariableDeclarator(node, path) {
    if (t.isIdentifier(node) && path.scope.hasBinding(node.name)) {
      const bindingNode = path.scope.getBinding(node.name).path.node;
      if (t.isVariableDeclarator(bindingNode)) {
        node = bindingNode.init;
      }
    }
    return node;
  }
  
  function findPluginConfig(node, path, plugin) {
    let plugins = node.properties.find(p => {
      return t.isIdentifier(p.key) && p.key.name === 'plugins';
    }).value;
    assert(plugins, `plugins is not configured.`);
    plugins = getVariableDeclarator(plugins, path);
    assert(t.isArrayExpression(plugins), `plugins must be array.`);
  
    for (let [index, el] of plugins.elements.entries()) {
      if (t.isStringLiteral(el) && el.value === plugin) {
        el = template(`(['${plugin}', {}])`)().expression;
        plugins.elements.splice(index, 1, el);
      }
      el = getVariableDeclarator(el, path);
      if (
        t.isArrayExpression(el) &&
        t.isStringLiteral(el.elements[0]) &&
        el.elements[0].value === plugin
      ) {
        if (!el.elements[1]) {
          el.elements.push(template(`({})`)().expression);
        }
        let config = el.elements[1];
        config = getVariableDeclarator(config, path);
        assert(t.isObjectExpression(config), `config for ${plugin} is not object`);
        return config;
      }
    }
  }
  
  export function update(content, key, value, plugin) {
    const ast = parser.parse(content, {
      sourceType: 'module',
      plugins: ['jsx', 'typescript'],
    });
    traverse(ast, {
      ExportDefaultDeclaration(path) {
        let node = path.node.declaration;
  
        // export default {} as IConfig;
        if (node.type === 'TSAsExpression') {
          node = node.expression;
        }
  
        // const a;
        // export default a;
        node = getVariableDeclarator(node, path);
  
        assert(t.isObjectExpression(node), `config file must export default a Plain Object`);
        if (plugin) {
          node = findPluginConfig(node, path, plugin);
        }
        let { properties } = node;
  
        let obj = key;
        if (typeof key === 'string') {
          obj = {
            [key]: value,
          };
        }
  
        Object.keys(obj).forEach(key => {
          const value = obj[key];
          const keys = key.split('.');
          let i = 0;
          let ps = properties;
          while (i < keys.length) {
            let hasFound;
            for (const property of ps) {
              if (
                t.isIdentifier(property.key, {
                  name: keys[i],
                })
              ) {
                if (i === keys.length - 1 || !t.isObjectExpression(property.value)) {
                  property.value = buildExpression(keys.slice(i + 1), value);
                  return;
                } else {
                  ps = property.value.properties;
                }
                hasFound = true;
                break;
              }
            }
            if (!hasFound) {
              ps.push(
                t.objectProperty(t.identifier(keys[i]), buildExpression(keys.slice(i + 1), value)),
              );
              break;
            } else {
              i += 1;
            }
          }
        });
      },
    });
    const newCode = generate(ast, {}).code;
    return prettier.format(newCode, {
      singleQuote: true,
      trailingComma: 'es5',
      printWidth: 100,
      parser: 'typescript',
    });
  }
  