/**
* The migration registry — passed to runMigrations(). Add a new entry
* (next version number) for each future schema upgrade.
*/
import { HyperIssues } from 'hyper-issues'
import { readOldOrgs } from './read-old-orgs.js'
import { migrateOrgs } from './migrate-orgs.js'
import { readOldIssues, readOldLabels } from './read-old-issues.js'
import { migrateIssues } from './migrate-issues.js'
/**
* Replay each org's v1 separate issue tracker (`issues:org:<name>`) into its
* v2 linked-core org room (#42). Per-repo trackers are untouched (they pass
* through unchanged). Optional within the v2 migration: only runs when the
* boot wiring supplies `openOldOrgTracker` (the source opener). The target is
* the org's own composed Room (newOrgs.getOrgRoom(name)), wrapped read/write
* by a HyperIssues that does NOT own the room.
*
* ctx:
* newOrgs the migrated per-org HyperOrgs (has getOrgRoom)
* openOldOrgTracker async (orgName) -> opened old issue tracker base | null
* orgNames? explicit list (defaults to newOrgs.list() names)
* log?
* @returns summary { orgs, issues, comments, labels, skipped, errors }
*/
export async function migrateOrgIssues (ctx = {}) {
const { newOrgs, openOldOrgTracker, log } = ctx
const summary = { orgs: 0, issues: 0, comments: 0, labels: 0, skipped: 0, errors: 0 }
if (typeof openOldOrgTracker !== 'function') return summary
if (!newOrgs || typeof newOrgs.getOrgRoom !== 'function') {
throw new Error('migrateOrgIssues needs ctx.newOrgs with getOrgRoom')
}
let orgNames = ctx.orgNames
if (!orgNames) {
const list = await newOrgs.list()
orgNames = list.map(o => o.name)
}
for (const name of orgNames) {
let oldTracker = null
try {
oldTracker = await openOldOrgTracker(name)
} catch (err) {
summary.errors++
if (log) log('open old org tracker ' + name + ' failed: ' + err.message)
continue
}
if (!oldTracker) continue // org had no separate issue tracker — nothing to migrate
const room = newOrgs.getOrgRoom(name)
if (!room) {
summary.errors++
if (log) log('org ' + name + ' has no room in target — skipped issues')
continue
}
// Wrap the org's composed room; ownsRoom:false so closing this tracker
// doesn't tear down the org that owns the room.
const target = new HyperIssues(null, { room, ownsRoom: false })
const s = await migrateIssues({
oldIssues: readOldIssues(oldTracker),
oldLabels: readOldLabels(oldTracker),
createIssue: (id, opts) => target.createWithId(id, opts),
createComment: (iid, cid, opts) => target.commentWithId(iid, cid, opts),
createLabel: (n, color, desc, dn) => target.createLabel(n, color, desc, dn),
onError: (key, err) => { if (log) log('org ' + name + ' ' + key + ': ' + err.message) }
})
summary.orgs++
summary.issues += s.issues
summary.comments += s.comments
summary.labels += s.labels
summary.skipped += s.skipped
summary.errors += s.errors
}
return summary
}
// v2: orgs single-autobase (v1) → one autobase per org (#37) + resourceKeys
// (Phase B), AND org issues from their separate `issues:org:<name>` autobase
// → the org's own room (#42 linked-core). In-place: reads the old `orgs`
// autobase + old per-org issue trackers, writes per-org HyperOrgs (with
// composed issues schema) in the same corestore.
//
// ctx (supplied by the boot wiring / test):
// oldOrgsBase - opened old single-orgs autobase (has .view)
// newOrgs - the new per-org HyperOrgs (target; getOrgRoom)
// openOldOrgTracker? - async (orgName) -> old issue tracker base | null
// resolveResourceKey? - (repoName) -> tracker bootstrapKey | null
// log?
export const orgsToPerOrg = {
version: 2,
name: 'orgs-single-to-per-org',
async up (ctx = {}) {
const { oldOrgsBase, newOrgs, resolveResourceKey, openOldOrgTracker, log } = ctx
if (!oldOrgsBase || !newOrgs) throw new Error('orgs migration needs ctx.oldOrgsBase + ctx.newOrgs')
const orgs = await migrateOrgs({
oldOrgs: readOldOrgs(oldOrgsBase),
create: (o) => newOrgs.create(o.name, o.type || 'group', {
displayName: o.displayName,
description: o.description,
about: o.about,
visibility: o.visibility,
userId: o.userId
}),
addMember: (name, m) => newOrgs.addMember(name, m.userId, m.role),
addResource: (name, r) => newOrgs.addResource(name, r.name, r.bootstrapKey ? { bootstrapKey: r.bootstrapKey } : {}),
resolveResourceKey,
onError: (n, e) => { if (log) log('org ' + n + ' failed: ' + e.message) }
})
// #42: replay org issues into their rooms (no-op unless boot supplies the opener).
const issues = await migrateOrgIssues({ newOrgs, openOldOrgTracker, log })
return { orgs, issues }
}
}
export const MIGRATIONS = [orgsToPerOrg]
export const TARGET_VERSION = MIGRATIONS.reduce((m, x) => Math.max(m, x.version), 0)