#!/usr/bin/env node /** * Resolve App Metadata — discover `bundle_name` / `app_name` from a HarmonyOS * project and write `app-metadata.json`. * * Usage: resolve-metadata-tool.ts --project-dir --output * Reads `/AppScope/app.json5` (JSON5 with comments/trailing commas), * extracts `app.bundleName` and `app.label` (resolving `$string:xxx` references * from the base-qualifier string.json), writes `{bundle_name, app_name, project_root}` * to `--output`, and emits the same JSON to stdout. */ import fs from 'node:fs'; import path from 'node:path'; // ---- JSON5 strip (remove comments + trailing commas, then JSON.parse) ---- function stripJson5(text: string): string { // Remove single-line comments (// ...) — but not inside strings let result = ''; let inString = false; let stringChar = ''; for (let i = 0; i < text.length; i++) { const ch = text[i]; const next = text[i + 1]; if (inString) { result += ch; if (ch === '\\' ) { result += next ?? ''; i++; continue; } if (ch === stringChar) inString = false; continue; } if (ch === '"' || ch === "'") { inString = true; stringChar = ch; result += ch; continue; } if (ch === '/' && next === '/') { while (i < text.length && text[i] !== '\n') i++; continue; } if (ch === '/' && next === '*') { i += 2; while (i < text.length - 1 && !(text[i] === '*' && text[i + 1] === '/')) i++; i++; continue; } result += ch; } // Remove trailing commas before } or ] result = result.replace(/,(\s*[}\]])/g, '$1'); return result; } function parseJson5(filePath: string): unknown { const raw = fs.readFileSync(filePath, 'utf-8'); return JSON.parse(stripJson5(raw)); } // ---- string resource resolver ---- function resolveStringResource(projectRoot: string, key: string): string | null { const stringJson = path.join(projectRoot, 'AppScope', 'resources', 'base', 'element', 'string.json'); if (!fs.existsSync(stringJson)) return null; let doc: unknown; try { doc = parseJson5(stringJson); } catch { return null; } const arr = (doc as { string?: unknown })?.string; if (!Array.isArray(arr)) return null; const entry = arr.find((e) => (e as { name?: unknown })?.name === key); const value = (entry as { value?: unknown } | undefined)?.value; return typeof value === 'string' && value ? value : null; } // ---- main ---- function main(): void { const args = process.argv.slice(2); let projectDir = ''; let output = ''; for (let i = 0; i < args.length; i++) { if (args[i] === '--project-dir') projectDir = args[++i]; else if (args[i] === '--output') output = args[++i]; else if (args[i] === '-h' || args[i] === '--help') { console.log('Usage: resolve-metadata-tool.ts --project-dir --output '); process.exit(0); } } if (!projectDir || !output) { console.error('Usage: resolve-metadata-tool.ts --project-dir --output '); process.exit(1); } const projectRoot = path.resolve(projectDir); const appJson5 = path.join(projectRoot, 'AppScope', 'app.json5'); if (!fs.existsSync(appJson5)) { console.error(`not a HarmonyOS project root (no AppScope/app.json5): ${projectRoot}`); process.exit(1); } let doc: unknown; try { doc = parseJson5(appJson5); } catch (e) { console.error(`failed to parse ${appJson5}: ${e instanceof Error ? e.message : String(e)}`); process.exit(1); } const app = (doc as { app?: Record })?.app; const bundleName = app?.bundleName; if (typeof bundleName !== 'string' || !bundleName) { console.error(`missing app.bundleName in ${appJson5}`); process.exit(1); } const label = app?.label; let appName: string; if (typeof label === 'string' && label.startsWith('$string:')) { const key = label.slice('$string:'.length); const resolved = resolveStringResource(projectRoot, key); if (resolved) { appName = resolved; } else { console.error(`warning: string resource '${key}' not found — using literal '${label}'`); appName = label; } } else if (typeof label === 'string' && label) { appName = label; } else { console.error(`warning: missing app.label — using bundleName as app_name`); appName = bundleName; } const meta = { bundle_name: bundleName, app_name: appName, project_root: projectRoot }; fs.mkdirSync(path.dirname(path.resolve(output)), { recursive: true }); fs.writeFileSync(path.resolve(output), JSON.stringify(meta) + '\n', 'utf-8'); console.log(JSON.stringify(meta)); } main();