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:
@@ -1,55 +1,65 @@
|
||||
'use strict';
|
||||
import assert from 'node:assert';
|
||||
import AuditSource from './auditsource.js';
|
||||
import BoxError from './boxerror.js';
|
||||
import * as changelog from './changelog.js';
|
||||
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 mailer from './mailer.js';
|
||||
import safe from 'safetydance';
|
||||
import * as users from './users.js';
|
||||
|
||||
exports = module.exports = {
|
||||
const debug = debugModule('box:notifications');
|
||||
|
||||
const TYPE_CLOUDRON_INSTALLED = 'cloudronInstalled';
|
||||
const TYPE_CLOUDRON_UPDATED = 'cloudronUpdated';
|
||||
const TYPE_CLOUDRON_UPDATE_FAILED = 'cloudronUpdateFailed';
|
||||
const TYPE_CERTIFICATE_RENEWAL_FAILED = 'certificateRenewalFailed';
|
||||
const TYPE_BACKUP_CONFIG = 'backupConfig';
|
||||
const TYPE_APP_OOM = 'appOutOfMemory';
|
||||
const TYPE_APP_UPDATED = 'appUpdated';
|
||||
const TYPE_BACKUP_FAILED = 'backupFailed';
|
||||
const TYPE_APP_DOWN = 'appDown';
|
||||
const TYPE_APP_UP = 'appUp';
|
||||
const TYPE_DISK_SPACE = 'diskSpace';
|
||||
const TYPE_MAIL_STATUS = 'mailStatus';
|
||||
const TYPE_REBOOT = 'reboot';
|
||||
const TYPE_UPDATE_UBUNTU = 'ubuntuUpdate';
|
||||
const TYPE_BOX_UPDATE = 'boxUpdate';
|
||||
const TYPE_MANUAL_APP_UPDATE_NEEDED = 'manualAppUpdate';
|
||||
const TYPE_DOMAIN_CONFIG_CHECK_FAILED = 'domainConfigCheckFailed';
|
||||
const _add = add;
|
||||
|
||||
export {
|
||||
get,
|
||||
update,
|
||||
list,
|
||||
del,
|
||||
|
||||
onEvent,
|
||||
|
||||
// these are notification types, keep in sync with client.js
|
||||
TYPE_CLOUDRON_INSTALLED: 'cloudronInstalled',
|
||||
TYPE_CLOUDRON_UPDATED: 'cloudronUpdated',
|
||||
TYPE_CLOUDRON_UPDATE_FAILED: 'cloudronUpdateFailed',
|
||||
TYPE_CERTIFICATE_RENEWAL_FAILED: 'certificateRenewalFailed',
|
||||
TYPE_BACKUP_CONFIG: 'backupConfig',
|
||||
TYPE_APP_OOM: 'appOutOfMemory',
|
||||
TYPE_APP_UPDATED: 'appUpdated',
|
||||
TYPE_BACKUP_FAILED: 'backupFailed',
|
||||
TYPE_APP_DOWN: 'appDown',
|
||||
TYPE_APP_UP: 'appUp',
|
||||
|
||||
// these are singleton types allowed in pin() and unpin()
|
||||
TYPE_DISK_SPACE: 'diskSpace',
|
||||
TYPE_MAIL_STATUS: 'mailStatus',
|
||||
TYPE_REBOOT: 'reboot',
|
||||
TYPE_UPDATE_UBUNTU: 'ubuntuUpdate',
|
||||
TYPE_BOX_UPDATE: 'boxUpdate',
|
||||
TYPE_MANUAL_APP_UPDATE_NEEDED: 'manualAppUpdate',
|
||||
TYPE_DOMAIN_CONFIG_CHECK_FAILED: 'domainConfigCheckFailed',
|
||||
|
||||
// these work off singleton types
|
||||
TYPE_CLOUDRON_INSTALLED,
|
||||
TYPE_CLOUDRON_UPDATED,
|
||||
TYPE_CLOUDRON_UPDATE_FAILED,
|
||||
TYPE_CERTIFICATE_RENEWAL_FAILED,
|
||||
TYPE_BACKUP_CONFIG,
|
||||
TYPE_APP_OOM,
|
||||
TYPE_APP_UPDATED,
|
||||
TYPE_BACKUP_FAILED,
|
||||
TYPE_APP_DOWN,
|
||||
TYPE_APP_UP,
|
||||
TYPE_DISK_SPACE,
|
||||
TYPE_MAIL_STATUS,
|
||||
TYPE_REBOOT,
|
||||
TYPE_UPDATE_UBUNTU,
|
||||
TYPE_BOX_UPDATE,
|
||||
TYPE_MANUAL_APP_UPDATE_NEEDED,
|
||||
TYPE_DOMAIN_CONFIG_CHECK_FAILED,
|
||||
pin,
|
||||
unpin,
|
||||
|
||||
// exported for testing
|
||||
_add: add
|
||||
_add,
|
||||
};
|
||||
|
||||
const assert = require('node:assert'),
|
||||
AuditSource = require('./auditsource.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
changelog = require('./changelog.js'),
|
||||
constants = require('./constants.js'),
|
||||
dashboard = require('./dashboard.js'),
|
||||
database = require('./database.js'),
|
||||
debug = require('debug')('box:notifications'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
safe = require('safetydance'),
|
||||
users = require('./users.js');
|
||||
|
||||
const NOTIFICATION_FIELDS = [ 'id', 'eventId', 'type', 'title', 'message', 'creationTime', 'acknowledged', 'context' ];
|
||||
|
||||
function postProcess(result) {
|
||||
@@ -163,11 +173,11 @@ async function oomEvent(eventId, containerId, app, addonName, event) {
|
||||
message = `The app has been restarted automatically. If you see this notification often, consider increasing the [memory limit](https://${dashboardFqdn}/#/app/${app.id}/resources)`;
|
||||
}
|
||||
|
||||
await add(exports.TYPE_APP_OOM, title, message, { eventId });
|
||||
await add(TYPE_APP_OOM, title, message, { eventId });
|
||||
|
||||
const admins = await users.getAdmins();
|
||||
for (const admin of admins) {
|
||||
if (admin.notificationConfig.includes(exports.TYPE_APP_OOM)) {
|
||||
if (admin.notificationConfig.includes(TYPE_APP_OOM)) {
|
||||
await safe(mailer.oomEvent(admin.email, containerId, app, addonName, event), { debug });
|
||||
}
|
||||
}
|
||||
@@ -179,7 +189,7 @@ async function appUp(eventId, app) {
|
||||
|
||||
const admins = await users.getAdmins();
|
||||
for (const admin of admins) {
|
||||
if (admin.notificationConfig.includes(exports.TYPE_APP_UP)) {
|
||||
if (admin.notificationConfig.includes(TYPE_APP_UP)) {
|
||||
await safe(mailer.appUp(admin.email, app), { debug });
|
||||
}
|
||||
}
|
||||
@@ -191,7 +201,7 @@ async function appDown(eventId, app) {
|
||||
|
||||
const admins = await users.getAdmins();
|
||||
for (const admin of admins) {
|
||||
if (admin.notificationConfig.includes(exports.TYPE_APP_DOWN)) {
|
||||
if (admin.notificationConfig.includes(TYPE_APP_DOWN)) {
|
||||
await safe(mailer.appDown(admin.email, app), { debug });
|
||||
}
|
||||
}
|
||||
@@ -210,7 +220,7 @@ async function appUpdated(eventId, app, fromManifest, toManifest) {
|
||||
const title = upstreamVersion ? `${toManifest.title} at ${app.fqdn} updated to ${upstreamVersion} (package version ${toManifest.version})`
|
||||
: `${toManifest.title} at ${app.fqdn} updated to package version ${toManifest.version}`;
|
||||
|
||||
await add(exports.TYPE_APP_UPDATED, title, `The application installed at https://${app.fqdn} was updated.\n\nChangelog:\n${toManifest.changelog}\n`, { eventId });
|
||||
await add(TYPE_APP_UPDATED, title, `The application installed at https://${app.fqdn} was updated.\n\nChangelog:\n${toManifest.changelog}\n`, { eventId });
|
||||
}
|
||||
|
||||
async function boxUpdated(eventId, oldVersion, newVersion) {
|
||||
@@ -221,7 +231,7 @@ async function boxUpdated(eventId, oldVersion, newVersion) {
|
||||
const changes = changelog.getChanges(newVersion);
|
||||
const changelogMarkdown = changes.map((m) => `* ${m}\n`).join('');
|
||||
|
||||
await add(exports.TYPE_CLOUDRON_UPDATED, `Cloudron updated to v${newVersion}`, `Cloudron was updated from v${oldVersion} to v${newVersion}.\n\nChangelog:\n${changelogMarkdown}\n`, { eventId });
|
||||
await add(TYPE_CLOUDRON_UPDATED, `Cloudron updated to v${newVersion}`, `Cloudron was updated from v${oldVersion} to v${newVersion}.\n\nChangelog:\n${changelogMarkdown}\n`, { eventId });
|
||||
}
|
||||
|
||||
async function boxInstalled(eventId, version) {
|
||||
@@ -231,18 +241,18 @@ async function boxInstalled(eventId, version) {
|
||||
const changes = changelog.getChanges(version.replace(/\.([^.]*)$/, '.0')); // last .0 release
|
||||
const changelogMarkdown = changes.map((m) => `* ${m}\n`).join('');
|
||||
|
||||
await add(exports.TYPE_CLOUDRON_INSTALLED, `Cloudron v${version} installed`, `Cloudron v${version} was installed.\n\nChangelog:\n${changelogMarkdown}\n`, { eventId });
|
||||
await add(TYPE_CLOUDRON_INSTALLED, `Cloudron v${version} installed`, `Cloudron v${version} was installed.\n\nChangelog:\n${changelogMarkdown}\n`, { eventId });
|
||||
}
|
||||
|
||||
async function boxUpdateError(eventId, errorMessage) {
|
||||
assert.strictEqual(typeof eventId, 'string');
|
||||
assert.strictEqual(typeof errorMessage, 'string');
|
||||
|
||||
await add(exports.TYPE_CLOUDRON_UPDATE_FAILED, 'Cloudron update failed', `Failed to update Cloudron: ${errorMessage}.`, { eventId });
|
||||
await add(TYPE_CLOUDRON_UPDATE_FAILED, 'Cloudron update failed', `Failed to update Cloudron: ${errorMessage}.`, { eventId });
|
||||
|
||||
const admins = await users.getAdmins();
|
||||
for (const admin of admins) {
|
||||
if (admin.notificationConfig.includes(exports.TYPE_CLOUDRON_UPDATE_FAILED)) {
|
||||
if (admin.notificationConfig.includes(TYPE_CLOUDRON_UPDATE_FAILED)) {
|
||||
await safe(mailer.boxUpdateError(admin.email, errorMessage), { debug });
|
||||
}
|
||||
}
|
||||
@@ -253,11 +263,11 @@ async function certificateRenewalError(eventId, fqdn, errorMessage) {
|
||||
assert.strictEqual(typeof fqdn, 'string');
|
||||
assert.strictEqual(typeof errorMessage, 'string');
|
||||
|
||||
await add(exports.TYPE_CERTIFICATE_RENEWAL_FAILED, `Certificate renewal of ${fqdn} failed`, `Failed to renew certs of ${fqdn}: ${errorMessage}. Renewal will be retried in 12 hours.`, { eventId });
|
||||
await add(TYPE_CERTIFICATE_RENEWAL_FAILED, `Certificate renewal of ${fqdn} failed`, `Failed to renew certs of ${fqdn}: ${errorMessage}. Renewal will be retried in 12 hours.`, { eventId });
|
||||
|
||||
const admins = await users.getAdmins();
|
||||
for (const admin of admins) {
|
||||
if (admin.notificationConfig.includes(exports.TYPE_CERTIFICATE_RENEWAL_FAILED)) {
|
||||
if (admin.notificationConfig.includes(TYPE_CERTIFICATE_RENEWAL_FAILED)) {
|
||||
await safe(mailer.certificateRenewalError(admin.email, fqdn, errorMessage), { debug });
|
||||
}
|
||||
}
|
||||
@@ -268,12 +278,12 @@ async function backupFailed(eventId, taskId, errorMessage) {
|
||||
assert.strictEqual(typeof taskId, 'string');
|
||||
assert.strictEqual(typeof errorMessage, 'string');
|
||||
|
||||
await add(exports.TYPE_BACKUP_FAILED, 'Backup failed', `Backup failed: ${errorMessage}. Logs are available [here](/logs.html?taskId=${taskId}).`, { eventId });
|
||||
await add(TYPE_BACKUP_FAILED, 'Backup failed', `Backup failed: ${errorMessage}. Logs are available [here](/logs.html?taskId=${taskId}).`, { eventId });
|
||||
|
||||
const { fqdn:dashboardFqdn } = await dashboard.getLocation();
|
||||
const superadmins = await users.getSuperadmins();
|
||||
for (const superadmin of superadmins) {
|
||||
if (superadmin.notificationConfig.includes(exports.TYPE_BACKUP_FAILED)) {
|
||||
if (superadmin.notificationConfig.includes(TYPE_BACKUP_FAILED)) {
|
||||
await safe(mailer.backupFailed(superadmin.email, errorMessage, `https://${dashboardFqdn}/logs.html?taskId=${taskId}`), { debug });
|
||||
}
|
||||
}
|
||||
@@ -282,7 +292,7 @@ async function backupFailed(eventId, taskId, errorMessage) {
|
||||
async function rebootRequired() {
|
||||
const admins = await users.getAdmins();
|
||||
for (const admin of admins) {
|
||||
if (admin.notificationConfig.includes(exports.TYPE_REBOOT)) {
|
||||
if (admin.notificationConfig.includes(TYPE_REBOOT)) {
|
||||
await safe(mailer.rebootRequired(admin.email), { debug });
|
||||
}
|
||||
}
|
||||
@@ -293,7 +303,7 @@ async function lowDiskSpace(message) {
|
||||
|
||||
const admins = await users.getAdmins();
|
||||
for (const admin of admins) {
|
||||
if (admin.notificationConfig.includes(exports.TYPE_DISK_SPACE)) {
|
||||
if (admin.notificationConfig.includes(TYPE_DISK_SPACE)) {
|
||||
await safe(mailer.lowDiskSpace(admin.email, message), { debug });
|
||||
}
|
||||
}
|
||||
@@ -304,9 +314,9 @@ async function onPin(type, message) {
|
||||
assert.strictEqual(typeof message, 'string');
|
||||
|
||||
switch (type) {
|
||||
case exports.TYPE_REBOOT:
|
||||
case TYPE_REBOOT:
|
||||
return await rebootRequired();
|
||||
case exports.TYPE_DISK_SPACE:
|
||||
case TYPE_DISK_SPACE:
|
||||
return await lowDiskSpace(message);
|
||||
default:
|
||||
break;
|
||||
@@ -326,7 +336,7 @@ async function pin(type, title, message, options) {
|
||||
}
|
||||
|
||||
// do not reset the ack state if user has already seen the update notification
|
||||
const isUpdateType = type === exports.TYPE_BOX_UPDATE || type === exports.TYPE_MANUAL_APP_UPDATE_NEEDED;
|
||||
const isUpdateType = type === TYPE_BOX_UPDATE || type === TYPE_MANUAL_APP_UPDATE_NEEDED;
|
||||
const acknowledged = (isUpdateType && result.message === message) ? result.acknowledged : false;
|
||||
|
||||
if (result.acknowledged && !acknowledged) await onPin(type, message);
|
||||
|
||||
Reference in New Issue
Block a user