feat: productionize learning engine and classroom
This commit is contained in:
72
OpenMAIC/scripts/build-learning-artifact.mjs
Normal file
72
OpenMAIC/scripts/build-learning-artifact.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
import { cp, mkdir, rm, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import process from 'node:process';
|
||||
|
||||
const variant = process.argv[2];
|
||||
if (variant !== 'engine' && variant !== 'ops' && variant !== 'player') {
|
||||
throw new Error('Usage: node scripts/build-learning-artifact.mjs <engine|ops|player>');
|
||||
}
|
||||
|
||||
const root = process.cwd();
|
||||
const role = variant === 'engine' ? 'server' : variant === 'ops' ? 'ops' : 'learner';
|
||||
const basePath = variant === 'ops' ? '/learning-ops' : '';
|
||||
const inheritedNodeOptions = process.env.NODE_OPTIONS?.trim() || '';
|
||||
const env = {
|
||||
...process.env,
|
||||
// The webpack graph for the complete Stage, editor, runtime and Ops surface
|
||||
// exceeds Node's default ~4 GiB heap. Keep CI, local artifacts and Docker
|
||||
// builds on the same explicit budget instead of relying on host defaults.
|
||||
NODE_OPTIONS: `${inheritedNodeOptions} --max-old-space-size=8192`.trim(),
|
||||
OPENMAIC_DEPLOYMENT_ROLE: role,
|
||||
NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE: role,
|
||||
OPENMAIC_BASE_PATH: basePath,
|
||||
};
|
||||
|
||||
function run(script, args = []) {
|
||||
const result = spawnSync(process.execPath, [script, ...args], {
|
||||
cwd: root,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (result.status !== 0) process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
run(path.join(root, 'scripts/assert-vendor-maic-importer.mjs'));
|
||||
// Next 16's default Turbopack build can stall indefinitely on this large
|
||||
// workspace after allocating its graph. The production Docker build and these
|
||||
// reproducible artifacts use the mature webpack path explicitly.
|
||||
run(path.join(root, 'node_modules/next/dist/bin/next'), ['build', '--webpack']);
|
||||
|
||||
if (variant === 'player') {
|
||||
run(path.join(root, 'scripts/build-makelore-player-artifact.mjs'));
|
||||
console.log('Learning player build completed from a fresh learner-role Next output.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const target = path.join(root, 'dist', `learning-${variant}`);
|
||||
await rm(target, { recursive: true, force: true });
|
||||
await mkdir(path.join(target, '.next'), { recursive: true });
|
||||
await cp(path.join(root, '.next', 'standalone'), target, { recursive: true });
|
||||
await cp(path.join(root, '.next', 'static'), path.join(target, '.next', 'static'), {
|
||||
recursive: true,
|
||||
});
|
||||
await cp(path.join(root, 'public'), path.join(target, 'public'), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(target, 'learning-artifact.json'),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
variant,
|
||||
role,
|
||||
basePath,
|
||||
start: 'node server.js',
|
||||
port: 3000,
|
||||
dataDirectory: '/app/data',
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
console.log(`Learning ${variant} standalone artifact: ${target}`);
|
||||
43
OpenMAIC/scripts/build-makelore-player-artifact.mjs
Normal file
43
OpenMAIC/scripts/build-makelore-player-artifact.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const projectRoot = resolve(import.meta.dirname, '..');
|
||||
const nextRoot = resolve(projectRoot, '.next');
|
||||
const outputRoot = resolve(process.argv[2] || resolve(projectRoot, 'dist/makelore-player'));
|
||||
const sourceHtml = resolve(nextRoot, 'server/app/makelore-player.html');
|
||||
const sourceStatic = resolve(nextRoot, 'static');
|
||||
const sourceAvatars = resolve(projectRoot, 'public/avatars');
|
||||
const sourcePublic = resolve(projectRoot, 'public');
|
||||
|
||||
const html = await readFile(sourceHtml, 'utf8').catch(() => {
|
||||
throw new Error('Missing .next player output. Run `pnpm build` first.');
|
||||
});
|
||||
|
||||
await rm(outputRoot, { recursive: true, force: true });
|
||||
await mkdir(resolve(outputRoot, '_next'), { recursive: true });
|
||||
await writeFile(resolve(outputRoot, 'index.html'), html, 'utf8');
|
||||
await cp(sourceStatic, resolve(outputRoot, '_next/static'), { recursive: true });
|
||||
await cp(sourceAvatars, resolve(outputRoot, 'avatars'), { recursive: true });
|
||||
// Makelore serves this tree through a strict local allowlist. Production Stage
|
||||
// and PBL chunks still contain root-relative public references, so omitting the
|
||||
// producer-owned assets creates a player that builds successfully but 404s at
|
||||
// runtime.
|
||||
await cp(sourcePublic, resolve(outputRoot, 'public'), { recursive: true });
|
||||
await writeFile(resolve(outputRoot, 'artifact.json'), JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
entrypoint: 'index.html',
|
||||
source: 'OpenMAIC production Stage',
|
||||
publicDirectory: 'public',
|
||||
publicAssets: [
|
||||
'mailuo-logo.png',
|
||||
'openmaic-mark.png',
|
||||
'logo-horizontal.png',
|
||||
'avatars/',
|
||||
'logos/',
|
||||
'vendor/',
|
||||
],
|
||||
htmlSha256: createHash('sha256').update(html).digest('hex'),
|
||||
}, null, 2));
|
||||
|
||||
process.stdout.write(`Makelore player artifact: ${outputRoot}\n`);
|
||||
Reference in New Issue
Block a user