npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

uniapp-launcher

v1.0.1

Published

Android base installer & wgt synchronizer (CLI + require)

Readme

launcher

Android 基座安装 + wgt 资源同步工具。

支持两种调用方式:

  • CLI 调用node bin/cli.js run --udid ... --project ... --compile ...
  • 模块依赖调用const { Launcher, initLauncher } = require('launcher');
    • Launcher:手动构造 + 手动组合设备选择 / 菜单
    • initLauncher:一行启动,包含设备选择 + run + 控制菜单(推荐)

安装

cd launcher
npm install        # 安装 archiver、portfinder

目录结构

launcher/
├── package.json
├── bin/
│   └── cli.js                 # CLI 入口
├── lib/
│   ├── index.js               # 对外模块入口
│   ├── config.js              # 配置 builder + 校验
│   ├── utils.js               # adb 执行、端口、token、日志
│   ├── android.js             # adb 设备/安装/版本/端口代理
│   ├── httpServer.js          # 安全的 HTTP 静态文件服务
│   ├── websocketServer.js     # 事件驱动的 WebSocket 控制
│   ├── packer.js              # www -> wgt(zip) 打包
│   ├── launcher.js            # 协调器(核心流程)
│   ├── controlMenu.js          # 运行态交互菜单
│   └── initLauncher.js         # 一站式启动入口(设备选择 + run + 菜单)
└── examples/
    └── use-as-module.js       # require 调用示例

CLI 用法

1. 列出设备

node bin/cli.js devices

2. 只安装基座

node bin/cli.js install --udid emulator-5554 \
    --base C:/path/to/android_base.apk

3. 完整运行(安装基座 + 启动 + 同步 wgt)

node bin/cli.js run \
    --udid emulator-5554 \
    --project D:/work/myuni \
    --compile D:/work/myuni/unpackage/dist/dev/app-plus \
    --appid __UNI__XXXX \
    --app-name myuni

常用选项:

| 选项 | 说明 | 默认值 | |---|---|---| | --adb <path> | adb 路径 | 自动探测 | | --base <apk> | 基座 APK 路径 | base/android_base.apk | | --custom-base | 使用自定义基座 | false | | --package <name> | 基座包名 | io.dcloud.HBuilder | | --activity <name> | 基座入口 Activity | io.dcloud.debug.PullDebugActivity | | --lan | 局域网模式(不走 adb reverse) | false | | --http-port <port> | HTTP 端口 | 自动分配 | | --ws-port <port> | WebSocket 端口 | 自动分配 | | --timeout <sec> | 等待基座连接超时 | 60 | | --log-level <level> | 日志级别 | info |

运行后按 Ctrl+C 停止并自动清理(关闭服务、移除 adb reverse、删除临时 zip)。

一站式启动(initLauncher)

initLauncher 封装了「设备选择 -> 构造 Launcher -> 事件挂载 -> run -> 控制菜单」整条链路,适合宿主脚本和外部依赖一行调用。

const { initLauncher } = require('launcher');

(async () => {
  const { udid, launcher } = await initLauncher({
    projectPath: 'D:/work/myuni/unpackage/dist/dev/app-plus',  // 必填:编译产物目录
    manifestPath: 'D:/work/myuni/src/manifest.json',          // 可选:自动解析 appid / appName
    // appid / appName 可覆盖 manifest 解析结果
    // appid: '__UNI__XXXX',
    // appName: 'myuni',
    // baseApkPath: null,
    // customBase: false,
    // timeout: 60,                // 等待基座连接超时
    // scanTimeout: 60,            // 设备扫描超时
    // restartTimeout: 30,         // 菜单「重启应用」超时
    // menu: true,                 // false=run 完成后不进入菜单
    logger: (level, ...args) => console.log(`[${level}]`, ...args),
    onExit: async () => {
      try { await launcher.stop(); } catch (e) { /* ignore */ }
      process.exit(0);
    }
  });
})();

参数说明:

| 参数 | 类型 | 默认 | 说明 | |---|---|---|---| | projectPath | string | - | 必填,编译产物目录(www) | | manifestPath | string\|false | ./src/manifest.json | 用于解析 appid/appName;传 false 跳过 | | appid / appName | string | - | 优先级高于 manifest 解析 | | baseApkPath / customBase | - | null / false | 基座配置 | | packageName / activityName | string | adb.DEFAULT_* | 基座包名 / 入口 Activity | | adbPath | string | 自动探测 | adb 可执行文件路径 | | lanMode | boolean | false | 局域网模式(不走 adb reverse) | | httpPort / wsPort | number | 0 | 0=自动分配 | | timeout | number | 60 | 等待基座反连超时秒数 | | scanTimeout | number | 60 | 设备扫描超时秒数 | | restartTimeout | number | 30 | 菜单「重启应用」等待反连超时 | | logLevel | string | 'info' | error/warn/info/debug | | logger | function | utils.log | 日志回调 (level, ...args) => void | | onExit | function | stop+exit(0) | 菜单退出回调 | | menu | boolean | true | false=run 完成后不进入菜单 |

返回 { udid, launcher }

模块调用用法

const { Launcher } = require('launcher');

const launcher = new Launcher({
  udid: 'emulator-5554',
  projectPath: 'D:/work/myuni',
  compilePath: 'D:/work/myuni/unpackage/dist/dev/app-plus',
  appid: '__UNI__XXXX',
  appName: 'myuni',
  logLevel: 'info'
});

launcher.on('install', r => console.log('install:', r));
launcher.on('push', r => console.log('push:', r));
launcher.on('ready', r => console.log('ready:', r));
launcher.on('log', (msg, level) => console.log(`[${level}] ${msg}`));
launcher.on('error', e => console.error(e));

(async () => {
  await launcher.run();                 // 安装基座 + 启动 + 首次推送
  // 代码变更后增量推送(不重装基座)
  // await launcher.pushResources();
  process.on('SIGINT', async () => {
    await launcher.stop();
    process.exit(0);
  });
})();

整体流程

sequenceDiagram
    participant CLI
    participant L as Launcher
    participant ADB as adb
    participant HTTP as HttpFileServer
    participant WS as WebSocketServer
    participant APK as 基座

    CLI->>L: new Launcher(opts)
    CLI->>L: run()
    L->>ADB: pm list packages / dumpsys versionCode
    alt 需更新
        L->>ADB: adb install -r -d -t base.apk
    end
    L->>ADB: am force-stop
    L->>HTTP: start(httpPort, 白名单, token)
    L->>WS: start(wsPort, token)
    L->>ADB: adb reverse tcp:httpPort tcp:httpPort
    L->>ADB: adb reverse tcp:wsPort tcp:wsPort
    L->>ADB: am start -n .../PullDebugActivity --es port --es ip --es token
    APK-->>WS: 反连 WebSocket (带 token 校验)
    WS-->>L: emit('connected')
    L->>L: compressToWgt(compilePath)  // 打 zip
    L->>WS: sendSyncFileMsg({sourcePath: http://...?path=zip&token=...})
    WS->>APK: 下发同步指令
    APK->>HTTP: GET /static/?path=zip&token=... (经 adb reverse)
    HTTP-->>APK: 200 Content-Length + Range 支持
    APK->>APK: 解压 reload
    APK-->>WS: application_Started
    WS-->>L: emit('log')
    L-->>CLI: emit('ready')

交互式设备选择

通过 lib/interactive.js 提供,支持自动扫描 + 手动选择 + 无设备时 WiFi/USB 引导。

flowchart TD
    A[开始] --> B[扫描 adb devices]
    B --> C{存在可用设备?}
    C -- 是 --> D[列出设备 供手动选择]
    C -- 否 --> E[选择连接方式]
    E --> F[WiFi 扫码连接]
    E --> G[USB 连接]
    F --> F1{USB 设备已连接?}
    F1 -- 是 --> F2[选择 USB 设备]
    F1 -- 否 --> F3[手动输入配对地址+配对码]
    F2 --> F4[tcpip 切换+connect]
    F3 --> F5[pair+connect]
    F4 --> H[轮询设备列表]
    F5 --> H
    G --> G1[提示插上USB+开启调试]
    G1 --> H
    H --> I{设备上线?}
    I -- 否 --> J[重新选择连接方式?]
    J -- 是 --> E
    J -- 否 --> K[退出]
    I -- 是 --> D
    D --> L[返回 udid]
    L --> M[执行 Launcher.run]

模块调用

const { interactive, adb } = require('launcher');

const adbPath = adb.detectADBPath();
const udid = await interactive.selectDevice(adbPath, { scanTimeout: 60 });
if (!udid) process.exit(0);
// udid 交给 Launcher 使用
  • selectDevice(adbPath, opts):交互式入口,返回 udid 或 null(取消)
  • connectViaWiFi(rl, adbPath):WiFi 子流程(USB 切换 / 扫码配对)
  • connectViaUSB(rl, adbPath):USB 子流程(轮询上线)

WiFi 扫码配对可选依赖 qrcode-terminal(已在 package.json 中声明),用于在终端打印配对二维码;未安装时自动跳过,不影响手动输入配对地址。

运行态交互菜单

通过 lib/controlMenu.js 提供,在 Launcher.run() 完成后进入循环菜单,支持重启应用 / 重新运行 / 停止运行。

flowchart TD
    A[Launcher.run 完成] --> B[打印菜单]
    B --> C{用户输入}
    C -- 1 重启应用 --> D{isActive?}
    D -- 否 --> B
    D -- 是 --> E[restart: force-stop + 拉起 Activity + 等待反连 + 推送资源]
    E --> B
    C -- 2 重新运行 --> F[rerun: stop + run 全流程]
    F --> B
    C -- 3 停止运行 --> G[stop]
    G --> B
    C -- 4 继续监听 --> B
    C -- 0 退出脚本 --> H[stop + process.exit 0]

模块调用

const { Launcher, controlMenu } = require('launcher');

const launcher = new Launcher({ /* ... */ });
await launcher.run();

// 进入交互菜单(阻塞,直到用户选择退出脚本)
await controlMenu.runControlMenu(launcher, {
  restartTimeout: 30,            // 重启应用等待反连超时秒数
  onExit: async () => {           // 自定义退出回调(默认 stop + exit(0))
    await launcher.stop();
    process.exit(0);
  }
});
  • runControlMenu(launcher, opts):主入口,阻塞式循环
  • createControlMenu(launcher, opts):构造菜单对象(便于定制 / 测试)
  • defaultMenu(launcher, opts):默认菜单项数组

自定义菜单项

const { controlMenu } = require('launcher');

await controlMenu.runControlMenu(launcher, {
  items: [
    { key: '1', label: '重启应用', action: async () => { await launcher.restart(); } },
    { key: '2', label: '我的自定义操作', action: async () => { /* ... */ } },
    { key: '0', label: '退出', action: async () => { /* ... */ } }
  ]
});

CLI 调用

runpush 命令在启动后会自动进入该菜单:

node bin/cli.js run --udid emulator-5554 --project D:/myuni --compile D:/myuni/unpackage/dist/dev/app-plus