Files
music/scripts/reindex-music.ts
T
pastilhasandClaude Opus 5 9f06297c64 reindex-music looked one level too deep and reported the sidecar down
It read `process.env.DATA_PATH ?? cwd/data`. DATA_PATH stopped being an env var on 2026-08-13, when
the install layout became something derived from the working directory rather than configured — so run
from the repo this looked for platform/data/music/.server, which is one level too deep, found nothing,
and said the sidecar was not running.

Imports DATA_PATH from the platform instead, so there is one derivation rather than a copy that fell
behind. Verified it resolves to /home/pastilhas/officer-dev/data.

Also corrects the usage line: the script moved into this plugin and the header still said
`bun scripts/reindex-music.ts` from the platform repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 03:39:01 +00:00

128 lines
4.3 KiB
TypeScript

#!/usr/bin/env bun
/**
* Trigger the music library index on the officer-music sidecar and follow its progress live,
* ending with a summary report. Run from the platform repo:
*
* bun plugins/music/scripts/reindex-music.ts
*
* It reads the sidecar's port from `<data>/music/.server` (written by the sidecar on startup) and
* consumes its /reindex/stream SSE endpoint — the same stream the app subscribes to.
*
* The data root comes from the PLATFORM, not from an env var. This read `process.env.DATA_PATH ??
* cwd/data` and DATA_PATH stopped existing on 2026-08-13, when the install layout became something
* derived from the working directory rather than configured. Run from the repo it looked in
* `platform/data/music/.server`, which is one level too deep, and reported the sidecar as not running.
*/
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { DATA_PATH } from '@@/data-path';
const PORT_FILE = join(DATA_PATH, 'music', '.server');
type Progress = {
running: boolean;
foldersScanned: number;
albumsBuilt: number;
albumsSkipped: number;
tracksIndexed: number;
coversSaved: number;
currentPath: string;
};
type Report = {
albums: number;
built: number;
skipped: number;
foldersScanned: number;
tracksIndexed: number;
coversSaved: number;
discographies: number;
elapsedSec: number;
error: string | null;
};
function readPort(): number {
try {
const p = parseInt(readFileSync(PORT_FILE, 'utf8').trim(), 10);
if (Number.isInteger(p) && p > 0) return p;
} catch {
/* fall through */
}
console.error(`✗ Could not read the sidecar port from ${PORT_FILE}.`);
console.error(' Is officer-music running? pm2 restart officer-music');
process.exit(1);
}
const isTTY = Boolean(process.stdout.isTTY);
const cols = () => (process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 100);
function printProgress(p: Progress): void {
const line =
`♪ indexing… folders ${p.foldersScanned} · tracks ${p.tracksIndexed} · ` +
`built ${p.albumsBuilt} · skipped ${p.albumsSkipped} · covers ${p.coversSaved}` +
(p.currentPath ? ` · ${p.currentPath}` : '');
if (isTTY) {
const clipped = line.length > cols() - 1 ? line.slice(0, cols() - 2) + '…' : line;
process.stdout.write('\r\x1b[2K' + clipped);
} else {
process.stdout.write(line + '\n');
}
}
function printReport(r: Report): void {
if (isTTY) process.stdout.write('\r\x1b[2K');
const l = (k: string, v: string | number) => console.log(` ${k.padEnd(9)} ${v}`);
console.log('\n─── Music index complete ───');
l('Albums:', `${r.albums} (${r.built} built, ${r.skipped} unchanged)`);
l('Tracks:', `${r.tracksIndexed} indexed`);
l('Covers:', `${r.coversSaved} compressed`);
l('Discogs:', `${r.discographies} artist${r.discographies === 1 ? '' : 's'}`);
l('Folders:', `${r.foldersScanned} scanned`);
l('Elapsed:', `${r.elapsedSec}s`);
if (r.error) l('Error:', r.error);
console.log('────────────────────────────\n');
}
async function main(): Promise<void> {
const port = readPort();
const url = `http://127.0.0.1:${port}/reindex/stream`;
console.log(`Triggering music index via ${url}\n`);
let res: Response;
try {
res = await fetch(url);
} catch (err) {
console.error(`✗ Could not reach the sidecar at 127.0.0.1:${port}: ${String(err)}`);
process.exit(1);
}
if (!res.ok || !res.body) {
console.error(`✗ Sidecar returned ${res.status}`);
process.exit(1);
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
let sep: number;
while ((sep = buf.indexOf('\n\n')) >= 0) {
const frame = buf.slice(0, sep);
buf = buf.slice(sep + 2);
let event = 'message';
let data = '';
for (const line of frame.split('\n')) {
if (line.startsWith('event:')) event = line.slice(6).trim();
else if (line.startsWith('data:')) data += line.slice(5).trim();
}
if (!data) continue;
if (event === 'progress') printProgress(JSON.parse(data) as Progress);
else if (event === 'done') printReport(JSON.parse(data) as Report);
}
}
}
main();