toes history
This commit is contained in:
parent
115d3199e8
commit
79a0471383
|
|
@ -13,4 +13,4 @@ export {
|
||||||
stopApp,
|
stopApp,
|
||||||
} from './manage'
|
} from './manage'
|
||||||
export { statsApp } from './stats'
|
export { statsApp } from './stats'
|
||||||
export { cleanApp, diffApp, getApp, pullApp, pushApp, rollbackApp, stashApp, stashListApp, stashPopApp, statusApp, syncApp, versionsApp } from './sync'
|
export { cleanApp, diffApp, getApp, historyApp, pullApp, pushApp, rollbackApp, stashApp, stashListApp, stashPopApp, statusApp, syncApp, versionsApp } from './sync'
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,55 @@ interface ManifestDiff {
|
||||||
remoteManifest: Manifest | null
|
remoteManifest: Manifest | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function historyApp(name?: string) {
|
||||||
|
const appName = resolveAppName(name)
|
||||||
|
if (!appName) return
|
||||||
|
|
||||||
|
type HistoryEntry = {
|
||||||
|
version: string
|
||||||
|
current: boolean
|
||||||
|
added: string[]
|
||||||
|
modified: string[]
|
||||||
|
deleted: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryResponse = { history: HistoryEntry[] }
|
||||||
|
|
||||||
|
const result = await get<HistoryResponse>(`/api/sync/apps/${appName}/history`)
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
if (result.history.length === 0) {
|
||||||
|
console.log(`No versions found for ${color.bold(appName)}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`History for ${color.bold(appName)}:\n`)
|
||||||
|
|
||||||
|
for (const entry of result.history) {
|
||||||
|
const date = formatVersion(entry.version)
|
||||||
|
const label = entry.current ? ` ${color.green('→')} ${color.bold(entry.version)}` : ` ${entry.version}`
|
||||||
|
const suffix = entry.current ? ` ${color.green('(current)')}` : ''
|
||||||
|
console.log(`${label} ${color.gray(date)}${suffix}`)
|
||||||
|
|
||||||
|
const hasChanges = entry.added.length > 0 || entry.modified.length > 0 || entry.deleted.length > 0
|
||||||
|
if (!hasChanges) {
|
||||||
|
console.log(color.gray(' No changes'))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of entry.added) {
|
||||||
|
console.log(` ${color.green('+')} ${file}`)
|
||||||
|
}
|
||||||
|
for (const file of entry.modified) {
|
||||||
|
console.log(` ${color.yellow('~')} ${file}`)
|
||||||
|
}
|
||||||
|
for (const file of entry.deleted) {
|
||||||
|
console.log(` ${color.red('-')} ${file}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getApp(name: string) {
|
export async function getApp(name: string) {
|
||||||
console.log(`Fetching ${color.bold(name)} from server...`)
|
console.log(`Fetching ${color.bold(name)} from server...`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
envRm,
|
envRm,
|
||||||
envSet,
|
envSet,
|
||||||
getApp,
|
getApp,
|
||||||
|
historyApp,
|
||||||
infoApp,
|
infoApp,
|
||||||
listApps,
|
listApps,
|
||||||
logApp,
|
logApp,
|
||||||
|
|
@ -258,6 +259,13 @@ program
|
||||||
.argument('[name]', 'app name (uses current directory if omitted)')
|
.argument('[name]', 'app name (uses current directory if omitted)')
|
||||||
.action(versionsApp)
|
.action(versionsApp)
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('history')
|
||||||
|
.helpGroup('Config:')
|
||||||
|
.description('Show file changes between versions')
|
||||||
|
.argument('[name]', 'app name (uses current directory if omitted)')
|
||||||
|
.action(historyApp)
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('rollback')
|
.command('rollback')
|
||||||
.helpGroup('Config:')
|
.helpGroup('Config:')
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,68 @@ router.get('/apps/:app/versions', c => {
|
||||||
return c.json({ versions, current: currentVersion })
|
return c.json({ versions, current: currentVersion })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.get('/apps/:app/history', c => {
|
||||||
|
const appName = c.req.param('app')
|
||||||
|
if (!appName) return c.json({ error: 'App not found' }, 404)
|
||||||
|
|
||||||
|
const appDir = safePath(APPS_DIR, appName)
|
||||||
|
if (!appDir) return c.json({ error: 'Invalid path' }, 400)
|
||||||
|
if (!existsSync(appDir)) return c.json({ error: 'App not found' }, 404)
|
||||||
|
|
||||||
|
const currentLink = join(appDir, 'current')
|
||||||
|
const currentVersion = existsSync(currentLink)
|
||||||
|
? realpathSync(currentLink).split('/').pop()
|
||||||
|
: null
|
||||||
|
|
||||||
|
const entries = readdirSync(appDir, { withFileTypes: true })
|
||||||
|
const versions = entries
|
||||||
|
.filter(e => e.isDirectory() && /^\d{8}-\d{6}$/.test(e.name))
|
||||||
|
.map(e => e.name)
|
||||||
|
.sort()
|
||||||
|
.reverse() // Newest first
|
||||||
|
|
||||||
|
// Generate manifests for each version
|
||||||
|
const manifests = new Map<string, Record<string, { hash: string }>>()
|
||||||
|
for (const version of versions) {
|
||||||
|
const versionPath = join(appDir, version)
|
||||||
|
const manifest = generateManifest(versionPath, appName)
|
||||||
|
manifests.set(version, manifest.files)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff consecutive pairs
|
||||||
|
const history = versions.map((version, i) => {
|
||||||
|
const current = version === currentVersion
|
||||||
|
const files = manifests.get(version)!
|
||||||
|
const olderVersion = versions[i + 1]
|
||||||
|
const olderFiles = olderVersion ? manifests.get(olderVersion)! : {}
|
||||||
|
|
||||||
|
const added: string[] = []
|
||||||
|
const modified: string[] = []
|
||||||
|
const deleted: string[] = []
|
||||||
|
|
||||||
|
// Files in this version
|
||||||
|
for (const [path, info] of Object.entries(files)) {
|
||||||
|
const older = olderFiles[path]
|
||||||
|
if (!older) {
|
||||||
|
added.push(path)
|
||||||
|
} else if (older.hash !== info.hash) {
|
||||||
|
modified.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files removed in this version
|
||||||
|
for (const path of Object.keys(olderFiles)) {
|
||||||
|
if (!files[path]) {
|
||||||
|
deleted.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { version, current, added: added.sort(), modified: modified.sort(), deleted: deleted.sort() }
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.json({ history })
|
||||||
|
})
|
||||||
|
|
||||||
router.get('/apps/:app/manifest', c => {
|
router.get('/apps/:app/manifest', c => {
|
||||||
const appName = c.req.param('app')
|
const appName = c.req.param('app')
|
||||||
if (!appName) return c.json({ error: 'App not found' }, 404)
|
if (!appName) return c.json({ error: 'App not found' }, 404)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user