• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hexojs / hexo-util / 16649341003

31 Jul 2025 12:44PM UTC coverage: 71.629% (-25.2%) from 96.875%
16649341003

Pull #426

github

web-flow
Merge ba0ee12e7 into d497bc760
Pull Request #426: build: support both ESM and CommonJS

730 of 1049 branches covered (69.59%)

1439 of 2000 new or added lines in 41 files covered. (71.95%)

6 existing lines in 1 file now uncovered.

5196 of 7254 relevant lines covered (71.63%)

68.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.88
/scripts/pretest.mjs
1
import { spawnSync } from 'child_process';
1✔
2
import fs from 'fs';
1✔
3
import path from 'path';
1✔
4
import { fileURLToPath } from 'url';
1✔
5

1✔
6
const __filename = fileURLToPath(import.meta.url);
1✔
7
const __dirname = path.dirname(__filename);
1✔
8
const libDir = path.resolve(__dirname, '../lib');
1✔
9
const markerFile = path.resolve(__dirname, '../.last_build');
1✔
10
const distDir = path.resolve(__dirname, '../dist');
1✔
11
const extraFiles = [
1✔
12
  path.resolve(__dirname, '../test/utils.cjs'),
1✔
13
  path.resolve(__dirname, '../tsup.config.js'),
1✔
14
  path.resolve(__dirname, '../test/dual_mode.spec.ts'),
1✔
15
  __filename
1✔
16
];
1✔
17

1✔
NEW
18
function getAllFiles(dir) {
×
NEW
19
  let results = [];
×
NEW
20
  const list = fs.readdirSync(dir);
×
NEW
21
  list.forEach(file => {
×
NEW
22
    const filePath = path.join(dir, file);
×
NEW
23
    const stat = fs.statSync(filePath);
×
NEW
24
    if (stat && stat.isDirectory()) {
×
NEW
25
      results = results.concat(getAllFiles(filePath));
×
NEW
26
    } else {
×
NEW
27
      results.push(filePath);
×
NEW
28
    }
×
NEW
29
  });
×
NEW
30
  return results;
×
NEW
31
}
×
32

1✔
33
function needsBuild() {
1✔
34
  if (!fs.existsSync(markerFile)) return true;
1✔
NEW
35
  if (!fs.existsSync(distDir)) return true;
×
NEW
36
  const markerMtime = fs.statSync(markerFile).mtimeMs;
×
NEW
37
  const files = getAllFiles(libDir).concat(extraFiles);
×
NEW
38
  return files.some(f => fs.existsSync(f) && fs.statSync(f).mtimeMs > markerMtime);
×
39
}
1✔
40

1✔
41
function isTestEnv() {
1✔
42
  return (
1✔
43
    process.env.MOCHA || process.argv.some(arg => arg.includes('mocha')) || process.env.JEST_WORKER_ID !== undefined
1✔
44
  );
1✔
45
}
1✔
46

1✔
47
// If the marker file is older than the source files, rebuild
1✔
48
if (needsBuild() && !isTestEnv()) {
1✔
49
  console.log('Building hexo-util...');
1✔
50
  const result = spawnSync('npm', ['run', 'build'], {
1✔
51
    stdio: 'inherit',
1✔
52
    cwd: process.cwd(),
1✔
53
    shell: true
1✔
54
  });
1✔
55
  if (result.status !== 0) {
1!
NEW
56
    throw new Error(`Build failed with exit code ${result.status || 1}`);
×
NEW
57
  }
×
58
  fs.writeFileSync(markerFile, String(Date.now()));
1✔
59
}
1✔
60

1✔
61
// Convert extension to .cjs for CommonJS files
1✔
62

1✔
63
/**
1✔
64
 * Recursively updates all import/require extensions from .js to .cjs, then copies .js files to .cjs in the specified directory and its subdirectories.
1✔
65
 * > while running CJS under ESM environment, we need to ensure that all CommonJS files use .cjs extension to avoid conflicts.
1✔
66
 * @param {string} [dir] - The directory to start from. Defaults to '../dist/cjs' relative to this file.
1✔
67
 */
1✔
68
function convertCjs(dir) {
1✔
69
  if (!dir) {
1✔
70
    dir = path.join(__dirname, '../dist/cjs');
1✔
71
  }
1✔
72
  if (!fs.existsSync(dir)) return;
1✔
NEW
73
  fs.readdirSync(dir).forEach(file => {
×
NEW
74
    const filePath = path.join(dir, file);
×
NEW
75
    const stat = fs.statSync(filePath);
×
NEW
76
    if (stat.isDirectory()) {
×
NEW
77
      convertCjs(filePath);
×
NEW
78
    } else if (file.endsWith('.js')) {
×
NEW
79
      // Update import/require extensions in the file
×
NEW
80
      let content = fs.readFileSync(filePath, 'utf8');
×
NEW
81
      // Replace only local require/import paths ending with .js to .cjs, but not directory imports (e.g. './dir')
×
NEW
82
      // require('./foo.js') or require('../foo.js'), but NOT require('./dir')
×
NEW
83
      content = content.replace(/(require\(["'`].*?)([^/]+)\.js(["'`]\))/g, (match, p1, filename, p3) => {
×
NEW
84
        // Only replace if path is local and not a directory import
×
NEW
85
        return /require\(["'`](\.|\.\.)\//.test(match) ? p1 + filename + '.cjs' + p3 : match;
×
NEW
86
      });
×
NEW
87
      // import ... from './foo.js' or '../foo.js', but NOT from './dir'
×
NEW
88
      content = content.replace(/(from\s+["'`].*?)([^/]+)\.js(["'`])/g, (match, p1, filename, p3) => {
×
NEW
89
        return /from\s+["'`](\.|\.\.)\//.test(match) ? p1 + filename + '.cjs' + p3 : match;
×
NEW
90
      });
×
NEW
91
      // dynamic import('./foo.js') or import('../foo.js'), but NOT import('./dir')
×
NEW
92
      content = content.replace(/(import\s*\(["'`].*?)([^/]+)\.js(["'`]\))/g, (match, p1, filename, p3) => {
×
NEW
93
        return /import\s*\(["'`](\.|\.\.)\//.test(match) ? p1 + filename + '.cjs' + p3 : match;
×
NEW
94
      });
×
NEW
95
      fs.writeFileSync(filePath, content, 'utf8');
×
NEW
96
      // Copy the file with .cjs extension instead of renaming
×
NEW
97
      const newPath = filePath.replace(/\.js$/, '.cjs');
×
NEW
98
      fs.copyFileSync(filePath, newPath);
×
NEW
99
      // console.log(`Updated imports and copied: ${filePath} -> ${newPath}`);
×
NEW
100
    }
×
NEW
101
  });
×
102
}
1✔
103

1✔
104
convertCjs(); // Ensure CJS files are renamed before running tests
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc