text: #! /usr/bin/env node
  
  import fs from 'fs'
  import os from 'os'
  import { execFileSync } from 'child_process'
  import { createComponent, generateIndex } from '../src/compiler/transpiler.js'
  import defaultConfig from '../src/compiler/config.js'
  
  // Read the version from package.json
  const packagePath = fs.existsSync('./node_modules/@polight/lego/package.json')
    ? './node_modules/@polight/lego/package.json'
    : './package.json'
  const { version } = JSON.parse(fs.readFileSync(packagePath))
  
  // Read the config from the args
  const args = process.argv
  const [sourceDir, targetDir] = args.slice(2).filter(a => !a.startsWith('-'))
  const argsConfig = {
    sourceDir,
    targetDir,
    watch: args.indexOf('-w') >= 0
  }
  
  /**
   * Merge 2 objects.
   * This is different from `Object.assign` as it will only overwrite
   * property that have a defined value.
   * @param {Object} native
   * @param {Object} override
   * @returns {Object} the graal you prayed for 🤲
   */
  function mergeObjects(native, override) {
    return Object.keys(native).reduce((obj, key) => {
      obj[key] = (key in override && override[key])
        ? override[key]
        : native[key]
      return obj
    }, {})
  }
  
  /**
   * Detect if the running system is a Microsoft Windows®
   * @returns boolean
   */
  function isWindows() {
    return os.platform() === 'win32'
  }
  
  /**
   * Browse files in a directory considering some extensions.
   *
   * @param {string} dirname Name of the directory from where to list
   * @param {string} extensions list of extensions to read from
   * @returns {Array} list of file path
   */
  async function walkDir(dirname, extensions) {
    const stdout = isWindows()
      ? execFileSync('cmd', ['/c', 'dir', '/s', '/b', fs.realpathSync(dirname)])
      : execFileSync('find', [dirname])
    const dirs = String(stdout).split(os.EOL).filter(d => d)
    if(!extensions) return dirs
    return dirs.filter(d => extensions.includes(d.split('.').splice(-1)[0]))
  }
  
  /**
   * Convert a series of HTML components into JS components.
   *
   * @param {string} sourceDir directory from where to read HTML components
   * @param {string} targetDir directory where to write JS components
   * @param {Object} config
   * @returns {Object} { component, filename } object
   */
  async function compile(sourceDir, targetDir, config) {
    const filenames = await walkDir(sourceDir, ['html'])
    fs.mkdirSync(targetDir, { recursive: true })
    return filenames.map(f => {
      const pattern = isWindows() ? /.*\\(.+)\.html/ : /.*\/(.+)\.html/
      const filename = f.replace(pattern, '$1')
      const component = createComponent({
        html: fs.readFileSync(f, 'utf8'),
        name: filename,
        version,
        ...config
      })
      fs.writeFileSync(`${targetDir}/${filename}.js`, component.content, 'utf8')
      return { component, filename }
    })
  }
  
  /**
   * Create an index.js file in a directory
   * @param {string} targetDir Directory where to write the file
   * @param {string} filenames List of files name to interpret as components
   */
  async function writeIndex(targetDir, filenames) {
    const content = generateIndex(filenames)
    fs.writeFileSync(`${targetDir}/index.js`, content, 'utf8')
  }
  
  /**
   * Build the whole stuff:
   * 1. calculate best configuration
   * 2. compile sourceDir to targetDir
   * 3. create the index.js and its content
   * 4. if `watch`ing for changes, run `compile` over again
   */
  async function build() {
    let userConfig = {}
    try {
      const pathPrefix = isWindows() ? 'file://' : ''
      const content = await import(`${pathPrefix + process.cwd()}/lego.config.js`)
      userConfig = content.default
    }
    catch {
      console.warn('⚠️  Missing lego.config.js file, building with defaults.')
    }
    const config = mergeObjects(mergeObjects(defaultConfig, userConfig), argsConfig)
    const { sourceDir, targetDir } = config
    const compiled = await compile(sourceDir, targetDir, config)
    writeIndex(targetDir, compiled.map(c => c.filename))
    const names = compiled.map(c => c.component.name)
    console.info(`⚙️  Current configuration:`, config)
    console.info(`🏗  ${names.length} component${names.length > 1 ? 's were' : ' was'} compiled into "${config.targetDir}": ${names.join(', ')}.`)
  
    if (config.watch) {
      console.info(`\n👀 Watching changes in ${sourceDir}…`)
      fs.watch(sourceDir, async (event, filename) => {
        await compile(sourceDir, targetDir, config)
        console.info(`  ♻️  ${filename} was recompiled!`)
      })
    }
  }
  
  build()
  