Files
Lauri Ojansivu 15877dd1bc Source server: a local package fork that a prebuilt package implies is loaded first.
ecmascript implies the local dynamic-import fork, so compiling ddp-server,
listed before it in .meteor/versions, stopped the source-loaded server
with "isopack dynamic-import not yet loaded". The loader now loads the
local package asked for and retries. The source server still stops later,
on "Cannot find module 'meteor/accounts-base'"; the bundle server is what
the browser tests ran against.

Thanks to xet7 !
2026-11-02 00:24:55 +02:00

264 lines
11 KiB
JavaScript

const fs = require('fs'),
path = require('path'),
Module = require('module');
const root = process.cwd();
const tool = process.env.WEKAN_SOURCE_TOOL;
const state = path.join(root, '.tools/dev-source', process.env.PORT);
fs.mkdirSync(state, {
recursive: true
});
process.env.WEKAN_SOURCE_STATE = state;
// Cache installed/local Meteor package transforms, never application bundles.
process.env.BABEL_CACHE_DIR = path.join(state, 'package-cache');
fs.mkdirSync(process.env.BABEL_CACHE_DIR, { recursive: true });
require(path.join(tool, 'tools/tool-env/install-babel.js'));
const { convertToStandardPath: standardPath } = require(path.join(tool, 'tools/static-assets/server/mini-files'));
const {
LocalCatalog
} = require(path.join(tool, 'tools/packaging/catalog/catalog-local'));
const bp = path.join(tool, 'tools/isobuild/bundler.js');
const mod = new Module(bp, module);
mod.filename = bp;
mod.paths = Module._nodeModulePaths(path.dirname(bp));
require.cache[bp] = mod;
// Meteor does not export its in-memory targets. Adapt this one export in memory;
// the installed distribution and all application source files remain untouched.
const bundlerSource = fs.readFileSync(bp, 'utf8');
const exportMarker = 'exports.NodeModulesDirectory = NodeModulesDirectory;';
if (!bundlerSource.includes(exportMarker)) throw Error('This Meteor linker is unsupported by Dev server nobuild; update the source-loader adapter.');
mod._compile(bundlerSource.replace(exportMarker, exportMarker + ' exports.SourceClient = ClientTarget; exports.SourceServer = JsImageTarget;'), bp);
mod.loaded = true;
const toolMain = new Module(path.join(tool, 'tools/index.js'));
toolMain.filename = path.join(tool, 'tools/index.js');
toolMain.paths = Module._nodeModulePaths(path.join(tool, 'tools'));
process.mainModule = toolMain;
const bm = require(path.join(tool, 'tools/utils/buildmessage'));
const {
PackageMap
} = require(path.join(tool, 'tools/packaging/package-map'));
const {
IsopackCache
} = require(path.join(tool, 'tools/isobuild/isopack-cache'));
const {
Isopack
} = require(path.join(tool, 'tools/isobuild/isopack'));
const versions = Object.fromEntries(fs.readFileSync('.meteor/versions', 'utf8').trim().split('\n').map(l => l.split('@')));
const packagePath = (n, v) => path.resolve(process.env.METEOR_WAREHOUSE_DIR, 'packages', n.replace(/:/g, '_'), v);
require(path.join(tool, "tools/utils/fiber-helpers")).makeGlobalAsyncLocalStorage().run({}, async () => {
await require(path.join(tool, 'tools/tool-env/isopackets')).ensureIsopacketsLoadable();
const {
MongoRunner
} = require(path.join(tool, 'tools/runners/run-mongo'));
const mongoPort = Number(process.env.WEKAN_SOURCE_MONGO_PORT || Number(process.env.PORT) + 1);
if (!Number.isInteger(mongoPort) || mongoPort < 1 || mongoPort > 65535 || mongoPort === Number(process.env.PORT)) throw Error('Invalid development MongoDB port');
const mongo = new MongoRunner({
projectLocalDir: standardPath(state),
port: mongoPort,
onFailure: () => {
throw Error('Development MongoDB failed to start');
}
});
// Never terminate another development database to acquire its port.
mongo.firstStart = false;
let closing = false;
const stop = async code => {
if (closing) return;
closing = true;
mongo.stop();
if (global.Package?.webapp?.WebApp?.httpServer) Package.webapp.WebApp.httpServer.close();
const net = require('node:net');
const deadline = Date.now() + 15000;
while (Date.now() < deadline) {
const open = await new Promise(resolve => {
const socket = net.connect({
host: '127.0.0.1',
port: mongo.port
});
socket.on('connect', () => {
socket.destroy();
resolve(true);
});
socket.on('error', () => resolve(false));
socket.setTimeout(500, () => {
socket.destroy();
resolve(false);
});
});
if (!open) break;
await new Promise(resolve => setTimeout(resolve, 100));
}
process.exit(code);
};
process.on('SIGINT', () => stop(130));
process.on('SIGTERM', () => stop(143));
process.on('exit', () => mongo.stop());
await mongo.start();
if (!mongo.handle) throw Error('Development MongoDB did not become ready');
if (process.send) process.send({ type: 'source-mongo-started', port: mongoPort });
process.env.MONGO_URL = mongo.mongoUrl().replace(/meteor$/, process.env.WEKAN_SOURCE_MONGO_DATABASE === 'wekan' ? 'wekan' : 'meteor');
process.env.MONGO_OPLOG_URL = mongo.oplogUrl();
console.log(`Source server: ${process.env.ROOT_URL}; bundled development MongoDB: ${process.env.MONGO_URL}`);
const messages = await require(path.join(tool, "tools/utils/fiber-helpers")).makeGlobalAsyncLocalStorage().run({}, () => bm.capture(async () => {
await require(path.join(tool, 'tools/packaging/catalog/catalog')).official.initialize({
offline: true
});
const localCatalog = new LocalCatalog();
await localCatalog.initialize({
localPackageSearchDirs: [standardPath(path.join(root, 'packages'))]
});
const packageMap = new PackageMap(versions, {
localCatalog
});
const cache = new IsopackCache({
packageMap,
tropohouse: {
packagePath: (name, version) => standardPath(packagePath(name, version))
}
});
for (const [name, version] of Object.entries(versions)) {
let dir = packagePath(name, version);
if (localCatalog.getPackageSource(name)) continue;
if (!fs.existsSync(dir)) throw Error(`missing ${name}`);
const iso = new Isopack();
await iso.initFromPath(name, standardPath(dir));
cache._isopacks[name] = iso;
}
// A prebuilt package can imply one of WeKan's local package forks -
// ecmascript implies the local dynamic-import - so compiling an earlier
// local package (ddp-server) can ask for a local one .meteor/versions lists
// after it: "isopack dynamic-import not yet loaded". Load that one first,
// then retry.
const loadLocal = async (name, asked = new Set()) => {
for (;;) {
try {
return await cache._ensurePackageLoaded(name, { [name]: true });
} catch (error) {
const missing = /isopack (\S+) not yet loaded/.exec(error && error.message);
if (!missing || asked.has(missing[1]) || !localCatalog.getPackageSource(missing[1])) throw error;
asked.add(missing[1]);
await loadLocal(missing[1], asked);
}
}
};
for (const name of Object.keys(versions)) if (localCatalog.getPackageSource(name)) await loadLocal(name);
const use = fs.readFileSync('.meteor/packages', 'utf8').split('\n').map(x => x.split('#')[0].trim().split('@')[0]).filter(x => x && !['rspack', 'standard-minifier-css', 'standard-minifier-js', 'meteortesting:mocha', 'hot-module-replacement'].includes(x));
const t = new mod.exports.SourceServer({
packageMap,
isopackCache: cache,
sourceRoot: standardPath(root),
arch: require(path.join(tool, 'tools/utils/archinfo')).host(),
buildMode: 'development'
});
console.log('Linking packages', use.length);
await t.make({
packages: use,
minifyMode: 'development'
});
const browser = require('./browser.cjs');
const client = new mod.exports.SourceClient({
packageMap,
isopackCache: cache,
sourceRoot: standardPath(root),
arch: 'web.browser',
buildMode: 'development'
});
await client.make({
packages: use,
minifyMode: 'development',
minifiers: []
});
await browser.prepare(client);
const image = t.toJsImage();
console.log('Linked', image.jsToLoad.length);
global.__meteor_bootstrap__ = {
startupHooks: [],
serverDir: path.join(state, 'server'),
configJson: {
clientPaths: {
'web.browser': '../web.browser'
}
},
isFibersDisabled: true
};
global.__meteor_runtime_config__ = {
meteorRelease: fs.readFileSync('.meteor/release', 'utf8').trim(),
ROOT_URL: process.env.ROOT_URL,
ROOT_URL_PATH_PREFIX: new URL(process.env.ROOT_URL).pathname.replace(/\/+$/, '')
};
global.Package = await image.load({
__meteor_bootstrap__,
__meteor_runtime_config__,
dynamicImportInfo: {
server: {
dynamicRoot: path.join(state, 'dynamic')
}
}
});
for (const [name, exports] of Object.entries(Package)) {
if (name.startsWith('_')) continue;
for (const [key, value] of Object.entries(exports)) if (key !== 'global' && key !== 'main' && !(key in global)) global[key] = value;
const id = path.join(state, 'meteor', name + '.js');
require.cache[id] = {
id,
filename: id,
loaded: true,
exports
};
}
const resolve = Module._resolveFilename;
Module._resolveFilename = function (id, parent, ...args) {
if (id.startsWith('meteor/') && Package[id.slice(7)]) return path.join(state, 'meteor', id.slice(7) + '.js');
if (id.startsWith('/') && !id.startsWith(root + '/') && (fs.existsSync(root + id) || fs.existsSync(root + id + '.js'))) id = root + id;
return resolve.call(this, id, parent, ...args);
};
const {
transform
} = require('./transform.cjs');
const js = Module._extensions['.js'];
Module._extensions['.js'] = function (m, file) {
const rel = path.relative(root, file);
if (/^(imports|models|server|client|config)[/\\]/.test(rel) || rel === 'sandstorm.js') {
const source = fs.readFileSync(file, 'utf8');
return m._compile(transform(source, file, 'inline'), file);
}
return js(m, file);
};
const privatePath = file => {
const base = path.join(root, 'private');
const result = path.resolve(base, file);
if (!result.startsWith(base + path.sep)) throw Error('Invalid private asset path');
return result;
};
global.Assets = {
getTextAsync: async file => fs.promises.readFile(privatePath(file), 'utf8'),
getBinaryAsync: async file => fs.promises.readFile(privatePath(file)),
absoluteFilePath: file => privatePath(file)
};
global.Npm = {
require: Module.createRequire(path.join(root, 'package.json'))
};
console.log('Loading source server');
require(path.join(root, 'server/main.js'));
Package.webapp.WebApp.rawConnectHandlers.use(browser.middleware);
Package.webapp.WebApp.rawConnectHandlers.use(__meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '/', Package.webapp.WebApp.express.static(path.join(root, 'public')));
console.log('Running startup hooks');
while (__meteor_bootstrap__.startupHooks.length) await __meteor_bootstrap__.startupHooks.shift()();
__meteor_bootstrap__.startupHooks = null;
await Package.webapp.main([]);
console.log('Ready');
if (process.env.WEKAN_SOURCE_VISUALIZE === '1') console.log('Source module visualizer: ' + process.env.ROOT_URL.replace(/\/+$/, '') + '/__source/visualizer');
if (process.env.WEKAN_SOURCE_TESTS === '1') stop(await require('./server-tests.cjs')({
root,
packagePath,
versions
}));
}));
if (messages.hasMessages()) {
console.error(messages.formatMessages());
stop(1);
}
}).catch(e => {
console.error(e);
process.exit(1);
});