Migrate codebase from CommonJS to ES Modules

- Convert all require()/module.exports to import/export across 260+ files
- Add "type": "module" to package.json to enable ESM by default
- Add migrations/package.json with "type": "commonjs" to keep db-migrate compatible
- Convert eslint.config.js to ESM with sourceType: "module"
- Replace __dirname/__filename with import.meta.dirname/import.meta.filename
- Replace require.main === module with process.argv[1] === import.meta.filename
- Remove 'use strict' directives (implicit in ESM)
- Convert dynamic require() in switch statements to static import lookup maps
  (dns.js, domains.js, backupformats.js, backupsites.js, network.js)
- Extract self-referencing exports.CONSTANT patterns into standalone const
  declarations (apps.js, services.js, locks.js, users.js, mail.js, etc.)
- Lazify SERVICES object in services.js to avoid circular dependency TDZ issues
- Add clearMailQueue() to mailer.js for ESM-safe queue clearing in tests
- Add _setMockApp() to ldapserver.js for ESM-safe test mocking
- Add _setMockResolve() wrapper to dig.js for ESM-safe DNS mocking in tests
- Convert backupupload.js to use dynamic imports so --check exits before
  loading the module graph (which requires BOX_ENV)
- Update check-install to use ESM import for infra_version.js
- Convert scripts/ (hotfix, release, remote_hotfix.js, find-unused-translations)
- All 1315 tests passing

Migration stats (AI-assisted using Cursor with Claude):
- Wall clock time: ~3-4 hours
- Assistant completions: ~80-100
- Estimated token usage: ~1-2M tokens

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Girish Ramakrishnan
2026-02-14 09:53:14 +01:00
parent e0e9f14a5e
commit 96dc79cfe6
277 changed files with 4789 additions and 3811 deletions
+55 -65
View File
@@ -1,12 +1,48 @@
'use strict';
import * as appPasswords from './apppasswords.js';
import assert from 'node:assert';
import BoxError from './boxerror.js';
import crypto from 'node:crypto';
import constants from './constants.js';
import * as dashboard from './dashboard.js';
import * as database from './database.js';
import debugModule from 'debug';
import eventlog from './eventlog.js';
import * as externalLdap from './externalldap.js';
import hat from './hat.js';
import * as mail from './mail.js';
import * as mailer from './mailer.js';
import mysql from 'mysql2';
import * as notifications from './notifications.js';
import oidcClients from './oidcclients.js';
import * as passkeys from './passkeys.js';
import qrcode from 'qrcode';
import safe from 'safetydance';
import * as settings from './settings.js';
import speakeasy from 'speakeasy';
import * as tokens from './tokens.js';
import * as translations from './translations.js';
import { UAParser as uaParser } from 'ua-parser-js';
import * as userDirectory from './user-directory.js';
import superagent from '@cloudron/superagent';
import util from 'node:util';
import * as validator from './validator.js';
import * as _ from './underscore.js';
exports = module.exports = {
const debug = debugModule('box:user');
const AP_MAIL = 'mail';
const AP_WEBADMIN = 'webadmin';
const ROLE_ADMIN = 'admin';
const ROLE_USER = 'user';
const ROLE_USER_MANAGER = 'usermanager';
const ROLE_MAIL_MANAGER = 'mailmanager';
const ROLE_OWNER = 'owner';
export {
removePrivateFields,
add,
createOwner,
isActivated,
list,
listPaged,
get,
@@ -17,87 +53,41 @@ exports = module.exports = {
getOwner,
getAdmins,
getSuperadmins,
verifyWithId,
verifyWithUsername,
verifyWithEmail,
setPassword,
setGhost,
updateProfile,
update,
del,
setTwoFactorAuthenticationSecret,
enableTwoFactorAuthentication,
disableTwoFactorAuthentication,
sendPasswordResetByIdentifier,
getPasswordResetLink,
sendPasswordResetEmail,
getInviteLink,
sendInviteEmail,
notifyLoginLocation,
setupAccount,
setAvatar,
getAvatar,
getBackgroundImage,
setBackgroundImage,
setNotificationConfig,
resetSources,
parseDisplayName,
AP_MAIL: 'mail',
AP_WEBADMIN: 'webadmin',
ROLE_ADMIN: 'admin',
ROLE_USER: 'user',
ROLE_USER_MANAGER: 'usermanager',
ROLE_MAIL_MANAGER: 'mailmanager',
ROLE_OWNER: 'owner',
AP_MAIL,
AP_WEBADMIN,
ROLE_ADMIN,
ROLE_USER,
ROLE_USER_MANAGER,
ROLE_MAIL_MANAGER,
ROLE_OWNER,
compareRoles,
};
const appPasswords = require('./apppasswords.js'),
assert = require('node:assert'),
BoxError = require('./boxerror.js'),
crypto = require('node:crypto'),
constants = require('./constants.js'),
dashboard = require('./dashboard.js'),
database = require('./database.js'),
debug = require('debug')('box:user'),
eventlog = require('./eventlog.js'),
externalLdap = require('./externalldap.js'),
hat = require('./hat.js'),
mail = require('./mail.js'),
mailer = require('./mailer.js'),
mysql = require('mysql2'),
notifications = require('./notifications'),
oidcClients = require('./oidcclients.js'),
passkeys = require('./passkeys.js'),
qrcode = require('qrcode'),
safe = require('safetydance'),
settings = require('./settings.js'),
speakeasy = require('speakeasy'),
tokens = require('./tokens.js'),
translations = require('./translations.js'),
uaParser = require('ua-parser-js'),
userDirectory = require('./user-directory.js'),
superagent = require('@cloudron/superagent'),
util = require('node:util'),
validator = require('./validator.js'),
_ = require('./underscore.js');
// the avatar and backgroundImage fields are special and not added here to reduce response sizes
const USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'creationTime', 'inviteToken', 'resetToken', 'displayName', 'language',
'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role', 'resetTokenCreationTime', 'loginLocationsJson', 'notificationConfigJson',
@@ -202,7 +192,7 @@ function removePrivateFields(user) {
function validateRole(role) {
assert.strictEqual(typeof role, 'string');
const ORDERED_ROLES = [ exports.ROLE_USER, exports.ROLE_USER_MANAGER, exports.ROLE_MAIL_MANAGER, exports.ROLE_ADMIN, exports.ROLE_OWNER ];
const ORDERED_ROLES = [ ROLE_USER, ROLE_USER_MANAGER, ROLE_MAIL_MANAGER, ROLE_ADMIN, ROLE_OWNER ];
if (ORDERED_ROLES.includes(role)) return null;
@@ -213,7 +203,7 @@ function compareRoles(role1, role2) {
assert.strictEqual(typeof role1, 'string');
assert.strictEqual(typeof role2, 'string');
const ORDERED_ROLES = [ exports.ROLE_USER, exports.ROLE_USER_MANAGER, exports.ROLE_MAIL_MANAGER, exports.ROLE_ADMIN, exports.ROLE_OWNER ];
const ORDERED_ROLES = [ ROLE_USER, ROLE_USER_MANAGER, ROLE_MAIL_MANAGER, ROLE_ADMIN, ROLE_OWNER ];
const roleInt1 = ORDERED_ROLES.indexOf(role1);
const roleInt2 = ORDERED_ROLES.indexOf(role2);
@@ -235,7 +225,7 @@ async function add(email, data, auditSource) {
let { username, password } = data;
let fallbackEmail = data.fallbackEmail || '';
const source = data.source || ''; // empty is local user
const role = data.role || exports.ROLE_USER;
const role = data.role || ROLE_USER;
const notificationConfig = 'notificationConfig' in data ? data.notificationConfig : null;
let error;
@@ -547,20 +537,20 @@ async function listByRole(role) {
}
async function getOwner() {
const owners = await listByRole(exports.ROLE_OWNER);
const owners = await listByRole(ROLE_OWNER);
if (owners.length === 0) return null;
return owners[0];
}
async function getAdmins() {
const owners = await listByRole(exports.ROLE_OWNER);
const admins = await listByRole(exports.ROLE_ADMIN);
const owners = await listByRole(ROLE_OWNER);
const admins = await listByRole(ROLE_ADMIN);
return owners.concat(admins);
}
async function getSuperadmins() {
return await listByRole(exports.ROLE_OWNER);
return await listByRole(ROLE_OWNER);
}
async function setGhost(user, password, expiresAt) {
@@ -875,7 +865,7 @@ async function createOwner(email, username, password, displayName, auditSource)
if (activated) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cloudron already activated');
const notificationConfig = [notifications.TYPE_BACKUP_FAILED, notifications.TYPE_CERTIFICATE_RENEWAL_FAILED, notifications.TYPE_MANUAL_APP_UPDATE_NEEDED, notifications.TYPE_APP_DOWN, notifications.TYPE_CLOUDRON_UPDATE_FAILED ];
return await add(email, { username, password, fallbackEmail: '', displayName, role: exports.ROLE_OWNER, notificationConfig }, auditSource);
return await add(email, { username, password, fallbackEmail: '', displayName, role: ROLE_OWNER, notificationConfig }, auditSource);
}
async function getInviteLink(user, auditSource) {