UNPKG

superpush

Version:

A command-line interface for SuperPe CodePush - enabling over-the-air (OTA) updates for React Native applications.

1 lines • 44.8 kB
{"version":3,"sources":["/home/runner/work/superpush-cli/superpush-cli/dist/index.cjs","../src/index.ts","../src/wrapper.ts","../src/configs.ts","../src/utils/keys.ts","../src/commands.ts","../src/utils/prompt.ts","../src/utils/api.ts","../src/utils/spinner.ts","../src/utils/compress.ts","../src/utils/exec.ts","../src/utils/interactive-history.ts","../src/utils/checks.ts"],"names":["config","response","spawn","chalk","release","version","path"],"mappings":"AAAA;AACA;AACA;ACFA,gEAAe;AACf,wEAAiB;AACjB,sCAAwB;ADIxB;AACA;AEPA,4EAAkB;AFSlB;AACA;AGVA;AACA,gFAAmB;AAEnB,gBAAA,CAAO,MAAA,CAAO,CAAA;AAEd,IAAM,WAAA,EAAa,YAAA;AACnB,IAAM,gBAAA,EAAkB,aAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAA,EAAG,UAAU,CAAA;AAEpD,IAAM,QAAA,EAAU;AAAA,EACrB,WAAA,EAAa,aAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA;AAAA,EACxC,UAAA;AAAA,EACA,eAAA;AAAA,EACA,OAAA,EAAS,SAAA;AAAA,EACT,qBAAA,EAAuB,CAAA,EAAA;AACvB,EAAA;AACK,EAAA;AACiB,EAAA;AACC,EAAA;AACT,EAAA;AACD,EAAA;AACf;AHS0B;AACA;AI9BT;AAES;AAEU;AACb,EAAA;AACvB;AAEyC;AAClB,EAAA;AACvB;AAEuC;AACpB,EAAA;AACnB;AJ4B0B;AACA;AK3CX;AACE;AACC;AACC;AL6CO;AACA;AMjDR;AACQ;AAEQ;AAChB,EAAA;AACC,IAAA;AACC,IAAA;AACjB,EAAA;AACH;AAEuB;AACG,EAAA;AAEJ,EAAA;AACI,IAAA;AACX,MAAA;AACW,MAAA;AACrB,IAAA;AACF,EAAA;AACH;ANgD0B;AACA;AOlET;AACP,EAAA;AACA,EAAA;AACA,EAAA;AAEuB,EAAA;AACdA,IAAAA;AACO,IAAA;AACJ,MAAA;AACN,MAAA;AACZ,IAAA;AAEqB,IAAA;AACX,MAAA;AACQ,MAAA;AACA,MAAA;AACF,MAAA;AAChB,IAAA;AACF,EAAA;AAGE,EAAA;AAIsB,IAAA;AACF,IAAA;AAEN,IAAA;AACK,IAAA;AAED,IAAA;AACZ,MAAA;AACI,QAAA;AACY,QAAA;AAIZ,QAAA;AACI,UAAA;AACC,UAAA;AACD,UAAA;AACV,QAAA;AAEe,QAAA;AACT,UAAA;AACF,YAAA;AAEM,YAAA;AACC,YAAA;AACP,YAAA;AACK,UAAA;AACL,YAAA;AACF,UAAA;AACF,QAAA;AAEc,QAAA;AACN,UAAA;AACA,UAAA;AACA,UAAA;AAEAC,UAAAA;AACS,UAAA;AAEF,UAAA;AACf,QAAA;AAEiB,QAAA;AACF,QAAA;AAEG,QAAA;AAEJ,MAAA;AACd,QAAA;AAEc,QAAA;AACL,UAAA;AACI,YAAA;AACF,YAAA;AACT,UAAA;AACF,QAAA;AAEiB,QAAA;AACnB,MAAA;AACF,IAAA;AAEO,IAAA;AACI,MAAA;AACF,MAAA;AACT,IAAA;AACF,EAAA;AAEgC,EAAA;AAC1B,IAAA;AACkB,MAAA;AACL,MAAA;AAEG,MAAA;AACE,QAAA;AACX,QAAA;AACI,UAAA;AACK,UAAA;AAKhB,QAAA;AACF,MAAA;AAEa,MAAA;AAEN,MAAA;AACI,QAAA;AACT,QAAA;AACF,MAAA;AACc,IAAA;AACP,MAAA;AACI,QAAA;AACF,QAAA;AACT,MAAA;AACF,IAAA;AACF,EAAA;AAEyC,EAAA;AACpB,IAAA;AACrB,EAAA;AAE+B,EAAA;AACjB,IAAA;AACd,EAAA;AAEgC,EAAA;AAClB,IAAA;AACd,EAAA;AAE+B,EAAA;AACjB,IAAA;AACd,EAAA;AAEkC,EAAA;AACpB,IAAA;AACd,EAAA;AAEiC,EAAA;AACnB,IAAA;AACd,EAAA;AAE4C,EAAA;AACtB,IAAA;AACD,IAAA;AACrB,EAAA;AAEgC,EAAA;AAClB,IAAA;AACO,IAAA;AACrB,EAAA;AACF;AAE0BD;AACH,EAAA;AACvB;AP4B0B;AACA;AQ/LR;AAEG;AACI,iBAAA;AACmB,kBAAA;AACnB,kBAAA;AACf,EAAA;AAEqB,EAAA;AACZ,IAAA;AACjB,EAAA;AAEQ,EAAA;AACe,IAAA;AACL,IAAA;AACC,MAAA;AACV,MAAA;AACD,IAAA;AACR,EAAA;AAE4B,EAAA;AACP,IAAA;AACE,MAAA;AACH,MAAA;AAClB,IAAA;AACqB,IAAA;AACA,IAAA;AACH,IAAA;AACJ,MAAA;AACd,IAAA;AACF,EAAA;AACF;AR6L0B;AACA;AS7ND;AAChB;AACA;AACa;AACA;AAEQ;AACF,EAAA;AACN,IAAA;AAChB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACa,MAAA;AAA4B,MAAA;AAC1C,IAAA;AACmB,IAAA;AACd,IAAA;AAES,IAAA;AAGE,IAAA;AACA,MAAA;AACF,QAAA;AACC,QAAA;AACd,MAAA;AACD,IAAA;AAEqB,IAAA;AACL,MAAA;AACF,QAAA;AACM,QAAA;AACnB,MAAA;AACD,IAAA;AAGoB,IAAA;AAEF,MAAA;AACL,QAAA;AACV,MAAA;AAEgB,IAAA;AACD,MAAA;AACF,QAAA;AACC,QAAA;AACd,MAAA;AACD,IAAA;AAGc,IAAA;AACE,MAAA;AACJ,QAAA;AACM,QAAA;AACnB,MAAA;AACD,IAAA;AACF,EAAA;AACH;ATqN0B;AACA;AUhRXE;AA2BoB;AACb,EAAA;AACE,IAAA;AACX,MAAA;AACA,MAAA;AACJ,MAAA;AACJ,IAAA;AAEkB,IAAA;AACA,MAAA;AAClB,IAAA;AAEkB,IAAA;AACP,MAAA;AACX,IAAA;AACF,EAAA;AACH;AVsP0B;AACA;AWlSR;AAoBX;AACG,EAAA;AACsB,kBAAA;AACH,kBAAA;AACF,kBAAA;AAEM,EAAA;AACjB,IAAA;AACd,EAAA;AAE2B,EAAA;AACP,IAAA;AAA0B,kBAAA;AAC1B,IAAA;AAAkC;AAErC,IAAA;AACK,MAAA;AAClB,MAAA;AACF,IAAA;AAEW,IAAA;AACb,EAAA;AAE8B,EAAA;AACd,IAAA;AACI,IAAA;AACA,IAAA;AAEZ,IAAA;AACA,IAAA;AACc,IAAA;AAGF,IAAA;AAEC,IAAA;AACC,MAAA;AAGC,MAAA;AACF,MAAA;AACX,MAAA;AAES,MAAA;AACK,QAAA;AACZ,QAAA;AACA,QAAA;AACA,QAAA;AAEM,QAAA;AACb,MAAA;AAGkB,MAAA;AACX,QAAA;AACMC,QAAAA;AAAuC,WAAA;AACrD,MAAA;AACK,IAAA;AACa,MAAA;AACpB,IAAA;AAEc,IAAA;AACH,IAAA;AACb,EAAA;AAEc,EAAA;AACK,IAAA;AACX,IAAA;AAGG,IAAA;AACM,MAAA;AACf,IAAA;AACS,IAAA;AACM,MAAA;AACf,IAAA;AAGmB,IAAA;AACE,MAAA;AACV,MAAA;AACM,QAAA;AACf,MAAA;AACS,MAAA;AACM,QAAA;AACf,MAAA;AACF,IAAA;AAEa,IAAA;AAEK,IAAA;AAEE,IAAA;AACT,IAAA;AACb,EAAA;AAE0B,EAAA;AACT,IAAA;AACR,MAAA;AACM,QAAA;AACF,UAAA;AACA,UAAA;AACM,UAAA;AACb,QAAA;AACA,QAAA;AACG,MAAA;AACM,QAAA;AACF,UAAA;AACA,UAAA;AACM,UAAA;AACb,QAAA;AACA,QAAA;AACG,MAAA;AACM,QAAA;AACF,UAAA;AACM,UAAA;AACb,QAAA;AACA,QAAA;AACG,MAAA;AACG,QAAA;AACA,QAAA;AACG,QAAA;AACF,UAAA;AACM,UAAA;AACb,QAAA;AACA,QAAA;AACG,MAAA;AACSA,QAAAA;AACZ,QAAA;AACF,MAAA;AACcA,QAAAA;AACM,QAAA;AACP,QAAA;AACX,QAAA;AACJ,IAAA;AACF,EAAA;AACF;AX2P0B;AACA;AYvZR;AAGc;AACP,EAAA;AACD,IAAA;AAAI;AAAA,8BAAA;AAAmE;AAAS;AACtF,IAAA;AAChB,EAAA;AACF;AAE+BC;AACZ,EAAA;AACK,IAAA;AACN,IAAA;AAChB,EAAA;AACF;AAE6B;AACZ,EAAA;AACO,IAAA;AACN,IAAA;AAChB,EAAA;AACF;AZyZ0B;AACA;AKjaJ;AACA,EAAA;AACX,EAAA;AACA,EAAA;AACV;AAEqB;AACX,EAAA;AACS,IAAA;AACC,IAAA;AACnB,EAAA;AACF;AAEiC;AAEX,EAAA;AACG,EAAA;AACH,EAAA;AAChB,EAAA;AACe,IAAA;AACf,MAAA;AACkB,MAAA;AACpB,IAAA;AAEa,IAAA;AACQ,MAAA;AACjB,QAAA;AACc,QAAA;AACC,QAAA;AACH,QAAA;AAAoF;AAClG,MAAA;AACe,MAAA;AACF,MAAA;AACK,MAAA;AAAM,iCAAA;AAA2C;AAC9D,IAAA;AACQ,MAAA;AACG,MAAA;AAClB,IAAA;AACmB,EAAA;AACN,IAAA;AACO,IAAA;AACN,IAAA;AAChB,EAAA;AACF;AAE4B;AACN,EAAA;AACC,EAAA;AACC,IAAA;AAChB,IAAA;AACY,MAAA;AACG,MAAA;AACf,QAAA;AACc,QAAA;AAChB,MAAA;AAEa,MAAA;AACL,QAAA;AACQ,UAAA;AACE,UAAA;AACC,UAAA;AACH,UAAA;AAAoF;AAClG,QAAA;AACe,QAAA;AACF,QAAA;AACDD,QAAAA;AACP,MAAA;AACQ,QAAA;AACCA,QAAAA;AACA,QAAA;AAChB,MAAA;AACmB,IAAA;AACN,MAAA;AACCA,MAAAA;AACA,MAAA;AAChB,IAAA;AACgB,EAAA;AACI,IAAA;AAAI,6DAAA;AAAmG;AAC7G,IAAA;AAChB,EAAA;AACF;AAEwBE;AACR,EAAA;AACF,IAAA;AACZ,EAAA;AAEmB,EAAA;AAEG,IAAA;AAAM,qCAAA;AAAiD;AAEzD,EAAA;AACR,IAAA;AACI,MAAA;AAAA;AAAA,8CAAA;AAA4D;AACtE,MAAA;AAAO,MAAA;AACT,IAAA;AACD,EAAA;AACL;AAEgC;AAEX,EAAA;AACI,IAAA;AACvB,EAAA;AAGsB,EAAA;AACF,IAAA;AACA,IAAA;AACE,MAAA;AAClB,MAAA;AACF,IAAA;AACF,EAAA;AAEuB,EAAA;AAEV,EAAA;AACP,IAAA;AACc,MAAA;AAGA,MAAA;AACFF,QAAAA;AACZ,QAAA;AACF,MAAA;AAEM,MAAA;AACY,MAAA;AAED,MAAA;AACf,QAAA;AACY,QAAA;AACZ,QAAA;AACF,MAAA;AAEa,MAAA;AACI,QAAA;AACH,UAAA;AACF,UAAA;AACV,QAAA;AACkB,QAAA;AACNA,QAAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACZ,QAAA;AACK,MAAA;AACW,QAAA;AAClB,MAAA;AAEmB,IAAA;AACb,MAAA;AAGW,MAAA;AACHA,QAAAA;AACZ,QAAA;AACK,MAAA;AACSA,QAAAA;AACFA,QAAAA;AACZ,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACF;AAEqB;AACE,EAAA;AAGF,EAAA;AAGnB,EAAA;AACE,IAAA;AAGiB,EAAA;AACC,IAAA;AAA6C;AAC7C,EAAA;AACE,IAAA;AACrB,EAAA;AACH;AAEuD;AACvC,EAAA;AACI,IAAA;AACE,IAAA;AAA+B,aAAA;AAC7B,IAAA;AACf,EAAA;AACa,IAAA;AAA4B,kBAAA;AAC1B,IAAA;AACA,IAAA;AACtB,EAAA;AACF;AAE8B;AACJ,EAAA;AACT,EAAA;AAET,EAAA;AAEe,EAAA;AACE,EAAA;AACjB,EAAA;AAEa,EAAA;AACG,IAAA;AAAI;AAAuE;AACjF,IAAA;AAChB,EAAA;AAEc,EAAA;AACU,EAAA;AACpB,EAAA;AACoB,IAAA;AACD,IAAA;AACA,IAAA;AACH,IAAA;AAA2C;AAC1C,EAAA;AACE,IAAA;AACD,IAAA;AACN,IAAA;AAChB,EAAA;AAEmB,EAAA;AACG,IAAA;AAA2E;AACjF,IAAA;AAChB,EAAA;AAEsB,EAAA;AAED,EAAA;AACL,EAAA;AACA,EAAA;AACA,EAAA;AACM,EAAA;AACN,EAAA;AAEK,EAAA;AACF,EAAA;AACE,EAAA;AAEA,EAAA;AACjB,EAAA;AACmB,IAAA;AACJ,IAAA;AACf,MAAA;AACA,MAAA;AACoB,MAAA;AACtB,IAAA;AACoB,IAAA;AACP,IAAA;AACO,MAAA;AAAyD;AACtE,IAAA;AACW,MAAA;AAClB,IAAA;AACmB,EAAA;AACC,IAAA;AACC,IAAA;AACD,IAAA;AACN,IAAA;AAChB,EAAA;AAEoB,EAAA;AACI,IAAA;AACb,IAAA;AACS,IAAA;AAAgD;AACpE,EAAA;AACF;AAE+B;AACL,EAAA;AACF,EAAA;AACA,EAAA;AACJ,EAAA;AAEZ,EAAA;AAEF,EAAA;AACe,IAAA;AACf,MAAA;AACU,MAAA;AACV,MAAA;AACF,IAAA;AACa,IAAA;AACO,MAAA;AACb,IAAA;AACW,MAAA;AAClB,IAAA;AACmB,EAAA;AACE,IAAA;AACD,IAAA;AACtB,EAAA;AACF;AAE8B;AACJ,EAAA;AAElB,EAAA;AAEF,EAAA;AACe,IAAA;AACf,MAAA;AAAuB,MAAA;AAAqC,MAAA;AAC9D,IAAA;AAEa,IAAA;AACL,MAAA;AACA,MAAA;AACD,IAAA;AACW,MAAA;AAClB,IAAA;AACmB,EAAA;AACE,IAAA;AACD,IAAA;AACtB,EAAA;AACF;AAE8B;AACJ,EAAA;AACF,EAAA;AACA,EAAA;AACJ,EAAA;AAEZ,EAAA;AAEF,EAAA;AACe,IAAA;AACf,MAAA;AACU,MAAA;AACV,MAAA;AACF,IAAA;AACa,IAAA;AACO,MAAA;AACb,IAAA;AACW,MAAA;AAClB,IAAA;AACmB,EAAA;AACE,IAAA;AACD,IAAA;AACtB,EAAA;AACF;AAEkC;AACZ,EAAA;AACT,EAAA;AACW,IAAA;AAAI,6DAAA;AAAmG;AAC3H,IAAA;AACF,EAAA;AAEsB,EAAA;AAAoB,uDAAA;AACxB,EAAA;AACE,IAAA;AAClB,IAAA;AACF,EAAA;AAEgB,EAAA;AACE,EAAA;AAAoE;AACxF;ALsY0B;AACA;AEvvBiB;AAExB,EAAA;AACI,IAAA;AACrB,EAAA;AAEiB,EAAA;AACK,IAAA;AAAiG;AACvG,IAAA;AAChB,EAAA;AAEsB,EAAA;AACxB;AFuvB0B;AACA;ACjwBFG;AACC;AAEG;AAIzB;AAKA;AAIiB;AAOjB;AAKA;AAKiB;AAQS,EAAA;AAE3B;AAGmB;AAKyB,EAAA;AAE5C;AAGkB;AAMA;AAK0B,EAAA;AAE5C;AAIC;AAGW","file":"/home/runner/work/superpush-cli/superpush-cli/dist/index.cjs","sourcesContent":[null,"import fs from \"fs\";\nimport path from \"path\";\nimport { Command } from \"commander\";\nimport { wrapper } from \"./wrapper\";\nimport { ReleaseOptions } from \"./utils/types\";\nimport { login, upgrade, init, build, release, rollback, history, disable, logout } from \"./commands\";\n\n// Dynamically read version from package.json\nconst packageJsonPath = path.join(__dirname, \"..\", \"package.json\");\nconst { version } = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n\nconst program = new Command();\n\nprogram\n .name(\"superpush\")\n .description(\"A SuperPe CodePush CLI tool\")\n .version(version, \"-v, --version\", \"Output the current version of SuperPe CodePush CLI\");\n\nprogram\n .command(\"login\")\n .description(\"Login to SuperPush\")\n .action(() => wrapper(\"login\", () => login()));\n\nprogram\n .command(\"upgrade\")\n .description(\"Upgrade to the latest version of superpush cli\")\n .argument(\"[version]\", \"Upgrade to a specify a version\")\n .action((version: string) => wrapper(\"upgrade\", () => upgrade(version)));\n\nprogram\n .command(\"init\")\n .description(\"Initialise the react application name\")\n .action(() => wrapper(\"init\", async () => init()));\n\nprogram\n .command(\"build\")\n .description(\"Build the bundle\")\n .argument(\"[platform]\", \"Platform to build for (e.g., android, ios). By default, it builds for both platforms.\")\n .action((platform: string) => wrapper(\"build\", () => build(platform)));\n\nprogram\n .command(\"release\")\n .description(\"Release the bundle\")\n .argument(\"<platform>\", \"Platform of the bundle\")\n .argument(\"<release_version>\", \"Release version to push (e.g., x.x.x)\")\n .option(\"-m, --message <message>\", \"Release message\")\n .option(\"-f, --force\", \"Mark the release as mandatory\")\n .option(\"-r, --rollout <rollout>\", \"Release rollout percentage\", (value) => parseInt(value, 10))\n .option(\"-d, --delete\", \"Delete the bundle directory post release\")\n .action((platform: string, release_version: string, options: ReleaseOptions) =>\n wrapper(\"release\", () => release(platform, release_version, options))\n );\n\nprogram\n .command(\"rollback\")\n .description(\"Rollback to specific version\")\n .argument(\"<platform>\", \"Platform to rollback (e.g., android, ios)\")\n .argument(\"<release>\", \"Rollback to specified version (e.g., x.x.x)\")\n .argument(\"<patch>\", \"Rollback to specified patch (e.g., vx)\")\n .action((platform: string, release: string, patch: string) =>\n wrapper(\"rollback\", () => rollback(platform, release, patch))\n );\n\nprogram\n .command(\"history\")\n .description(\"List the releases history\")\n .argument(\"<platform>\", \"Releases by platform (e.g., android, ios)\")\n .action((platform: string) => wrapper(\"history\", () => history(platform)));\n\nprogram\n .command(\"disable\")\n .description(\"Disable the release for a specific platform\")\n .argument(\"<platform>\", \"Platform to disable (e.g., android, ios)\")\n .argument(\"<release>\", \"Disable specified version (e.g., x.x.x)\")\n .argument(\"<patch>\", \"Disable specified patch (e.g., vx)\")\n .action((platform: string, release: string, patch: string) =>\n wrapper(\"disable\", () => disable(platform, release, patch))\n );\n\nprogram\n .command(\"logout\")\n .description(\"Logout from SuperPush\")\n .action(() => wrapper(\"logout\", () => logout()));\n\nprogram.parse();\n","import chalk from 'chalk';\nimport { CONFIGS } from './configs';\nimport { getKey } from './utils/keys';\nimport { refreshToken } from \"./commands\";\n\nexport const wrapper = async (cmd: string, callback: CallableFunction) => {\n\n if (![\"upgrade\", \"login\", \"build\", \"logout\"].includes(cmd)) {\n await refreshToken();\n }\n\n if (![\"upgrade\", \"login\", \"init\", \"build\", \"logout\"].includes(cmd) && !getKey(`${CONFIGS.PROJECT_DIR}`)) {\n console.error(chalk.red(`Application not configured. Please run '${chalk.italic(\"superpush init\")}' to configure.\\n`));\n process.exit(1);\n }\n\n await Promise.resolve(callback());\n};","import path from 'path';\nimport dotenv from 'dotenv';\n\ndotenv.config();\n\nconst CONFIG_DIR = \".superpush\";\nconst CONFIG_DIR_PATH = path.join(process.cwd(), CONFIG_DIR);\n\nexport const CONFIGS = {\n PROJECT_DIR: path.basename(process.cwd()),\n CONFIG_DIR,\n CONFIG_DIR_PATH,\n ANDROID: \"android\",\n ANDROID_BUNDLE_OUTPUT: `${CONFIG_DIR}/android/index.android.bundle`,\n ANDROID_ASSET_DESTINATION: `${CONFIG_DIR}/android`,\n IOS: \"ios\",\n IOS_BUNDLE_OUTPUT: `${CONFIG_DIR}/ios/main.jsbundle`,\n IOS_ASSET_DESTINATION: `${CONFIG_DIR}/ios`,\n API_BASE_URL: \"https://superpush-api.superpe.in\",\n API_VERSION: \"/api/v1\",\n};\n","import Conf from 'conf';\n\nconst config = new Conf({ projectName: 'superpush' });\n\nexport function setKey(key: string, value: any) {\n config.set(key, value);\n}\n\nexport function getKey(key: string): any {\n return config.get(key);\n}\n\nexport function deleteKey(key: string) {\n config.delete(key);\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport { rm } from 'fs/promises';\nimport { CONFIGS } from \"./configs\";\nimport { prompt } from \"./utils/prompt\";\nimport { createApi } from \"./utils/api\";\nimport { Spinner } from \"./utils/spinner\";\nimport { ReleaseOptions } from \"./utils/types\";\nimport { getKey, setKey, deleteKey } from \"./utils/keys\";\nimport { compressBundle } from \"./utils/compress\";\nimport { executeInteractive } from \"./utils/exec\";\nimport { InteractiveHistory } from \"./utils/interactive-history\";\nimport { isValidPlatform, isValidVersion, isValidPatch } from \"./utils/checks\";\n\nconst api = createApi({\n baseUrl: `${CONFIGS.API_BASE_URL}${CONFIGS.API_VERSION}`,\n timeout: 5000,\n retries: 2,\n});\n\nconst secureHeaders = {\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${getKey(\"auth\") ? getKey(\"auth\").access_token : \"\"}`,\n }\n}\n\nexport const login = async () => {\n\n const email = await prompt(chalk.blue(\"šŸ“± Enter your email: \"));\n const password = await prompt(chalk.blue(\"šŸ“± Enter your password: \"));\n const spinner = new Spinner(`Logging in as ${email}...`);\n try {\n const response = await api!.post<{ access_token?: string; refresh_token?: string; expires_at?: string }>(\n \"/login\",\n { email, password },\n );\n\n if (response.success && response.data) {\n const authTokens = {\n email,\n access_token: response.data.access_token,\n refresh_token: response.data.refresh_token,\n expires_at: response.data.expires_at || new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString(), // Default to 1 week if not provided\n };\n setKey(\"auth\", authTokens);\n spinner.stop();\n console.log(chalk.green(`\\nāœ… Logged in successfully as ${email}\\n`));\n } else {\n spinner.stop();\n throw new Error(response.error || \"Login failed\");\n }\n } catch (error: any) {\n spinner.stop();\n console.error(chalk.red(\"\\nāŒ Login failed:\\n\"), error.message || error, \"\\n\");\n process.exit(1);\n }\n};\n\nexport const refreshToken = async () => {\n const auth = getKey(\"auth\");\n if (auth && new Date(auth.expires_at) < new Date()) {\n const spinner = new Spinner(`Auth token has expired. Re-authenticating...`);\n try {\n spinner.start();\n const response = await api!.post<{ access_token?: string; refresh_token?: string; expires_at?: string }>(\n \"/refresh-token\",\n { email: auth.email, refresh_token: auth.refresh_token },\n );\n\n if (response.success && response.data) {\n const authTokens = {\n email: auth.email,\n access_token: response.data.access_token,\n refresh_token: response.data.refresh_token,\n expires_at: response.data.expires_at || new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString(), // Default to 1 week if not provided\n };\n setKey(\"auth\", authTokens);\n spinner.stop();\n console.log(chalk.green(\"āœ… Auth token refreshed successfully!\"));\n } else {\n spinner.stop();\n console.error(chalk.red(`āŒ Failed to refresh auth token. Please login using '${chalk.italic(\"superpush login\")}'.`));\n process.exit(1);\n }\n } catch (error: any) {\n spinner.stop();\n console.error(chalk.red(\"\\nāŒ Failed to refresh auth token:\\n\"), error.message || error, \"\\n\");\n process.exit(1);\n }\n } else if (!auth) {\n console.error(chalk.red(`\\nāŒ No SuperPush session found. Please login first using '${chalk.italic(\"superpush login\")}'.\\n`));\n process.exit(1);\n }\n};\n\nexport const upgrade = (version: string | null) => {\n if (!version) {\n version = \"latest\";\n }\n\n executeInteractive(`npm i -g superpush@${version}`)\n .then((output) => {\n console.log(chalk.green(`\\nāœ… Upgraded superpush version to ${version}\\n`));\n })\n .catch((error) => {\n console.error(\n chalk.red(`\\n\\nāŒ Failed to upgrade superpush version to ${version}:\\n`),\n error, \"\\n\"\n );\n });\n};\n\nexport const init = async () => {\n // Ensure .superpush directory exists\n if (!fs.existsSync(CONFIGS.CONFIG_DIR_PATH)) {\n fs.mkdirSync(CONFIGS.CONFIG_DIR_PATH);\n }\n\n // Check if config already exists\n if (getKey(`${CONFIGS.PROJECT_DIR}`)) {\n const overwrite = await prompt(chalk.yellow(\"\\nāš ļø Configuration file already exists. Overwrite? (y/N): \"));\n if (![\"y\", \"yes\"].includes(overwrite.toLowerCase())) {\n console.log(chalk.red(\"āŒ Initialization cancelled.\\n\"));\n return;\n }\n }\n\n console.log(chalk.blue(\"\\n\\n\\t\\tšŸš€ Welcome to SuperPush CLI! Let's set up your app...\\n\"));\n\n while (true) {\n try {\n const appName = await prompt(chalk.blue(\"šŸ“± Enter your app name: \"));\n\n // Check if user wants to quit (empty input)\n if (!appName || appName.trim() === '') {\n console.log(chalk.red(\"āŒ App name cannot be empty. Please try again or press Ctrl+C to quit.\"));\n continue;\n }\n\n const trimmedAppName = appName.trim();\n console.log(chalk.cyan(`šŸ” Checking availability for \"${trimmedAppName}\"...`));\n\n const response = await api!.post<{ id?: string; success?: boolean }>(\n \"/init\",\n { app_name: trimmedAppName },\n secureHeaders,\n );\n\n if (response.success && response.data) {\n const config = {\n app_name: trimmedAppName,\n app_id: response.data.id,\n };\n setKey(`${CONFIGS.PROJECT_DIR}`, config);\n console.log(chalk.green(`āœ… App \"${trimmedAppName}\" created successfully!`));\n console.log(`ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”`)\n console.log(\"│\", chalk.yellow(\"āš ļø Run the below command to ignore the configuration file from version control.\"), \"│\");\n console.log(\"│\", chalk.gray.italic(`\\techo \"\\\\n# superpush CLI\\\\n${CONFIGS.CONFIG_DIR}\" >> .gitignore\\t\\t\\t `), \"│\");\n console.log(`ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜`)\n return; // Exit the loop on success\n } else {\n throw new Error(response.error || \"Failed to create app\");\n }\n\n } catch (error: any) {\n const errorMessage = error.message || error;\n\n // Check if app name is taken\n if (errorMessage.includes(\"already taken\") || errorMessage.includes(\"exists\") || errorMessage.includes(\"unavailable\")) {\n console.log(chalk.red(\"āŒ App name is already taken. Please try a different name.\"));\n continue; // Continue the loop to ask for another name\n } else {\n console.error(chalk.red(`āŒ Failed to create app: ${errorMessage}`));\n console.log(chalk.yellow(\"Please try again or press Ctrl+C to quit.\"));\n continue; // Continue the loop to retry\n }\n }\n }\n};\n\nconst buildBundle = (platform: string) => {\n const bundleOutput = platform === CONFIGS.ANDROID ?\n CONFIGS.ANDROID_BUNDLE_OUTPUT : CONFIGS.IOS_BUNDLE_OUTPUT;\n\n const assetsDest = platform === CONFIGS.ANDROID ?\n CONFIGS.ANDROID_ASSET_DESTINATION : CONFIGS.IOS_ASSET_DESTINATION;\n\n executeInteractive(\n `npx react-native bundle \\\n --platform ${platform} --dev false --entry-file index.js \\\n --bundle-output ${bundleOutput} --assets-dest ${assetsDest}`\n ).then((output) => {\n console.log(chalk.green(`āœ… Build successful for ${platform}\\n`));\n }).catch((error) => {\n console.error(chalk.red(\"āŒ Build failed:\\n\"), error, \"\\n\");\n });\n}\n\nexport const build = (platform: string | undefined) => {\n if (platform) {\n isValidPlatform(platform);\n console.log(chalk.blue(`\\nBuilding for ${platform}...`));\n buildBundle(platform);\n } else {\n console.log(chalk.blue(`\\nBuilding for both ${CONFIGS.ANDROID} and ${CONFIGS.IOS}...`));\n buildBundle(CONFIGS.ANDROID);\n buildBundle(CONFIGS.IOS);\n }\n};\n\nexport const release = async (platform: string, release_version: string, options: ReleaseOptions) => {\n isValidPlatform(platform);\n isValidVersion(release_version);\n\n const superpush_config = getKey(`${CONFIGS.PROJECT_DIR}`)\n\n const bundleOutput = platform === CONFIGS.ANDROID ? CONFIGS.ANDROID_BUNDLE_OUTPUT : CONFIGS.IOS_BUNDLE_OUTPUT;\n const platformDir = `${CONFIGS.CONFIG_DIR}/${platform}`;\n const bundleOutputCompressed = `${CONFIGS.CONFIG_DIR}/${platform}_bundle.tar.br`;\n\n if (!fs.existsSync(bundleOutput)) {\n console.error(chalk.red(`\\nāŒ Bundle file not found. Please run 'superpush build <platform>'\\n`));\n process.exit(1);\n }\n\n console.log(\"\");\n const compressSpinner = new Spinner(`Compressing ${platform} bundle...`);\n try {\n compressSpinner.start();\n await compressBundle(platformDir, bundleOutputCompressed);\n compressSpinner.stop();\n console.log(chalk.green(`āœ… Bundle compressed successfully\\n`));\n } catch (error: any) {\n compressSpinner.stop();\n console.error(chalk.red(\"\\nāŒ Failed to compress bundle:\\n\"), error.message || error, \"\\n\");\n process.exit(1);\n }\n\n if (!fs.existsSync(bundleOutputCompressed)) {\n console.error(chalk.red(`āŒ Compressed bundle file not found. Please check the build process\\n`));\n process.exit(1);\n }\n\n const bundleFile = fs.readFileSync(bundleOutputCompressed);\n\n const formData = new FormData();\n formData.append(\"app_id\", superpush_config.app_id);\n formData.append(\"platform\", platform);\n formData.append(\"release_version\", release_version);\n const fileName = path.basename(bundleOutputCompressed);\n formData.append(\"bundle\", new Blob([bundleFile]), fileName);\n\n if (options.message) formData.append(\"release_notes\", options.message);\n if (options.force) formData.append(\"mandatory\", options.force);\n if (options.rollout) formData.append(\"rollout\", options.rollout);\n\n let releaseSpinner = new Spinner(`Releasing ${platform} bundle...`);\n try {\n releaseSpinner.start();\n const response = await api!.post<{ id?: string; success?: boolean }>(\n \"/release\",\n formData,\n { ...secureHeaders, timeout: 0, retries: 0 },\n );\n releaseSpinner.stop();\n if (response.success && response.data) {\n console.log(chalk.green(`āœ… Successfully released the ${platform} bundle\\n`));\n } else {\n throw new Error(response.error || \"Failed to release bundle\");\n }\n } catch (error: any) {\n releaseSpinner.stop();\n const errorMessage = error.message || error;\n console.error(chalk.red(\"\\nāŒ Failed to release the bundle:\\n\"), errorMessage, \"\\n\");\n process.exit(1);\n }\n\n if (options.delete) {\n await rm(platformDir, { recursive: true, force: true });\n await rm(bundleOutputCompressed, { recursive: true, force: true });\n console.log(chalk.yellow(`šŸ—‘ļø Deleted ${platform} build files\\n`));\n }\n};\n\nexport const rollback = async (platform: string, version: string, patch: string) => {\n isValidPlatform(platform);\n isValidVersion(version);\n patch = `${version}_${patch}`;\n isValidPatch(patch);\n\n const superpush_config = getKey(`${CONFIGS.PROJECT_DIR}`);\n\n try {\n const response = await api!.post<any>(\n \"/rollback\",\n { app_id: superpush_config.app_id, platform, release_version: version, composite_version: patch },\n secureHeaders,\n );\n if (response.success && response.data) {\n console.log(chalk.green(`āœ… ${response.data.message}`));\n } else {\n throw new Error(response.error || \"Failed to fetch release history\");\n }\n } catch (error: any) {\n const errorMessage = error.message || error;\n console.error(chalk.red(\"\\nāŒ Failed to rollback:\\n\"), errorMessage, \"\\n\");\n }\n}\n\nexport const history = async (platform: string) => {\n isValidPlatform(platform);\n\n const superpush_config = getKey(`${CONFIGS.PROJECT_DIR}`);\n\n try {\n const response = await api!.get<any>(\n \"/releases\", { app_id: superpush_config.app_id, platform }, secureHeaders,\n );\n\n if (response.success && response.data) {\n const interactiveHistory = new InteractiveHistory(response.data);\n await interactiveHistory.start(superpush_config.app_id, platform);\n } else {\n throw new Error(response.error || \"Failed to fetch release history\");\n }\n } catch (error: any) {\n const errorMessage = error.message || error;\n console.error(chalk.red(\"\\nāŒ Failed to fetch release history:\\n\"), errorMessage, \"\\n\");\n }\n}\n\nexport const disable = async (platform: string, version: string, patch: string) => {\n isValidPlatform(platform);\n isValidVersion(version);\n patch = `${version}_${patch}`;\n isValidPatch(patch);\n\n const superpush_config = getKey(`${CONFIGS.PROJECT_DIR}`);\n\n try {\n const response = await api!.post<any>(\n \"/disable\",\n { app_id: superpush_config.app_id, platform, release_version: version, composite_version: patch },\n secureHeaders,\n );\n if (response.success && response.data) {\n console.log(chalk.green(`āœ… ${response.data.message}`));\n } else {\n throw new Error(response.error || \"Failed to disable the patch\");\n }\n } catch (error: any) {\n const errorMessage = error.message || error;\n console.error(chalk.red(\"\\nāŒ Failed to disable:\\n\"), errorMessage, \"\\n\");\n }\n}\n\nexport const logout = async () => {\n const auth = getKey(\"auth\");\n if (!auth) {\n console.error(chalk.red(`\\nāŒ No SuperPush session found. Please login first using '${chalk.italic(\"superpush login\")}'.\\n`));\n return;\n }\n\n const confirm = await prompt(chalk.yellow(`\\nAre you sure you want to logout from SuperPush? (y/N): `));\n if (![\"y\", \"yes\"].includes(confirm.toLowerCase())) {\n console.log(chalk.red(\"āŒ Logout cancelled.\\n\"));\n return;\n }\n\n deleteKey(\"auth\");\n console.log(chalk.green(`āœ… Logged out successfully from SuperPush as ${auth.email}\\n`));\n};\n","import chalk from \"chalk\";\nimport * as readline from 'readline';\n\nexport const createPrompt = () => {\n return readline.createInterface({\n input: process.stdin,\n output: process.stdout\n });\n};\n\nexport const prompt = (question: string): Promise<string> => {\n const rl = createPrompt();\n\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n};\n\nexport const promptWithValidation = async (\n question: string,\n validator: (input: string) => boolean | string,\n errorMessage?: string\n): Promise<string> => {\n while (true) {\n const answer = await prompt(question);\n const validation = validator(answer);\n\n if (validation === true) {\n return answer;\n }\n\n const error = typeof validation === 'string' ? validation : errorMessage;\n if (error) {\n console.log(chalk.red(`āŒ ${error}`));\n }\n }\n};","import { ApiResponse, ApiRequestConfig, ApiConfig } from './types';\n\nexport class Api {\n private baseUrl: string;\n private defaultConfig: ApiRequestConfig;\n private defaultHeaders: Record<string, string>;\n\n constructor(config: ApiConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.defaultHeaders = {\n 'Content-Type': 'application/json',\n ...config.headers,\n };\n\n this.defaultConfig = {\n method: 'GET',\n timeout: config.timeout || 10000,\n retries: config.retries || 3,\n headers: this.defaultHeaders,\n };\n }\n\n private async makeRequest<T>(\n endpoint: string,\n params?: any,\n config?: ApiRequestConfig\n ): Promise<ApiResponse<T>> {\n const finalConfig = { ...this.defaultConfig, ...config };\n const url = `${this.baseUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;\n\n let attempt = 0;\n const maxRetries = finalConfig.retries || 3;\n\n while (attempt <= maxRetries) {\n try {\n const controller = new AbortController();\n const timeoutId = finalConfig.timeout && finalConfig.timeout > 0\n ? setTimeout(() => controller.abort(), finalConfig.timeout)\n : null;\n\n const requestOptions: RequestInit = {\n method: finalConfig.method,\n headers: finalConfig.headers,\n signal: controller.signal,\n };\n\n if (params && ['POST', 'PUT', 'PATCH'].includes(finalConfig.method || 'GET')) {\n if (params instanceof FormData) {\n requestOptions.body = params;\n // Remove Content-Type header to let browser set it with boundary\n const headers = { ...finalConfig.headers };\n delete headers['Content-Type'];\n requestOptions.headers = headers;\n } else {\n requestOptions.body = JSON.stringify(params);\n }\n }\n\n if (params && finalConfig.method === 'GET') {\n const searchParams = new URLSearchParams(params);\n const separator = url.includes('?') ? '&' : '?';\n const finalUrl = `${url}${separator}${searchParams.toString()}`;\n\n const response = await fetch(finalUrl, requestOptions);\n if (timeoutId) clearTimeout(timeoutId);\n\n return await this.handleResponse<T>(response);\n }\n\n const response = await fetch(url, requestOptions);\n if (timeoutId) clearTimeout(timeoutId);\n\n return await this.handleResponse<T>(response);\n\n } catch (error) {\n attempt++;\n\n if (attempt > maxRetries) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error occurred',\n };\n }\n\n await this.delay(Math.pow(2, attempt) * 1000);\n }\n }\n\n return {\n success: false,\n error: 'Max retries exceeded',\n };\n }\n\n private async handleResponse<T>(response: Response): Promise<ApiResponse<T>> {\n try {\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n const errorData = isJson ? await response.json() : await response.text();\n return {\n success: false,\n error: typeof errorData === 'string'\n ? errorData\n : (typeof errorData === 'object' && errorData !== null && 'detail' in errorData && typeof (errorData as any).detail === 'string'\n ? (errorData as any).detail\n : 'Request failed'),\n };\n }\n\n const data = isJson ? await response.json() : await response.text();\n\n return {\n success: true,\n data: data as T,\n };\n } catch (error) {\n return {\n success: false,\n error: 'Failed to parse response',\n };\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n async get<T>(endpoint: string, params?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {\n return this.makeRequest<T>(endpoint, params, { ...config, method: 'GET' });\n }\n\n async post<T>(endpoint: string, params?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {\n return this.makeRequest<T>(endpoint, params, { ...config, method: 'POST' });\n }\n\n async put<T>(endpoint: string, params?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {\n return this.makeRequest<T>(endpoint, params, { ...config, method: 'PUT' });\n }\n\n async delete<T>(endpoint: string, params?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {\n return this.makeRequest<T>(endpoint, params, { ...config, method: 'DELETE' });\n }\n\n async patch<T>(endpoint: string, params?: any, config?: ApiRequestConfig): Promise<ApiResponse<T>> {\n return this.makeRequest<T>(endpoint, params, { ...config, method: 'PATCH' });\n }\n\n setHeader(key: string, value: string): void {\n this.defaultHeaders[key] = value;\n this.defaultConfig.headers = this.defaultHeaders;\n }\n\n removeHeader(key: string): void {\n delete this.defaultHeaders[key];\n this.defaultConfig.headers = this.defaultHeaders;\n }\n}\n\nexport const createApi = (config: ApiConfig): Api => {\n return new Api(config);\n};","import chalk from 'chalk';\n\nexport class Spinner {\n private frames = ['ā ‹', 'ā ™', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ‡', 'ā '];\n private interval: NodeJS.Timeout | null = null;\n private currentFrame = 0;\n private message: string;\n\n constructor(message: string) {\n this.message = message;\n }\n\n start() {\n process.stdout.write('\\x1B[?25l'); // Hide cursor\n this.interval = setInterval(() => {\n process.stdout.write('\\r' + chalk.cyan(this.frames[this.currentFrame]) + ' ' + this.message);\n this.currentFrame = (this.currentFrame + 1) % this.frames.length;\n }, 100);\n }\n\n stop(finalMessage?: string) {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n process.stdout.write('\\r' + ' '.repeat(this.message.length + 2) + '\\r'); // Clear line\n process.stdout.write('\\x1B[?25h'); // Show cursor\n if (finalMessage) {\n console.log(finalMessage);\n }\n }\n}","import { pipeline } from 'stream/promises';\nimport { createBrotliCompress } from 'zlib';\nimport { createWriteStream } from 'fs';\nimport * as path from 'path';\nimport { spawn } from 'child_process';\n\nexport const compressBundle = async (platformDir: string, bundleOutputCompressed: string) => {\n return new Promise<void>((resolve, reject) => {\n const tar = spawn('tar', [\n '--exclude=*/.DS_Store/*',\n '--exclude=._*',\n '--exclude=*/._*',\n '-c',\n '-C',\n path.dirname(platformDir), path.basename(platformDir)\n ]);\n const writeStream = createWriteStream(bundleOutputCompressed);\n const brotliCompress = createBrotliCompress();\n\n let hasError = false;\n\n // Handle tar process errors\n tar.on('error', (error) => {\n if (!hasError) {\n hasError = true;\n reject(error);\n }\n });\n\n tar.stderr.on('data', (data) => {\n if (!hasError) {\n hasError = true;\n reject(new Error(`tar error: ${data.toString()}`));\n }\n });\n\n // Set up the pipeline\n pipeline(tar.stdout, brotliCompress, writeStream)\n .then(() => {\n if (!hasError) {\n resolve();\n }\n })\n .catch((error) => {\n if (!hasError) {\n hasError = true;\n reject(error);\n }\n });\n\n // Handle tar process exit\n tar.on('close', (code) => {\n if (!hasError && code !== 0) {\n hasError = true;\n reject(new Error(`tar process exited with code ${code}`));\n }\n });\n });\n}\n","import { exec, spawn } from \"child_process\";\n\n/**\n * Executes a shell command (non-interactive).\n * @param {string} cmd - The shell command to execute.\n * @returns {Promise<string>} - Resolves with stdout, rejects with error/stderr.\n */\nexport const execute = (cmd: string): Promise<string> => {\n return new Promise((resolve, reject) => {\n exec(cmd, (error: Error | null, stdout: string, stderr: string) => {\n if (error) {\n reject(stderr || error.message);\n } else {\n resolve(stdout);\n }\n });\n });\n};\n\n/**\n * Spawns an interactive shell command.\n * Streams stdio to the parent process.\n * @param {string} cmd - The command to run.\n * @param {string[]} args - Arguments for the command.\n * @param {object} [options] - Optional spawn options.\n * @returns {Promise<number>} - Resolves with exit code.\n */\nexport const executeInteractive = (cmd: string, args: string[] = [], options: object = {}): Promise<number> => {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, {\n stdio: \"inherit\",\n shell: true,\n ...options,\n });\n\n child.on(\"close\", (code) => {\n resolve(code ?? 0);\n });\n\n child.on(\"error\", (err) => {\n reject(err);\n });\n });\n};\n","import chalk from 'chalk';\nimport { prompt } from './prompt';\n\ninterface Patch {\n version: string;\n message: string;\n is_latest: boolean;\n}\n\ninterface Release {\n version: string;\n patches: Patch[];\n}\n\ninterface HistoryData {\n appId: string;\n platform: string;\n releases: Release[];\n}\n\nexport class InteractiveHistory {\n private data: HistoryData;\n private currentReleaseIndex = 0;\n private currentPatchPage = 0;\n private patchesPerPage = 5;\n\n constructor(data: HistoryData) {\n this.data = data;\n }\n\n async start(appId: string, platform: string) {\n console.log(chalk.blue(`\\nšŸ“± App ID: ${appId}`));\n console.log(chalk.blue(`šŸ”§ Platform: ${platform}\\n`));\n\n if (!this.data.releases || this.data.releases.length === 0) {\n console.log(chalk.yellow(\"āš ļø No releases found for this platform.\\n\"));\n return;\n }\n\n await this.showCurrentView(appId, platform);\n }\n\n private async showCurrentView(appId: string, platform: string) {\n console.clear();\n console.log(chalk.blue(`šŸ“± App ID: ${appId} | šŸ”§ Platform: ${platform}`));\n console.log(chalk.bold.white(\"šŸ“¦ Release History\\n\"));\n\n const currentRelease = this.data.releases[this.currentReleaseIndex];\n const isLatestRelease = this.currentReleaseIndex === 0;\n const versionIcon = isLatestRelease ? \"šŸš€\" : \"šŸ“‹\";\n\n // Show current release info\n console.log(chalk.green(`${versionIcon} Version: ${currentRelease.version} (${this.currentReleaseIndex + 1}/${this.data.releases.length})`));\n\n if (currentRelease.patches && currentRelease.patches.length > 0) {\n console.log(chalk.cyan(\" šŸ“„ Patches:\"));\n\n // Show paginated patches\n const startIndex = this.currentPatchPage * this.patchesPerPage;\n const endIndex = Math.min(startIndex + this.patchesPerPage, currentRelease.patches.length);\n const visiblePatches = currentRelease.patches.slice(startIndex, endIndex);\n\n visiblePatches.forEach((patch: Patch, index: number) => {\n const patchIcon = patch.is_latest ? \"ā—\" : \"ā—‹\";\n const patchColor = patch.is_latest ? chalk.bold.green : chalk.gray;\n const latestLabel = patch.is_latest ? \" (LATEST)\" : \"\";\n const messageText = patch.message ? ` - ${patch.message}` : \"\";\n\n console.log(patchColor(` ${patchIcon} ${patch.version}${latestLabel}${chalk.dim(messageText)}`));\n });\n\n // Show pagination info for patches\n if (currentRelease.patches.length > this.patchesPerPage) {\n const totalPages = Math.ceil(currentRelease.patches.length / this.patchesPerPage);\n console.log(chalk.dim(`\\n Showing ${startIndex + 1}-${endIndex} of ${currentRelease.patches.length} patches (Page ${this.currentPatchPage + 1}/${totalPages})`));\n }\n } else {\n console.log(chalk.gray(\" šŸ“„ No patches available\"));\n }\n\n console.log(\"\");\n await this.showNavigationMenu(appId, platform);\n }\n\n private async showNavigationMenu(appId: string, platform: string) {\n const options = [];\n const currentRelease = this.data.releases[this.currentReleaseIndex];\n\n // Release navigation\n if (this.currentReleaseIndex > 0) {\n options.push(\"p - Previous release\");\n }\n if (this.currentReleaseIndex < this.data.releases.length - 1) {\n options.push(\"n - Next release\");\n }\n\n // Patch pagination\n if (currentRelease.patches && currentRelease.patches.length > this.patchesPerPage) {\n const totalPages = Math.ceil(currentRelease.patches.length / this.patchesPerPage);\n if (this.currentPatchPage > 0) {\n options.push(\"u - Previous patches\");\n }\n if (this.currentPatchPage < totalPages - 1) {\n options.push(\"d - Next patches\");\n }\n }\n\n options.push(\"q - Quit\");\n\n console.log(chalk.cyan(\"Navigation: \") + chalk.dim(options.join(\" | \")));\n\n const input = await prompt(\"\\nEnter command: \");\n await this.handleInput(appId, platform, input.toLowerCase().trim());\n }\n\n private async handleInput(appId: string, platform: string, input: string) {\n switch (input) {\n case 'p':\n if (this.currentReleaseIndex > 0) {\n this.currentReleaseIndex--;\n this.currentPatchPage = 0; // Reset patch page when changing release\n await this.showCurrentView(appId, platform);\n }\n break;\n case 'n':\n if (this.currentReleaseIndex < this.data.releases.length - 1) {\n this.currentReleaseIndex++;\n this.currentPatchPage = 0; // Reset patch page when changing release\n await this.showCurrentView(appId, platform);\n }\n break;\n case 'u':\n if (this.currentPatchPage > 0) {\n this.currentPatchPage--;\n await this.showCurrentView(appId, platform);\n }\n break;\n case 'd':\n const currentRelease = this.data.releases[this.currentReleaseIndex];\n const totalPages = Math.ceil(currentRelease.patches.length / this.patchesPerPage);\n if (this.currentPatchPage < totalPages - 1) {\n this.currentPatchPage++;\n await this.showCurrentView(appId, platform);\n }\n break;\n case 'q':\n console.log(chalk.green(\"\\nšŸ‘‹ Goodbye!\\n\"));\n break;\n default:\n console.log(chalk.red(\"\\nāŒ Invalid command. Please try again.\"));\n await new Promise(resolve => setTimeout(resolve, 1000));\n await this.showCurrentView(appId, platform);\n break;\n }\n }\n}","import chalk from \"chalk\";\nimport { CONFIGS } from \"../configs\";\n\nexport const isValidPlatform = (platform: string) => {\n if (![CONFIGS.ANDROID, CONFIGS.IOS].includes(platform)) {\n console.error(chalk.red(`\\n\\nāŒ Invalid platform. Use '${CONFIGS.ANDROID}' or '${CONFIGS.IOS}'.\\n\\n`));\n process.exit(1);\n }\n}\n\nexport const isValidVersion = (release: string) => {\n if (!release || !/^\\d+\\.\\d+\\.\\d+$/.test(release)) {\n console.error(chalk.red(\"\\n\\nāŒ Invalid release version format. Use 'x.x.x'\\n\\n\"));\n process.exit(1);\n }\n}\n\nexport const isValidPatch = (patch: string) => {\n if (!patch || !/^\\d+\\.\\d+\\.\\d+_v\\d+$/.test(patch)) {\n console.error(chalk.red(\"\\n\\nāŒ Invalid patch format. Use 'x.x.x_vx'\\n\\n\"));\n process.exit(1);\n }\n}\n"]}