Files
openmaic/OpenMAIC/scripts/migrate-coursewares.ts
2026-08-16 14:58:47 +08:00

96 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Legacy classroom migration — P4 tooling.
*
* Scans the server classrooms dir (`data/classrooms/*.json`) and re-packages
* every classroom into a published frozen bundle (design doc §7 "旧课件迁移":
* migration = re-packaging, the packager consumes classroom-storage data
* directly).
*
* Byte resolution: media/narration bytes must come from somewhere the server
* can reach. Pass `--resolver <module>` to load a module exporting
* `createResolvers(): Promise<ClassroomByteResolvers>` (e.g. wired to the
* asset registry). Without a resolver, classrooms whose media is only in
* learner browsers are reported as `incomplete` — re-publish those from the
* ops workbench instead (which packages from IndexedDB).
*
* Usage:
* npx tsx scripts/migrate-coursewares.ts --base-url https://ops.example.com
* npx tsx scripts/migrate-coursewares.ts --resolver ./scripts/asset-resolvers.mjs
*
* Exit codes: 0 = all migrated or cleanly reported; 1 = hard error.
*/
import { migrateAllClassrooms, type ClassroomByteResolvers } from '@/lib/courseware-repo/migrate';
import { createLogger } from '@/lib/logger';
const log = createLogger('MigrateCLI');
function parseArgs(argv: string[]): { baseUrl: string; resolver?: string; classroomsDir?: string } {
const args: Record<string, string> = {};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg.startsWith('--')) {
const key = arg.slice(2);
const value = argv[i + 1];
if (value && !value.startsWith('--')) {
args[key] = value;
i++;
} else {
args[key] = 'true';
}
}
}
return {
baseUrl: args['base-url'] ?? 'http://localhost:3000',
resolver: args['resolver'],
classroomsDir: args['classrooms-dir'],
};
}
async function main() {
const { baseUrl, resolver, classroomsDir } = parseArgs(process.argv.slice(2));
let resolvers: ClassroomByteResolvers | undefined;
if (resolver) {
const resolverModule = await import(resolver);
const factory = resolverModule.createResolvers ?? resolverModule.default;
if (typeof factory !== 'function') {
throw new Error(`Resolver module ${resolver} must export createResolvers()`);
}
resolvers = await factory();
log.info('Loaded byte resolvers from', resolver);
} else {
log.warn(
'No resolver: classrooms whose media lives only in learner browsers will be reported incomplete. ' +
'Re-publish those from the ops workbench (export menu → 发布课件).',
);
}
const report = await migrateAllClassrooms({ baseUrl, resolvers, classroomsDir });
console.log('\n=== 迁移报告 (Migration report) ===');
console.log(`已发布 published: ${report.published.length}`);
for (const item of report.published) {
if (item.status !== 'published') continue;
console.log(`${item.classroomId}${item.coursewareId} v${item.version}`);
}
console.log(`未完成 incomplete: ${report.incomplete.length}`);
for (const item of report.incomplete) {
if (item.status !== 'incomplete') continue;
console.log(`${item.classroomId} (${item.missing.length} missing resources)`);
for (const line of item.missing.slice(0, 8)) console.log(` - ${line}`);
if (item.missing.length > 8) console.log(`${item.missing.length - 8} more`);
}
console.log(`跳过 skipped: ${report.skipped.length}`, report.skipped.join(', ') || '');
if (report.incomplete.length > 0) {
console.log('\n提示未完成的课件请用运营端发布工作台重新发布媒体在浏览器端时服务端无法取到字节。');
}
}
main().catch((error) => {
log.error('Migration failed:', error);
process.exitCode = 1;
});