feat: add configurable VDO.Ninja overview and floating window focus
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
require('dotenv').config({ quiet: true });
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const url = new URL(process.env.DATABASE_URL);
|
||||
url.hostname = '192.168.0.20';
|
||||
url.port = '15437';
|
||||
url.pathname = '/lms';
|
||||
url.searchParams.set('connect_timeout', '5');
|
||||
const db = new PrismaClient({ datasources: { db: { url: url.toString() } } });
|
||||
const apply = process.argv.includes('--apply');
|
||||
const include = {
|
||||
weeks: { orderBy: { week: 'asc' }, include: {
|
||||
modules: { orderBy: { sortOrder: 'asc' } }, topics: { orderBy: { sortOrder: 'asc' } },
|
||||
practices: { orderBy: { sortOrder: 'asc' } }, references: { orderBy: { id: 'asc' } },
|
||||
} },
|
||||
environmentProfiles: { orderBy: { id: 'asc' } }, maps: { orderBy: { id: 'asc' } },
|
||||
weekMaps: { orderBy: { id: 'asc' } }, instructorCurriculumWeekOverrides: { orderBy: { id: 'asc' } },
|
||||
};
|
||||
const snapshot = client => client.course.findMany({ orderBy: { id: 'asc' }, include });
|
||||
function data(row) {
|
||||
const { id, createdAt, updatedAt, ...rest } = row;
|
||||
return rest;
|
||||
}
|
||||
async function main() {
|
||||
const before = await snapshot(db);
|
||||
const base = before.find(c => c.slug === 'futuredrive-autonomy-basic');
|
||||
assert(base);
|
||||
const source = base.weeks.find(w => w.week === 3);
|
||||
assert.equal(source?.title, '원격 개발');
|
||||
assert.equal(source.topics.length, 2);
|
||||
assert.equal(source.modules.length, 11);
|
||||
const profiles = base.environmentProfiles.filter(p => p.week === 3);
|
||||
assert.equal(profiles.length, 2);
|
||||
const targets = before.filter(c => c.id !== base.id
|
||||
&& c.slug !== 'autonomy-studio-lms-guide'
|
||||
&& !c.weeks.some(w => w.week === 3));
|
||||
for (const c of targets) {
|
||||
assert(!c.environmentProfiles.some(p => p.week === 3), `${c.title}: existing profile`);
|
||||
assert(!c.weekMaps.some(p => p.week === 3), `${c.title}: existing map assignments`);
|
||||
assert(!c.instructorCurriculumWeekOverrides.some(p => p.week === 3), `${c.title}: existing override`);
|
||||
}
|
||||
if (apply) {
|
||||
fs.mkdirSync('backups', { recursive: true });
|
||||
const file = `backups/courses-before-remote-week-${Date.now()}.json`;
|
||||
fs.writeFileSync(file, JSON.stringify(before, null, 2), { mode: 0o600, flag: 'wx' });
|
||||
console.log('Backup:', file);
|
||||
}
|
||||
const summary = [];
|
||||
try {
|
||||
await db.$transaction(async tx => {
|
||||
assert.deepEqual(await snapshot(tx), before);
|
||||
for (const c of targets) {
|
||||
const { modules, topics, practices, references, ...weekData } = data(source);
|
||||
const week = await tx.courseWeek.create({ data: { ...weekData, courseId: c.id } });
|
||||
for (const topic of topics) await tx.courseWeekTopic.create({ data: { ...data(topic), weekId: week.id } });
|
||||
for (const practice of practices) await tx.courseWeekPractice.create({ data: { ...data(practice), weekId: week.id } });
|
||||
for (const reference of references) await tx.courseWeekReference.create({ data: { ...data(reference), weekId: week.id } });
|
||||
const ids = new Map();
|
||||
for (const module of modules) {
|
||||
const created = await tx.courseWeekModule.create({ data: { ...data(module), weekId: week.id } });
|
||||
ids.set(module.id, created.id);
|
||||
}
|
||||
for (const profile of profiles) {
|
||||
const config = structuredClone(profile.config);
|
||||
config.moduleSet.modules = config.moduleSet.modules.map(m => {
|
||||
assert(ids.has(m.id), 'Cross-week module reference');
|
||||
return { ...m, id: ids.get(m.id) };
|
||||
});
|
||||
await tx.courseEnvironmentProfile.create({ data: { ...data(profile), courseId: c.id, config } });
|
||||
}
|
||||
for (const assignment of base.weekMaps.filter(m => m.week === 3)) {
|
||||
const systemMap = ['nav2-turtlebot3-world', 'nav2-turtlebot3-house'].includes(assignment.mapKey);
|
||||
if (!systemMap && !c.maps.some(m => m.key === assignment.mapKey)) {
|
||||
const map = base.maps.find(m => m.key === assignment.mapKey);
|
||||
assert(map, 'Missing source map');
|
||||
await tx.courseMap.create({ data: { ...data(map), courseId: c.id } });
|
||||
}
|
||||
await tx.courseWeekMap.create({ data: { ...data(assignment), courseId: c.id } });
|
||||
}
|
||||
summary.push({ course: c.title, week: 3, weekId: week.id, modules: ids.size });
|
||||
}
|
||||
const after = await snapshot(tx);
|
||||
for (const old of before) {
|
||||
const current = after.find(c => c.id === old.id);
|
||||
if (!targets.some(c => c.id === old.id)) { assert.deepEqual(current, old); continue; }
|
||||
assert.deepEqual(current.weeks.filter(w => w.week !== 3), old.weeks);
|
||||
assert.deepEqual(current.environmentProfiles.filter(p => p.week !== 3), old.environmentProfiles);
|
||||
assert.deepEqual(current.weekMaps.filter(m => m.week !== 3), old.weekMaps);
|
||||
assert.deepEqual(current.instructorCurriculumWeekOverrides, old.instructorCurriculumWeekOverrides);
|
||||
for (const map of old.maps) assert.deepEqual(current.maps.find(m => m.id === map.id), map);
|
||||
const added = current.weeks.find(w => w.week === 3);
|
||||
assert.equal(added.topics.length, 2);
|
||||
assert.equal(added.modules.length, 11);
|
||||
const envs = current.environmentProfiles.filter(p => p.week === 3);
|
||||
assert.equal(envs.length, 2);
|
||||
for (const p of envs) {
|
||||
assert.equal(p.config.moduleSet.modules.length, 9);
|
||||
assert(p.config.moduleSet.modules.every(m => added.modules.some(n => n.id === m.id)));
|
||||
}
|
||||
}
|
||||
if (!apply) throw new Error('DRY_RUN_ROLLBACK');
|
||||
}, { isolationLevel: 'Serializable', timeout: 30000 });
|
||||
} catch (e) { if (e.message !== 'DRY_RUN_ROLLBACK') throw e; }
|
||||
console.log(JSON.stringify({ applied: apply, courses: summary }, null, 2));
|
||||
}
|
||||
main().catch(e => { console.error(e.message); process.exitCode = 1; }).finally(() => db.$disconnect());
|
||||
Reference in New Issue
Block a user