dshm-compat 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INSTALL.md ADDED
@@ -0,0 +1,57 @@
1
+ # Installing dshm-compat
2
+
3
+ The artifact is a normal DSH bundle package: `dshm-compat-0.1.0.tgz`.
4
+ It is host-only, has no runtime dependencies, and adds eight agent tools.
5
+
6
+ ## A. DSHM (the tablet)
7
+
8
+ ### A1. npm or GitHub (recommended)
9
+
10
+ Publish the package to npm (remove `"private": true` from `package.json`
11
+ first), or push this directory to a GitHub repository, then in DSHM:
12
+
13
+ 1. Open **Settings → Plugins → Add plugin**.
14
+ 2. Enter `dshm-compat` (npm) or the GitHub repository URL.
15
+ 3. Install, enable the bundle, and restart DSHM if the app asks for it.
16
+ 4. Confirm the plugin card shows **DSHM Plugin Compatibility**.
17
+
18
+ ### A2. HTTPS tarball
19
+
20
+ Put `dshm-compat-0.1.0.tgz` on any HTTPS host (GitHub Release, Gitee Release,
21
+ object storage) and paste the `.tgz` URL into the same Add plugin input. DSHM's
22
+ installer accepts tarball specs; if the current market UI refuses a plain URL,
23
+ use A1.
24
+
25
+ ### A3. hdc / local path
26
+
27
+ If `hdc` is available and the app can read the path:
28
+
29
+ ```sh
30
+ hdc file send dshm-compat-0.1.0.tgz /data/storage/el2/base/haps/entry/files/dshm-compat-0.1.0.tgz
31
+ ```
32
+
33
+ Then enter the absolute path (or `file:/...`) in the Add plugin input. This
34
+ depends on the DSHM build's local-file support; A1 is safer.
35
+
36
+ ## B. Desktop DSH (for testing only)
37
+
38
+ ```powershell
39
+ & 'D:\安装\dsh-desktop\resources\runtime\cli\bin\dsh.cmd' plugin --profile desktop add 'D:\dsh\dsh-26-09-30\dshm-compat\dshm-compat-0.1.0.tgz'
40
+ ```
41
+
42
+ If the profile reports `ERR_PNPM_UNEXPECTED_VIRTUAL_STORE`, its `node_modules`
43
+ was migrated from another DSH home; repair that profile first or use a fresh
44
+ profile.
45
+
46
+ ## C. After installation
47
+
48
+ - The bundle row is `dshm-compat`; enable it if it is disabled.
49
+ - Ask the agent to run `compat_status`; it should return the data directory and
50
+ empty report list.
51
+ - Then run `compat_analyze`, `compat_plan`, `compat_convert`, `compat_install`.
52
+ - No market input interception is included; the tools are the entry point.
53
+
54
+ ## D. Uninstall
55
+
56
+ Remove `dshm-compat` from the plugin market/plugin manager. Its data lives under
57
+ `$DSH_HOME/dshm-compat` and is not removed automatically.
package/README.md CHANGED
@@ -1,3 +1,85 @@
1
- # Temporary Holding Version
1
+ # dshm-compat
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Analyze DSH plugins that cannot install or run on HarmonyOS DSHM, then convert
4
+ them through the least invasive strategy that can work.
5
+
6
+ ## Current phase
7
+
8
+ Phase 1 ships the analyzer, the S0/S1 converter, the dsh-ops recipe, guest
9
+ probing/provisioning, and the DSHM install-queue transport:
10
+
11
+ - `compat_analyze` downloads an npm/GitHub/local package and classifies it.
12
+ - `compat_plan` returns the concrete conversion plan without changing anything.
13
+ - `compat_scan` triages up to 50 specs in parallel and returns a strategy table.
14
+ - `compat_convert` rewrites an S0 package's manifest (foreign `os`/`cpu` gates,
15
+ install lifecycle scripts, client platform), vendors missing pure-JS runtime
16
+ dependencies into a flat `node_modules`, validates the resulting tarball, and
17
+ repacks it as a DSHM tarball; for S1 it removes a native dependency that the
18
+ DSHM core already provides. For `dsh-ops` it applies the 0.3.x recipe once an
19
+ AArch64 Linux worker is supplied via `workerPath`; without one it returns
20
+ `needs-native-build` with the exact cross-build command.
21
+ - `compat_guest_probe` reports the Alpine guest kernel/packages/tools.
22
+ - `compat_guest_provision` installs a reviewed allowlist of Alpine packages in
23
+ the guest (`confirm:true` required).
24
+ - `compat_status` lists stored reports and install attempts.
25
+ - `compat_install` submits an already-built tarball to the host install queue
26
+ (`confirm:true` required).
27
+
28
+ The converter deliberately stops at the manifest/repack/vendor boundary:
29
+ source transpilation and native builds are later phases. A report saying
30
+ `S2`/`S3`/`S4` is returned as-is rather than being reported as a successful
31
+ conversion.
32
+
33
+ See [architecture](docs/ARCHITECTURE.md) and [conversion recipes](docs/RECIPES.md).
34
+
35
+ ## CLI
36
+
37
+ The same converter runs on a PC without installing the DSH plugin:
38
+
39
+ ```sh
40
+ node bin/dshm-compat.mjs analyze dsh-m
41
+ node bin/dshm-compat.mjs plan dsh-ops
42
+ node bin/dshm-compat.mjs convert dsh-cache-billing --no-vendor
43
+ node bin/dshm-compat.mjs convert dsh-ops --worker ./dsh-ops-worker
44
+ node bin/dshm-compat.mjs scan dsh-m dsh-ops some-plugin
45
+ ```
46
+
47
+ `convert` writes the tarball under `$DSH_HOME/dshm-compat/builds` (override with
48
+ `DSHM_COMPAT_DIR`).
49
+
50
+ ## Test
51
+
52
+ ```sh
53
+ node test/run.mjs
54
+ ```
55
+
56
+ The harness builds synthetic tarballs under `_tmp`, uses a fake DSHM core, and
57
+ never touches a real DSH profile.
58
+
59
+ Strategy codes:
60
+
61
+ | Code | Meaning |
62
+ |---|---|
63
+ | `S0` | pure JS/TS; code-level conversion on the host |
64
+ | `S1` | known native dependency with an OHOS/JS substitute |
65
+ | `S2` | native binary or shell command; guest Alpine sidecar |
66
+ | `S3` | source-available native; WASM or remote build |
67
+ | `S4` | unsupported, e.g. a win32-only binary with no source |
68
+
69
+ ## Install
70
+
71
+ Install the package directory as a DSH bundle in the target profile. On DSHM,
72
+ the package can be packed and submitted through the install queue once the
73
+ converter phase is in place.
74
+
75
+ ## Data
76
+
77
+ `$DSH_HOME/dshm-compat/`
78
+
79
+ - `reports/<fingerprint>.json` — full analyzer report.
80
+ - `state.json` — bounded report/install index.
81
+
82
+ ## Safety
83
+
84
+ The analyzer never executes install scripts. `compat_install` runs third-party
85
+ code only after the caller passes `confirm:true`.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone PC-side entry for dshm-compat.
4
+ *
5
+ * The same modules run inside DSHM, but a desktop checkout can convert a
6
+ * package and hand the tarball to the device. No dependencies are required.
7
+ */
8
+ import { analyze } from '../lib/analyze.js'
9
+ import { scan } from '../lib/batch.js'
10
+ import { convert } from '../lib/convert.js'
11
+ import { buildPlan } from '../lib/plan.js'
12
+ import { status } from '../lib/state.js'
13
+
14
+ const USAGE = `dshm-compat <command> [args]
15
+
16
+ commands:
17
+ analyze <spec> classify one npm/GitHub/local package
18
+ plan <spec> print the conversion plan
19
+ convert <spec> [--no-vendor] [--worker <path>]
20
+ write a converted tarball
21
+ scan <spec> [<spec> ...] classify several packages
22
+ status show the local compatibility data directory
23
+
24
+ spec: npm name[@version], https://github.com/owner/repo, or a local .tgz path
25
+ `
26
+
27
+ const args = process.argv.slice(2)
28
+ const command = args[0]
29
+ const rest = args.slice(1)
30
+
31
+ /** @param {string} message */
32
+ function fail(message) {
33
+ console.error(message)
34
+ process.exitCode = 1
35
+ }
36
+
37
+ if (command === undefined || command === '--help' || command === '-h') {
38
+ console.log(USAGE)
39
+ } else if (command === 'analyze') {
40
+ if (rest[0] === undefined) fail('analyze requires a spec')
41
+ else {
42
+ try {
43
+ const report = await analyze(rest[0], { textScan: true })
44
+ console.log(JSON.stringify(report, null, 2))
45
+ } catch (error) { fail(String(error?.message ?? error)) }
46
+ }
47
+ } else if (command === 'plan') {
48
+ if (rest[0] === undefined) fail('plan requires a spec')
49
+ else {
50
+ try {
51
+ const report = await analyze(rest[0], { textScan: true })
52
+ console.log(JSON.stringify(buildPlan(report), null, 2))
53
+ } catch (error) { fail(String(error?.message ?? error)) }
54
+ }
55
+ } else if (command === 'convert') {
56
+ if (rest[0] === undefined) fail('convert requires a spec')
57
+ else {
58
+ try {
59
+ const vendor = !rest.includes('--no-vendor')
60
+ const workerAt = rest.indexOf('--worker')
61
+ const workerPath = workerAt >= 0 ? rest[workerAt + 1] : undefined
62
+ const report = await analyze(rest[0], { textScan: false })
63
+ const result = await convert(rest[0], { report, vendor, workerPath })
64
+ console.log(JSON.stringify(result, null, 2))
65
+ if (result.status !== 'ok') process.exitCode = 1
66
+ } catch (error) { fail(String(error?.message ?? error)) }
67
+ }
68
+ } else if (command === 'scan') {
69
+ if (rest.length === 0) fail('scan requires at least one spec')
70
+ else {
71
+ try {
72
+ const result = await scan(rest)
73
+ console.log(JSON.stringify({
74
+ counts: result.counts,
75
+ items: result.items.map(({ report: _report, ...item }) => item),
76
+ }, null, 2))
77
+ } catch (error) { fail(String(error?.message ?? error)) }
78
+ }
79
+ } else if (command === 'status') {
80
+ console.log(JSON.stringify(status(), null, 2))
81
+ } else {
82
+ fail(`unknown command: ${command}\n\n${USAGE}`)
83
+ }
@@ -0,0 +1,6 @@
1
+ # dshm-compat: one host row. The row registers the compatibility analyzer and
2
+ # (in later phases) conversion/install tools. It never changes other rows.
3
+ - insert:
4
+ - id: dshm-compat
5
+ name: dshm-compat
6
+ config: {}
@@ -0,0 +1,92 @@
1
+ # dshm-compat architecture
2
+
3
+ `dshm-compat` is a DSH plugin that classifies and converts plugins that cannot
4
+ install or run on HarmonyOS DSHM. It is deliberately layered: a conversion that
5
+ would require a different runtime is reported as such, not disguised as a
6
+ manifest edit.
7
+
8
+ ## DSHM constraints that shape the design
9
+
10
+ 1. **No spawnable shell on the host.** The app sandbox rejects `execve` of
11
+ bundled tools. `dshm-vm-bash` redirects `bash -c` into an Alpine QEMU guest.
12
+ 2. **No host `dlopen` of profile native code.** `dshm-vm-bash`'s path notes say
13
+ `dlopen` only admits `/data/storage/el1/bundle/libs/arm64`; a `.node` placed
14
+ in a profile or plugin directory does not load. Native code must run in the
15
+ guest, or be WASM/pure JS.
16
+ 3. **The install path is a queue.** `$DSH_HOME/install-queue/<id>.req` is
17
+ consumed by the host's in-process installer, which writes `<id>.done` or
18
+ `<id>.fail` JSON. There is no package manager process to call.
19
+ 4. **The guest is a full Alpine 3.22 aarch64 userland** with 9p access to the
20
+ sandbox, QGA command execution, `apk`, and network through slirp. It is the
21
+ universal fallback for native code and shell commands.
22
+
23
+ ## Strategy layers
24
+
25
+ | Code | Layer | What runs where | Conversion |
26
+ |---|---|---|---|
27
+ | `S0` | code | host | rewrite manifest, repack; dependency vendoring and source builds later |
28
+ | `S1` | dependency | host | remove a native dependency that the DSHM core already provides as an OHOS port |
29
+ | `S2` | environment | Alpine guest | provision Node/npm/build tools, run or build the native artifact, proxy tools |
30
+ | `S3` | instruction | host/guest/remote | compile the addon to WASM with an N-API shim, or build/run remotely |
31
+ | `S4` | none | nowhere | report the blocking artifact honestly |
32
+
33
+ ## Pipeline
34
+
35
+ ```
36
+ spec ──► resolve (npm / GitHub / local .tgz)
37
+ ──► extract (ustar/pax, no dependency)
38
+ ──► analyze (manifest, native files, scripts, platform/Electron scan, core inventory)
39
+ ──► plan (S0..S4 + concrete steps)
40
+ ──► convert (S0/S1 now; S2+ is a plan)
41
+ ──► pack (ustar writer)
42
+ ──► install queue (DSHM only)
43
+ ```
44
+
45
+ ## Modules
46
+
47
+ | Module | Responsibility |
48
+ |---|---|
49
+ | `lib/fetch.js` | resolve npm specs, GitHub repos, local archives; bounded downloads |
50
+ | `lib/tar.js` | ustar/pax extraction with traversal and size guards |
51
+ | `lib/analyze.js` | classification evidence, strategy, confidence, next action |
52
+ | `lib/core.js` | locate `$DSH_HOME/cores/<version>/node_modules` and compare runtime dependencies |
53
+ | `lib/rules.js` | exact recipes for known packages (`dsh-ops`) |
54
+ | `lib/convert.js` | S0 manifest rewrite/repack and vendoring; S1 native-dependency removal |
55
+ | `lib/vendor.js` | flat `node_modules` vendoring for missing pure-JS dependencies |
56
+ | `lib/validate.js` | post-conversion manifest/entry/dependency validation |
57
+ | `lib/plan.js` | user-visible conversion plan for every strategy |
58
+ | `lib/guest.js` | read-only Alpine guest probe through `subprocess` |
59
+ | `lib/install.js` | DSHM install-queue producer with polling and cancellation |
60
+ | `lib/state.js` | bounded report/install index and atomic writes |
61
+ | `lib/pack.js` | dependency-free ustar/gzip writer |
62
+
63
+ ## Tool surface
64
+
65
+ | Tool | Side effects |
66
+ |---|---|
67
+ | `compat_analyze` | network download; writes a report under `$DSH_HOME/dshm-compat` |
68
+ | `compat_plan` | network download; writes a report; no package changes |
69
+ | `compat_scan` | network downloads; writes one report per successful spec |
70
+ | `compat_convert` | writes a converted tarball; optionally submits it to the install queue |
71
+ | `compat_guest_probe` | read-only guest command |
72
+ | `compat_guest_provision` | installs allowlisted Alpine packages in the guest |
73
+ | `compat_install` | writes one install-queue request; runs third-party code |
74
+ | `compat_status` | read-only |
75
+
76
+ ## Phase status
77
+
78
+ - **Phase 1 (done):** analyzer, batch triage, S0 manifest converter + flat
79
+ dependency vendoring + output validation, S1 manifest substitution,
80
+ dependency preflight, guest probe/provisioning, worker execution primitive,
81
+ dsh-ops 0.3.x recipe (external AArch64 worker), install queue, offline tests.
82
+ - **Phase 2 (next):** on-device guest verification, dsh-ops worker build, nested
83
+ version-conflict vendoring, sidecar RPC.
84
+ - **Phase 3:** WASM/remote fallback, market UI entry, compatibility registry
85
+ updates.
86
+
87
+ ## Non-goals
88
+
89
+ - Translating a win32-only binary into OHOS code.
90
+ - Loading profile `.node` files in the host.
91
+ - Running arbitrary `postinstall` scripts on the host.
92
+ - Claiming S2/S3/S4 conversions succeeded when only a plan exists.
@@ -0,0 +1,140 @@
1
+ # Conversion recipes
2
+
3
+ These are the concrete recipes behind `S1`/`S2` plans. They are intentionally
4
+ written as patches to the upstream package, not as claims that the current
5
+ converter executes them.
6
+
7
+ ## `dsh-ops` (Rust native worker)
8
+
9
+ Upstream: `T-Auto/dsh-ops`. The core is `native/` (Rust) and the published
10
+ worker is `runtime/<target>/dsh-ops-worker`.
11
+
12
+ ### 1. Support matrix
13
+
14
+ `lib/ptc/runtime.js` hard-codes:
15
+
16
+ ```js
17
+ const PLATFORMS = ['win32-x64', 'linux-x64', 'darwin-arm64']
18
+ ```
19
+
20
+ `resolveWorker()` checks this matrix **before** considering
21
+ `DSH_OPS_WORKER_BIN`, so a binary path override alone cannot unblock DSHM.
22
+
23
+ Patch:
24
+
25
+ - add `linux-arm64` to `PLATFORMS`;
26
+ - map an OHOS `process.platform` to `linux-arm64` before the matrix check;
27
+ - keep `workerName()` returning `dsh-ops-worker` on non-win32;
28
+ - look for `runtime/linux-arm64/dsh-ops-worker`.
29
+
30
+ Also update:
31
+
32
+ - `package.json` `os`/`cpu` (remove the gates or add `openharmony`/`arm64`);
33
+ - `dsh-plugin.json` `platforms`, `architectures`, and `targets` for each
34
+ `x-components` entry.
35
+
36
+ ### 2. Build the worker
37
+
38
+ From a Linux build host or CI:
39
+
40
+ ```sh
41
+ rustup target add aarch64-unknown-linux-musl
42
+ # either
43
+ cross build --release --locked --manifest-path native/Cargo.toml --target aarch64-unknown-linux-musl
44
+ # or
45
+ cargo zigbuild --release --locked --manifest-path native/Cargo.toml --target aarch64-unknown-linux-musl
46
+ ```
47
+
48
+ The Windows/macOS metadata crates are target-gated in `native/Cargo.toml`; the
49
+ portable dependencies are `cap-std`, `cap-fs-ext`, `fs2`, `rustix`, `sha2`,
50
+ `serde`, `serde_json`, `uuid`, `regex`.
51
+
52
+ Stage the result as `runtime/linux-arm64/dsh-ops-worker` with a matching
53
+ `provenance.json` (`version`, `target`, `executable`, `sha256`).
54
+
55
+ ### 3. Spawn through the guest
56
+
57
+ `lib/file/worker.js` and `lib/ptc/worker.js` call:
58
+
59
+ ```js
60
+ subprocess.spawn({ argv: [file], ... })
61
+ ```
62
+
63
+ `dshm-vm-bash` only redirects `bash -c`, so patch both call sites to:
64
+
65
+ ```js
66
+ argv: ['bash', '-c', 'exec "$0"', file]
67
+ ```
68
+
69
+ The worker path is under the profile's 9p share, so the guest sees the same
70
+ absolute path. If exec from 9p fails on a device, copy the worker to
71
+ `/tmp/dsh-ops-worker` once (hash-keyed) and execute that copy.
72
+
73
+ ### 4. Shell component
74
+
75
+ `lib/shells/resolve.js` treats every non-win32 host as POSIX and probes the
76
+ **host** for `/bin/bash`. DSHM's host has no bash. Patch the DSHM branch to
77
+ report the guest's `/bin/bash` as available; `ops_bash` then spawns
78
+ `bash -c`, which `dshm-vm-bash` routes into the guest.
79
+
80
+ Disable the PowerShell rung: the pwsh payload is win32-only.
81
+
82
+ ### 5. PTC vs file
83
+
84
+ `lib/ptc/worker.js` calls `sandbox.confine()` for non-`danger-full-access`
85
+ policies. If DSHM's sandbox provider cannot confine the worker, PTC tools fail
86
+ closed. The `file` component uses policy checks but no `sandbox.confine`, so
87
+ port `ops_*` file tools first and treat PTC as a later optimization.
88
+
89
+ ### 6. Verification
90
+
91
+ Run on the device:
92
+
93
+ - `ops_stat` on a workspace file;
94
+ - `ops_grep` over a small tree;
95
+ - `ops_write_file` then `ops_stat` (check the new revision);
96
+ - `ops_copy` / `ops_move` (9p rename/atomicity);
97
+ - a failure case with a read-only policy.
98
+
99
+ Watch for `cap-std`/`rustix` failures on 9p (`openat2`, `statx`, `renameat2`,
100
+ `flock`). If those fail, the fallback is a JS reimplementation of the worker
101
+ protocol rather than a guest port.
102
+
103
+ ## Common native dependencies
104
+
105
+ | Dependency | DSHM route |
106
+ |---|---|
107
+ | `sharp` | DSHM core ships an OpenHarmony port; remove the plugin's dependency so the core resolves it (S1) |
108
+ | `koffi` | same as `sharp` |
109
+ | `node-pty` | same as `sharp`; DSHM's terminal backend already uses it |
110
+ | `better-sqlite3` / `sqlite3` | replace calls with `node:sqlite` (Node 24) or run in the guest |
111
+ | `canvas` | no core port; guest or WASM |
112
+ | `esbuild` | `esbuild-wasm` on the host or `esbuild` in the guest |
113
+ | `bcrypt` | `bcryptjs` |
114
+ | `argon2` | `hash-wasm` |
115
+
116
+ ## Electron API shim
117
+
118
+ For host plugins that import `electron` only for platform glue:
119
+
120
+ | Electron | DSHM equivalent |
121
+ |---|---|
122
+ | `app.getPath(...)` | DSH home/paths service |
123
+ | `shell.openExternal` / `openPath` | `open-in-app` |
124
+ | `dialog.showOpenDialog` | directory picker |
125
+ | `clipboard` | ArkWeb clipboard API |
126
+ | `ipcMain` / `ipcRenderer` | DSH client connection / Cordis events |
127
+ | `BrowserWindow` | not shimmed; reimplement as a DSH client plugin or run remotely |
128
+
129
+ ## Shell commands
130
+
131
+ A plugin that calls `child_process` directly will not be redirected by
132
+ `dshm-vm-bash`. Two options:
133
+
134
+ 1. Rewrite the import in the bundled host code to DSH's `subprocess` service
135
+ (which routes `bash -c` to the guest).
136
+ 2. Run the whole plugin host half in the guest and proxy its tools over RPC.
137
+
138
+ The guest has `apk`; ffmpeg, git, and Python can be installed offline by
139
+ downloading the Alpine packages on the host, placing them in the 9p share, and
140
+ running `apk add --no-network`.
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="DSHM compatibility">
2
+ <rect x="3" y="3" width="58" height="58" rx="14" fill="#1f2937"/>
3
+ <path d="M18 22h12l5 6-5 6H18l5-6-5-6Z" fill="#60a5fa"/>
4
+ <path d="M34 30h12l-5 6 5 6H34l-5-6 5-6Z" fill="#f59e0b" opacity=".95"/>
5
+ <path d="M14 45h36" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
6
+ </svg>