diff --git a/src/package.ts b/src/package.ts index e4f4b29..908c5a2 100644 --- a/src/package.ts +++ b/src/package.ts @@ -27,6 +27,15 @@ import { t } from './utils/i18n'; import { getBooleanOption, getStringListOption } from './utils/options'; import { bundleLocationFields, locateZipEntry } from './utils/zip-range'; +// parse* output is JSON so the bundleHash (content fingerprint) can be piped +// into jq; the raw bundle bytes are dropped — a Buffer preview is just noise. +function printPackageInfo({ + bundleFile: _bundleFile, + ...info +}: Record) { + console.log(JSON.stringify(info, null, 2)); +} + type PackageCommandOptions = Record & { appId?: string; appKey?: string; @@ -386,28 +395,28 @@ export const packageCommands = { if (!fn?.endsWith('.app')) { throw new Error(t('usageParseApp')); } - console.log(await getAppInfo(fn)); + printPackageInfo(await getAppInfo(fn)); }, parseIpa: async ({ args }: { args: string[] }) => { const fn = args[0]; if (!fn?.endsWith('.ipa')) { throw new Error(t('usageParseIpa')); } - console.log(await getIpaInfo(fn)); + printPackageInfo(await getIpaInfo(fn)); }, parseApk: async ({ args }: { args: string[] }) => { const fn = args[0]; if (!fn?.endsWith('.apk')) { throw new Error(t('usageParseApk')); } - console.log(await getApkInfo(fn)); + printPackageInfo(await getApkInfo(fn)); }, parseAab: async ({ args }: { args: string[] }) => { const fn = args[0]; if (!fn?.endsWith('.aab')) { throw new Error(t('usageParseAab')); } - console.log(await getAabInfo(fn)); + printPackageInfo(await getAabInfo(fn)); }, extractApk: async ({ args, diff --git a/tests/package.test.ts b/tests/package.test.ts index 3e06ae4..faba9da 100644 --- a/tests/package.test.ts +++ b/tests/package.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test'; +import { createHash } from 'crypto'; import fs from 'fs'; import os from 'os'; import path from 'path'; @@ -179,6 +180,48 @@ describe('packageCommands native upload', () => { }); }); +describe('packageCommands parse*', () => { + let tempDir: string; + let consoleSpy: ReturnType; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rnu-parse-')); + consoleSpy = spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + test('prints JSON with bundleHash and without the bundle bytes', async () => { + const appPath = path.join(tempDir, 'demo.app'); + await createZip(appPath, { + 'resources/rawfile/bundle.harmony.js': 'console.log("hi");', + 'resources/rawfile/meta.json': JSON.stringify({ + versionName: '1.2.3', + pushy_build_time: 1700000000000, + }), + 'resources/rawfile/update.json': JSON.stringify({ + harmony: { appId: 7, appKey: 'k' }, + }), + }); + + await packageCommands.parseApp({ args: [appPath] }); + + expect(consoleSpy).toHaveBeenCalledTimes(1); + expect(JSON.parse(consoleSpy.mock.calls[0][0])).toEqual({ + versionName: '1.2.3', + buildTime: 1700000000000, + bundleHash: createHash('sha256') + .update('console.log("hi");') + .digest('hex'), + appId: 7, + appKey: 'k', + }); + }); +}); + describe('choosePackage', () => { afterEach(() => { global.NO_INTERACTIVE = undefined;