import { constants } from 'node:fs'; import { access, readFile } from 'node:fs/promises'; import { extname, join } from 'node:path'; export const WORKS_PUBLISH_FILE_NAME = 'works-publish.json'; export type WorksPublishData = { app_id: string; title: string; summary: string; category: string; age_band: string; difficulty: string; version_name: string; change_log: string; zip_file_path: string; }; export type WorksPublishReadResult = | { status: 'ready'; filePath: string; publish: WorksPublishData } | { status: 'missing'; filePath: string } | { status: 'invalid'; filePath: string; error: string }; function readString(record: Record, key: string): string { const value = record[key]; return typeof value === 'string' ? value.trim() : ''; } function readRequiredString(record: Record, key: string): string { const value = readString(record, key); if (!value) { throw new Error(`Missing ${key} in ${WORKS_PUBLISH_FILE_NAME}`); } return value; } function parsePublishRecord(value: unknown): WorksPublishData { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new Error(`${WORKS_PUBLISH_FILE_NAME} must contain a JSON object`); } const record = value as Record; const appId = readRequiredString(record, 'app_id'); const zipFilePath = readRequiredString(record, 'zip_file_path'); if (extname(zipFilePath).toLowerCase() !== '.zip') { throw new Error('zip_file_path must point to a .zip file'); } return { app_id: appId, title: readRequiredString(record, 'title'), summary: readRequiredString(record, 'summary'), category: readRequiredString(record, 'category'), age_band: readRequiredString(record, 'age_band'), difficulty: readRequiredString(record, 'difficulty'), version_name: readRequiredString(record, 'version_name'), change_log: readRequiredString(record, 'change_log'), zip_file_path: zipFilePath, }; } export async function readWorksPublishFile(projectPath: string): Promise { const filePath = join(projectPath, WORKS_PUBLISH_FILE_NAME); try { await access(filePath, constants.F_OK); } catch { return { status: 'missing', filePath }; } try { const raw = await readFile(filePath, 'utf8'); return { status: 'ready', filePath, publish: parsePublishRecord(JSON.parse(raw) as unknown), }; } catch (error) { return { status: 'invalid', filePath, error: error instanceof Error ? error.message : String(error), }; } }