112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
// Migration script: converts apps from versioned directory structure
|
|
// (apps/<name>/<timestamp>/ with `current` symlink) to flat structure (apps/<name>/).
|
|
//
|
|
// Usage: bun run remote:migrate
|
|
//
|
|
// What it does:
|
|
// 1. Scans APPS_DIR for apps with a `current` symlink
|
|
// 2. Resolves the symlink to find the active version directory
|
|
// 3. Moves files from the active version into the app root (preserving node_modules/logs)
|
|
// 4. Removes old version directories and the symlink
|
|
//
|
|
// Safe to run multiple times -- skips apps already in flat structure.
|
|
|
|
import { existsSync, mkdirSync, readdirSync, renameSync, lstatSync, readlinkSync, rmSync } from 'fs'
|
|
import { join, resolve } from 'path'
|
|
|
|
const APPS_DIR = process.env.APPS_DIR ?? join(process.env.HOME!, 'apps')
|
|
const VERSION_RE = /^\d{8}-\d{6}$/
|
|
|
|
if (!existsSync(APPS_DIR)) {
|
|
console.error(`APPS_DIR does not exist: ${APPS_DIR}`)
|
|
process.exit(1)
|
|
}
|
|
|
|
const apps = readdirSync(APPS_DIR, { withFileTypes: true })
|
|
.filter(e => e.isDirectory())
|
|
.map(e => e.name)
|
|
|
|
let migrated = 0
|
|
let skipped = 0
|
|
|
|
for (const name of apps) {
|
|
const appDir = join(APPS_DIR, name)
|
|
const currentLink = join(appDir, 'current')
|
|
|
|
// Already flat -- has package.json at root with no current symlink
|
|
if (!existsSync(currentLink)) {
|
|
if (existsSync(join(appDir, 'package.json'))) {
|
|
skipped++
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Verify it's actually a symlink
|
|
let stat
|
|
try {
|
|
stat = lstatSync(currentLink)
|
|
} catch {
|
|
continue
|
|
}
|
|
|
|
if (!stat.isSymbolicLink()) {
|
|
console.warn(` [skip] ${name}: 'current' exists but is not a symlink`)
|
|
skipped++
|
|
continue
|
|
}
|
|
|
|
// Resolve the symlink to get the active version directory
|
|
const target = readlinkSync(currentLink)
|
|
const activeDir = resolve(appDir, target)
|
|
|
|
if (!existsSync(activeDir)) {
|
|
console.warn(` [skip] ${name}: symlink target does not exist: ${target}`)
|
|
skipped++
|
|
continue
|
|
}
|
|
|
|
console.log(` [migrate] ${name} (active version: ${target})`)
|
|
|
|
// Collect all version directories and other entries
|
|
const entries = readdirSync(appDir, { withFileTypes: true })
|
|
const versionDirs = entries
|
|
.filter(e => e.isDirectory() && VERSION_RE.test(e.name) && e.name !== target)
|
|
.map(e => e.name)
|
|
|
|
// Remove old (non-active) version directories first
|
|
for (const ver of versionDirs) {
|
|
const verPath = join(appDir, ver)
|
|
rmSync(verPath, { recursive: true, force: true })
|
|
console.log(` removed old version: ${ver}`)
|
|
}
|
|
|
|
// Remove the current symlink
|
|
rmSync(currentLink)
|
|
|
|
// Move files from active version directory into app root
|
|
const activeEntries = readdirSync(activeDir)
|
|
for (const entry of activeEntries) {
|
|
const src = join(activeDir, entry)
|
|
const dest = join(appDir, entry)
|
|
|
|
// Skip if destination already exists (e.g. node_modules, logs at root level)
|
|
if (existsSync(dest)) {
|
|
console.log(` skip (exists): ${entry}`)
|
|
continue
|
|
}
|
|
|
|
renameSync(src, dest)
|
|
}
|
|
|
|
// Clean up the now-empty active version directory
|
|
rmSync(activeDir, { recursive: true, force: true })
|
|
|
|
migrated++
|
|
console.log(` done`)
|
|
}
|
|
|
|
console.log()
|
|
console.log(`Migration complete: ${migrated} migrated, ${skipped} skipped`)
|