Repository navigation
Releases: lw0129a/dsh-app-packager
Release list
v0.6.4 — Windows 上引擎不再因中文输出崩掉 · 测试可移植 · 锁文件跟上
The engine no longer dies printing Chinese on a Windows console. engine/lib/common.sh exports PYTHONUTF8=1 and PYTHONIOENCODING=utf-8 for every engine process. Windows python3 encodes stdout with the local code page (cp1252 / cp936) unless told otherwise, so any engine step that printed Chinese raised UnicodeEncodeError mid-run — CI's Windows legs caught it in android_agp_effective_version (the keep 9.3.2 (requested 8.12.0 跨 AGP 大版本) line). macOS and Linux were already UTF-8, so the export is a no-op there.
The test suite is portable again. The JSONC round-trip assertion now reads the normalized file with grep instead of a python3 -c that embedded a POSIX /tmp/... path (MSYS does not rewrite a path that sits inside a code string, so native Python could not open it), and the download-destination test builds its expectation from os.tmpdir() instead of a hardcoded /tmp/dl (on Windows path.join('/tmp/dl', 'a.apk') is D:\tmp\dl\a.apk). pnpm-lock.yaml also caught up with workspace:^0.6.3, without which CI's pnpm install --frozen-lockfile aborted with ERR_PNPM_OUTDATED_LOCKFILE.
v0.6.3 — 经典 uni-app 也能本地打 Android 包 · 面板证书板块 · 安装包下载
Added
-
An installer that is already archived can be downloaded from the panel, and where it lands is one setting. Every row of the Upload card (which reads exactly what
<home>/packages/<Platform>/*-latest.jsonnames) gains a Download button that posts{platform, project, dir}toPOST /api/app-packager/download; the host finds that artifact withlistArtifacts(home)and copies it withfs.promises.copyFile, answering{ok, platform, project, saved, dir, size}, and the panel prints the saved path under the row. The destination is the card's new Download folder row: an input pre-filled fromstate.downloadDir(the host'sDEFAULT_DOWNLOAD_DIR, i.e.~/Downloads), a Choose folder… button that reusesPOST /api/app-packager/pick, and a hint explaining that a blank value falls back to the default.downloadDestination(dir, fileName)expands a leading~, treats a blank value as the default, and never overwrites: an existing name gets-1,-2, … appended to its stem, so downloading the same build twice keeps both files. The folder is remembered per browser inlocalStorage['ap-download-dir']and nowhere else.plugin.test.mjspins the path helper (blank /~/ collision) and does a real copy twice to prove the second one lands as…-1.apkand that a missing artifact rejects. -
Classic uni-app projects (uni-app + Vue, i.e. no
uni-app-xkey inmanifest.json) build Android APKs locally now. Newproject_is_uniappx()drives a flavor split through the Android pipeline, which until now assumed uni-app x step by step: the resource step runsHBuilderX publish app --type appResourceand readsunpackage/resources/<appid>/wwwinstead ofpublish app-android+unpackage/resources/app-android;copy_android_resources()drops thatwwwinto<module>/src/main/assets/apps/<appid>/www(the HBuilder-Integrate-AS / HBuilder-HelloUniApp layout) and rewritesassets/data/dcloud_control.xmlto name the project's appid instead of copying uni-app x Kotlin sources intouniappx/;sync_android_permissions()falls back to theapp-pluspermission block when there is noapp-androidone;patch_android_project()also patches the"apk.applicationId"manifest placeholder; andapply_android_brand_assets()(which targetsuniappx/src/main/res) stays out of the way. Such a project keeps its own Gradle wrapper — the AGP 8 template cannot run on the cached Gradle 9, andcheck androidnow reportsGradle: 项目自带 Wrapper(经典 uni-app,构建时使用)instead of probing--version(which would trigger a 130 MB wrapper download) — and its own JDK: with a JDK 24+ default, Gradle 8 dies in semantic analysis (Unsupported class file major version 69), so the classic path selects JDK 21/17 through the newclassic_android_java_home()(ANDROID_CLASSIC_JAVA_HOMEinconfig/settings.envpins one explicitly) and says so in the log. Verified end to end on a classichello-uniappproject:com.example.hellouniapp1.0.0, 71 MB APK,assets/apps/__UNI__98CDDE3/www/app-service.jsin place, signed with the template's own test keystore. iOS and HarmonyOS remain uni-app x only — Android is the one platform with two working local paths, and HBuilderX cloud packaging (cli pack) is the alternative for classic projects.
Changed
- The panel no longer calls every project a "uni-app x" project, and neither does the engine. Fifteen strings that labelled generic behaviour as x-specific became
uni-app 项目(lib/common.sh,lib/runner.sh,lib/init.sh,src/cli.mjs,src/doctor.mjs), andapp-packager --helpnow titles itselfuni-app / uni-app x 打包命令行. The strings that really are x-only were left alone: iOS local packaging still requiresUniAppXDemo.xcodeproj, HarmonyOS still installs the@dcloudio/uni-app-x-runtimeohpm package, and theShareWithSystembundle id is still an x-only detail.
Fixed
-
A JSONC
manifest.jsonno longer costs a classic build its whole permission set. The classic template ships/* … */comments inmanifest.json, and the strictjson.loadsin the full-permission merge (PY_FULL_PERMISSIONS) raisedJSONDecodeError— while the build carried on, becausesync_full_permissions()is called from anifcondition whereerrexitis suspended, so the failure never reacheddie. The result was a silent downgrade: the same project produced 69 permissions before the fix and 89 after, the full-permission profile (FULL_PERMISSION_PROFILE=true) having been dropped on the floor.manifest_jsonc_to_json()now runs inprepare_workspace()right after the project is copied into the isolated workspace: a string-aware scanner strips//and/* */comments and trailing commas, writes the file back only when the result actually parses, and leaves anything it cannot clean untouched —"url": "http://…"keeps its slashes, the business project's own file is never modified, and the panel log printsmanifest.json 已规范化为标准 JSONwhen something was normalized.engine.test.shpins normalization, the in-string//, idempotence, and the untouched broken input. -
A
$variableimmediately followed by a Chinese character no longer aborts a build withunbound variable. bash parses the multibyte character as part of the name, solog "…($flavor,静默模式)"dereferenced a variable actually namedflavor,andset -ukilled the first classic Android build atcommon.sh:1904before HBuilderX was even called.${flavor},${APP_ID}and (in the pgyer HAP path)${profile_distribution}now end their names explicitly — that last one was a real pre-existing bug, the warning branch for a mismatched distribution type could never print.engine.test.shwalks every engine shell file for$namedirectly followed by a non-ASCII byte, so the class cannot come back.
v0.6.2 — 面板重构 · 打包与上传分离 · 发布流水线
AppPackager 0.6.2 —— 面板重构、打包与上传分成两条流程。
npm 已发布(2026-10-11):app-packager@0.6.2、dsh-app-packager@0.6.2。
原计划由 GitHub Actions 的 trusted publishing(OIDC)发布,但 GitHub 对 2026-07-15 之后创建的仓库强制使用不可变 OIDC subject(repo:owner@id/repo@id:ref:…),npm 侧仍按旧订阅格式匹配,换票一律 401(npm/cli#9969)。本轮改用 bypass-2FA granular token 直发;完整实测记录见仓库 docs/zh-CN/publishing.md 第一节,npm 修好后工作流即可恢复。
安装:dsh plugin --profile <profile> add dsh-app-packager
v0.6.1 — 一键配置可反复点,SDK 路径跟着引擎目录
Warning
这一版的面板会白屏,已作废,请勿安装。 面板拿到数据(GET state)时会抛 ReferenceError: Cannot access 'hb' before initialization —— client.js 里 SDK 卡片的派生 const 声明写在了卡片之后(时间死区),React 随即卸载整块面板。修复已进 0.6.2(见 CHANGELOG.md 的 [0.6.2] 一节),本页的两个 tarball 仅作历史留存与打包复现的对照件(sha256 与 0.6.2 的打包流程一致),仍然请勿安装;tag 同理。
[0.6.1] - 2026-10-10
Changed
- One-click setup is now repeatable and cheap (engine script version
2026.10.10.3).sdk installskips any platform that is alreadyreadywith an已就绪,跳过line, so pressing One-click setup again no longer re-downloads the 800 MB-class iOS archive;--fileis an explicit request and still runs. - The SDK paths persisted in
config/settings.local.envnow follow the engine directory. Paths inside the engine directory are written as"$PIPELINE_ROOT/...", so when the engine directory is renamed as a whole (for example~/AppPackager→<plugin>/home) the installed SDKs are still found instead of being reported asmissing.
Fixed
sdk installno longer reports success when nothing was installed (engine script version2026.10.10.2). The result is now decided by whether the platform is actuallyreadyafterwards, so declining the HarmonyOS prompt, a missing DevEcoohpm, or a failed download/unpack exits non-zero with a<platform> SDK 未就绪line instead of printingSDK 配置完成and exiting 0.- A plugin upgrade now actually refreshes the engine copy in
<plugin>/home. The host only materialized the engine when the home was missing entirely, so a home created by an older plugin kept running its old scripts (a 0.6.0 home ran the engine without thesdk installfixes above). Materialization now runs on every call and no-ops while.engine-versionmatches, so the scripts are refreshed in place and user-owned files (config/*.local.env,config/projects/*.env,certificates/,sdk/) are still never overwritten. The panel reports the version actually on disk and shows目录里是 <version>,需要刷新instead of a blanket Ready while the copy is stale.
Install
From npm (once 0.6.1 is published — tracked in docs/publishing.md):
dsh plugin --profile <profile> add dsh-app-packagerOffline, from the assets below (CLI first, the plugin depends on it):
npm i -g ./app-packager-0.6.1.tgz
dsh plugin --profile <profile> add ./dsh-app-packager-0.6.1.tgzPublishing goes through GitHub Actions + npm trusted publishing (.github/workflows/publish.yml, id-token: write, no npm token anywhere); configure the trusted publisher once per package on npmjs.com (repository lw0129a/dsh-app-packager, workflow publish.yml). A v* tag push or a manual "Run workflow" then publishes, and re-runs skip versions that are already on the registry.
v0.6.0 — HBuilderX 版本感知的 SDK 一键配置,引擎目录进插件
[0.6.0] - 2026-10-10
Added
-
The panel reads your HBuilderX version and recommends the matching SDK for iOS, Android and HarmonyOS — then installs it for you. The engine's new
sdksubcommand reports the detected HBuilderX app/CLI and its version series, each platform's SDK directory and state (ready/mismatch/missing), the official download page and the file name to look for; because the iOS archive name is derivable from the series alone the engine also hands out that direct URL, while Android's file name carries a build number so the engine resolves it from DCloud's official page, and HarmonyOS has no archive at all (its runtime comes from ohpm). One-click setup then runs the engine's own download/unpack path per platform, Process downloaded SDKs imports archives already sitting insdk/, and the card lists what is waiting there plus how many Baidu-Netdisk downloads are still incomplete. Nothing is installed behind your back: the card only recommends until you press the button, and a platform whose SDK is alreadyreadyis left alone. -
Engine:
sdksubcommand (engine script version2026.10.10.1) —sdk status(JSON, for the panel and other tools),sdk urls(the same thing for humans),sdk install <ios|android|harmony|all> [--yes] [--file <archive>]andsdk process.installreuses the existingresolve_direct_sdk_url(official page → direct link) andimport_sdk_archive(unpack, place, rewriteconfig/settings.local.env), prints the official download page when a platform has no deterministic link, and takes a locally downloaded archive through--filefor machines that cannot reach the download host. -
The engine directory now lives inside the plugin, and upgrading the plugin keeps it.
~/AppPackagerstill works: the first resolution renames it to<plugin>/home— a rename, never a copy, so multi-GB SDKs move instantly — after whichAPP_PACKAGER_HOMEor thehomeconfig key can point anywhere else. The new Upgrade plugin button runsdsh plugin --profile <name> add dsh-app-packager@latest(orpnpm add) with the engine directory temporarily renamed out of the way, so reinstalling cannot delete your downloaded SDKs, certificates or registered projects; and a profile that runs the plugin from somewhere other thannode_modulessimply hides the button and says why. -
packages/dsh-app-packager/index.mjsholds the path resolution both halves share (resolvePluginHome,withHomePreserved,pluginRoot,looksLikeEngineHome), so the host tools, the panel backend and the upgrade wrapper cannot disagree about where the engine directory is. -
Publishing now runs in CI through npm trusted publishing (OIDC).
.github/workflows/publish.ymlpublishes both tarballs on av*tag (or on demand from the Actions tab /gh workflow run publish.yml) withid-token: writeand no npm token anywhere, so a 2FA account needs neither an OTP nor a manual approval of a staged package. Configure the trusted publisher once per package on npmjs.com (repositorylw0129a/dsh-app-packager, workflowpublish.yml).
Fixed
-
harmony_sdk_readyno longer calls a half-installed HarmonyOS runtime ready. It acceptedoh-package.json5as proof, but the installer writes that manifest before runningohpm install, so a failed install counted as ready and the HarmonyOS build went ahead without a runtime. Readiness is now onlyoh_modules/@dcloudio/uni-app-x-runtime, its vapor counterpart, or a*.harin the directory. -
The engine test suite ran against the repository instead of its temporary root. It symlinked
lib/into the temp root, and sincelib/init.shrecomputesPIPELINE_ROOTfrom its own location, anything sourced through it resolved back to the checkout — the SDK cases wrote their fixtures intopackages/app-packager/engine/sdk/. The harness copieslib/now, soPIPELINE_ROOTis really the temp root. -
A test can no longer delete a real engine directory.
resolvePluginHomeonly migrates a directory that is actually an engine home (.engine-versionor打包工具.commandpresent), and the legacy path is an explicit option instead of being derived from$HOMEinside the test, so a test that fakes a home cannot reach the real one even if the runtime ignores the fake. -
The Upgrade plugin button no longer walks the plugin backwards. When the plugin is installed from a local tarball, the registry's
latestcan be older than what is on the machine (today:0.2.0against a local0.6.0), andadd dsh-app-packager@latestwould quietly replace it — dropping the SDK card and the new engine. The upgrade now reads the installed version, compares it with the registry and stops with a message when there is nothing newer;node <plugin>/upgrade.mjs --forcestill reinstalls on demand. Verified against the real profile install (0.2.0on the registry,0.6.0installed, engine home untouched).
Install (offline tarballs, attached): app-packager-0.6.0.tgz + dsh-app-packager-0.6.0.tgz — install both together, the plugin depends on the engine package:
pnpm add ./dsh-app-packager-0.6.0.tgz ./app-packager-0.6.0.tgznpm: 0.6.0 is not on the registry yet. Publishing runs in CI through npm trusted publishing (OIDC) — .github/workflows/publish.yml uses id-token: write, no npm token, no OTP and no staged-package approval. One-time setup per package on npmjs.com (Settings → Trusted Publisher → GitHub Actions: user lw0129a, repository dsh-app-packager, workflow publish.yml); the maintainer's npm account also has to be out of its temporary suspension first. After that, pushing a v* tag (or running the workflow from the Actions tab) publishes both packages, and a re-run skips whatever version is already on the registry.