dshm-compat 0.1.3 → 0.1.5
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/AGENTS.md +17 -0
- package/CHANGELOG.md +75 -9
- package/INSTALL.md +6 -6
- package/README.md +39 -15
- package/assets/qemu-x86_64/NOTICE.md +17 -0
- package/assets/qemu-x86_64/qemu-x86_64 +0 -0
- package/assets/qemu-x86_64/rootfs/lib/x86_64-linux-gnu/libc.so.6 +0 -0
- package/assets/qemu-x86_64/rootfs/lib/x86_64-linux-gnu/libgcc_s.so.1 +0 -0
- package/assets/qemu-x86_64/rootfs/lib64/ld-linux-x86-64.so.2 +0 -0
- package/docs/ARCHITECTURE.md +18 -8
- package/docs/RECIPES.md +79 -91
- package/index.js +104 -1
- package/lib/analyze.js +9 -1
- package/lib/convert.js +58 -4
- package/lib/elf.js +41 -0
- package/lib/guest.js +6 -3
- package/lib/plan.js +10 -0
- package/lib/release.js +185 -0
- package/lib/rules.js +7 -22
- package/lib/s2-native.js +316 -0
- package/lib/sidecar.js +185 -0
- package/lib/source-build.js +165 -0
- package/lib/uninstall.js +94 -0
- package/lib/zip.js +59 -0
- package/package.json +3 -1
- package/lib/recipes/dsh-ops.js +0 -209
package/lib/uninstall.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile-local uninstall.
|
|
3
|
+
*
|
|
4
|
+
* Removes a package from the owning profile: its `node_modules` directory, the
|
|
5
|
+
* `dependencies` entry, and the `dsh.profile.bundles` entry. The profile
|
|
6
|
+
* manifest is only changed after the files are moved aside, so a failed delete
|
|
7
|
+
* can be rolled back.
|
|
8
|
+
*
|
|
9
|
+
* @module dshm-compat/uninstall
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
12
|
+
import { join } from 'node:path'
|
|
13
|
+
import { resolveProfileDir } from './local-install.js'
|
|
14
|
+
|
|
15
|
+
/** @param {unknown} value */
|
|
16
|
+
function record(value) {
|
|
17
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @param {string} file */
|
|
21
|
+
function readJson(file) {
|
|
22
|
+
try { return JSON.parse(readFileSync(file, 'utf8').replace(/^\uFEFF/, '')) } catch { return undefined }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @param {string} file @param {unknown} value */
|
|
26
|
+
function writeJsonAtomic(file, value) {
|
|
27
|
+
const temporary = `${file}.tmp-${process.pid}-${Date.now()}`
|
|
28
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
|
|
29
|
+
renameSync(temporary, file)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} name
|
|
34
|
+
* @param {{ profileDir?: string, env?: NodeJS.ProcessEnv }} [options]
|
|
35
|
+
*/
|
|
36
|
+
export function uninstallLocalPackage(name, options = {}) {
|
|
37
|
+
const env = options.env ?? process.env
|
|
38
|
+
if (typeof name !== 'string' || !/^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/i.test(name)) {
|
|
39
|
+
return { ok: false, error: `invalid package name: ${String(name)}` }
|
|
40
|
+
}
|
|
41
|
+
const profileDir = typeof options.profileDir === 'string' && options.profileDir !== ''
|
|
42
|
+
? options.profileDir
|
|
43
|
+
: resolveProfileDir(env)
|
|
44
|
+
if (profileDir === undefined) return { ok: false, error: 'cannot locate the DSH profile; pass profileDir explicitly' }
|
|
45
|
+
const manifestPath = join(profileDir, 'package.json')
|
|
46
|
+
const manifest = record(readJson(manifestPath))
|
|
47
|
+
if (manifest === undefined) return { ok: false, error: `profile manifest is not an object: ${manifestPath}` }
|
|
48
|
+
|
|
49
|
+
const destination = join(profileDir, 'node_modules', ...name.split('/'))
|
|
50
|
+
const dependencies = record(manifest.dependencies) ?? {}
|
|
51
|
+
const hadDependency = Object.prototype.hasOwnProperty.call(dependencies, name)
|
|
52
|
+
const dsh = record(manifest.dsh) ?? {}
|
|
53
|
+
const profile = record(dsh.profile) ?? {}
|
|
54
|
+
const bundles = Array.isArray(profile.bundles) ? profile.bundles.filter(item => typeof item === 'string') : []
|
|
55
|
+
const hadBundle = bundles.includes(name)
|
|
56
|
+
if (!hadDependency && !hadBundle && !existsSync(destination)) {
|
|
57
|
+
return { ok: true, name, removed: false, profileDir, restartRequired: false, message: 'not installed in this profile' }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const backup = `${destination}.uninstall-${process.pid}-${Date.now()}`
|
|
61
|
+
const originalManifest = JSON.parse(JSON.stringify(manifest))
|
|
62
|
+
let moved = false
|
|
63
|
+
try {
|
|
64
|
+
if (existsSync(destination)) {
|
|
65
|
+
renameSync(destination, backup)
|
|
66
|
+
moved = true
|
|
67
|
+
}
|
|
68
|
+
delete dependencies[name]
|
|
69
|
+
manifest.dependencies = dependencies
|
|
70
|
+
profile.bundles = bundles.filter(item => item !== name)
|
|
71
|
+
dsh.profile = profile
|
|
72
|
+
manifest.dsh = dsh
|
|
73
|
+
writeJsonAtomic(manifestPath, manifest)
|
|
74
|
+
if (moved) {
|
|
75
|
+
try { rmSync(backup, { recursive: true, force: true }) } catch { /* keep the backup if it is locked */ }
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
name,
|
|
80
|
+
removed: true,
|
|
81
|
+
profileDir,
|
|
82
|
+
destination,
|
|
83
|
+
removedDependency: hadDependency,
|
|
84
|
+
removedBundle: hadBundle,
|
|
85
|
+
restartRequired: true,
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
try { writeJsonAtomic(manifestPath, originalManifest) } catch { /* best effort */ }
|
|
89
|
+
if (moved && existsSync(backup)) {
|
|
90
|
+
try { renameSync(backup, destination) } catch { /* best effort */ }
|
|
91
|
+
}
|
|
92
|
+
return { ok: false, error: `uninstall failed: ${String(error?.message ?? error)}` }
|
|
93
|
+
}
|
|
94
|
+
}
|
package/lib/zip.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ZIP reader for GitHub release assets.
|
|
3
|
+
*
|
|
4
|
+
* Supports stored (0) and deflate (8) entries and the classic EOCD layout.
|
|
5
|
+
* It intentionally rejects ZIP64 and encrypted archives: release worker
|
|
6
|
+
* payloads are small, and a missing worker is better than guessing.
|
|
7
|
+
*
|
|
8
|
+
* @module dshm-compat/zip
|
|
9
|
+
*/
|
|
10
|
+
import { inflateRawSync } from 'node:zlib'
|
|
11
|
+
|
|
12
|
+
const EOCD = 0x06054b50
|
|
13
|
+
const CENTRAL = 0x02014b50
|
|
14
|
+
const LOCAL = 0x04034b50
|
|
15
|
+
|
|
16
|
+
/** @param {Buffer} buffer */
|
|
17
|
+
export function extractZip(buffer) {
|
|
18
|
+
if (!Buffer.isBuffer(buffer) || buffer.length < 22) throw new Error('not a ZIP archive')
|
|
19
|
+
let eocd = -1
|
|
20
|
+
const start = Math.max(0, buffer.length - 65557)
|
|
21
|
+
for (let i = buffer.length - 22; i >= start; i--) {
|
|
22
|
+
if (buffer.readUInt32LE(i) === EOCD) { eocd = i; break }
|
|
23
|
+
}
|
|
24
|
+
if (eocd < 0) throw new Error('ZIP end-of-central-directory not found')
|
|
25
|
+
const count = buffer.readUInt16LE(eocd + 10)
|
|
26
|
+
const centralOffset = buffer.readUInt32LE(eocd + 16)
|
|
27
|
+
if (centralOffset === 0xffffffff || count === 0xffff) throw new Error('ZIP64 is not supported')
|
|
28
|
+
|
|
29
|
+
/** @type {{ path: string, data: Buffer }[]} */
|
|
30
|
+
const entries = []
|
|
31
|
+
let offset = centralOffset
|
|
32
|
+
for (let index = 0; index < count; index++) {
|
|
33
|
+
if (offset + 46 > buffer.length || buffer.readUInt32LE(offset) !== CENTRAL) throw new Error('invalid ZIP central directory')
|
|
34
|
+
const method = buffer.readUInt16LE(offset + 10)
|
|
35
|
+
const compressedSize = buffer.readUInt32LE(offset + 20)
|
|
36
|
+
const uncompressedSize = buffer.readUInt32LE(offset + 24)
|
|
37
|
+
const nameLength = buffer.readUInt16LE(offset + 28)
|
|
38
|
+
const extraLength = buffer.readUInt16LE(offset + 30)
|
|
39
|
+
const commentLength = buffer.readUInt16LE(offset + 32)
|
|
40
|
+
const localOffset = buffer.readUInt32LE(offset + 42)
|
|
41
|
+
const name = buffer.subarray(offset + 46, offset + 46 + nameLength).toString('utf8')
|
|
42
|
+
offset += 46 + nameLength + extraLength + commentLength
|
|
43
|
+
if (name.endsWith('/')) continue
|
|
44
|
+
const normalized = name.replace(/\\/g, '/').replace(/^\.\//, '')
|
|
45
|
+
if (normalized.startsWith('/') || normalized.split('/').includes('..')) throw new Error(`unsafe ZIP path: ${name}`)
|
|
46
|
+
if (localOffset + 30 > buffer.length || buffer.readUInt32LE(localOffset) !== LOCAL) throw new Error(`invalid ZIP local header: ${name}`)
|
|
47
|
+
const localNameLength = buffer.readUInt16LE(localOffset + 26)
|
|
48
|
+
const localExtraLength = buffer.readUInt16LE(localOffset + 28)
|
|
49
|
+
const dataOffset = localOffset + 30 + localNameLength + localExtraLength
|
|
50
|
+
const compressed = buffer.subarray(dataOffset, dataOffset + compressedSize)
|
|
51
|
+
let data
|
|
52
|
+
if (method === 0) data = compressed
|
|
53
|
+
else if (method === 8) data = inflateRawSync(compressed)
|
|
54
|
+
else throw new Error(`unsupported ZIP compression method ${method} for ${name}`)
|
|
55
|
+
if (data.length !== uncompressedSize) throw new Error(`ZIP size mismatch for ${name}`)
|
|
56
|
+
entries.push({ path: normalized, data })
|
|
57
|
+
}
|
|
58
|
+
return entries
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dshm-compat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Analyze and convert DSH plugins that cannot be installed or run on HarmonyOS DSHM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -24,12 +24,14 @@
|
|
|
24
24
|
"index.js",
|
|
25
25
|
"bin",
|
|
26
26
|
"lib",
|
|
27
|
+
"assets",
|
|
27
28
|
"cordis.patch.yml",
|
|
28
29
|
"locale/*.json",
|
|
29
30
|
"icon.svg",
|
|
30
31
|
"README.md",
|
|
31
32
|
"INSTALL.md",
|
|
32
33
|
"CHANGELOG.md",
|
|
34
|
+
"AGENTS.md",
|
|
33
35
|
"docs/*.md"
|
|
34
36
|
],
|
|
35
37
|
"engines": {
|
package/lib/recipes/dsh-ops.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-ops native recipe.
|
|
3
|
-
*
|
|
4
|
-
* dsh-ops is the first S2 sample: its Rust worker supports Linux, but the
|
|
5
|
-
* published package carries a win32-only runtime and a support matrix that
|
|
6
|
-
* rejects every other target before `DSH_OPS_WORKER_BIN` is consulted. This
|
|
7
|
-
* recipe applies the minimal source patches and stages an externally built
|
|
8
|
-
* `aarch64-unknown-linux-musl` worker. It never builds the worker itself.
|
|
9
|
-
*
|
|
10
|
-
* @module dshm-compat/recipes/dsh-ops
|
|
11
|
-
*/
|
|
12
|
-
import { createHash } from 'node:crypto'
|
|
13
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
14
|
-
import { join } from 'node:path'
|
|
15
|
-
import { analyze } from '../analyze.js'
|
|
16
|
-
import { loadArchive, resolveSpec } from '../fetch.js'
|
|
17
|
-
import { packTarGz } from '../pack.js'
|
|
18
|
-
import { compatDir } from '../state.js'
|
|
19
|
-
import { extractTar, findRootPrefix } from '../tar.js'
|
|
20
|
-
import { validateTarball } from '../validate.js'
|
|
21
|
-
|
|
22
|
-
const BUILD_COMMAND = 'cross build --release --locked --manifest-path native/Cargo.toml --target aarch64-unknown-linux-musl'
|
|
23
|
-
const TARGET = 'aarch64-unknown-linux-musl'
|
|
24
|
-
|
|
25
|
-
/** @param {import('../tar.js').TarEntry[]} entries */
|
|
26
|
-
function selectRoot(entries) {
|
|
27
|
-
const candidates = entries
|
|
28
|
-
.filter(entry => entry.type === 'file' && (entry.path === 'package.json' || entry.path.endsWith('/package.json')))
|
|
29
|
-
.map(entry => ({ dir: entry.path === 'package.json' ? '' : entry.path.slice(0, -'/package.json'.length), depth: entry.path.split('/').length }))
|
|
30
|
-
.sort((a, b) => a.depth - b.depth)
|
|
31
|
-
return candidates.length === 0 ? '' : (candidates[0].dir === '' ? '' : `${candidates[0].dir}/`)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** @param {Buffer} buffer */
|
|
35
|
-
function isAarch64Elf(buffer) {
|
|
36
|
-
return buffer.length >= 64
|
|
37
|
-
&& buffer[0] === 0x7f && buffer[1] === 0x45 && buffer[2] === 0x4c && buffer[3] === 0x46
|
|
38
|
-
&& buffer[4] === 2 && buffer[5] === 1
|
|
39
|
-
&& buffer.readUInt16LE(18) === 183
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {string} spec
|
|
44
|
-
* @param {{ report?: any, workerPath?: string, workerBinary?: Buffer, signal?: AbortSignal }} [options]
|
|
45
|
-
*/
|
|
46
|
-
export async function convertDshOps(spec, options = {}) {
|
|
47
|
-
const report = options.report ?? await analyze(spec, { textScan: false, signal: options.signal })
|
|
48
|
-
const packageName = report.package?.name ?? report.resolved?.name
|
|
49
|
-
const packageVersion = report.package?.version ?? report.resolved?.version
|
|
50
|
-
if (packageName !== 'dsh-ops') {
|
|
51
|
-
return { status: 'not-dsh-ops', message: 'the dsh-ops recipe only accepts the dsh-ops package' }
|
|
52
|
-
}
|
|
53
|
-
if (!String(packageVersion ?? '').startsWith('0.3.')) {
|
|
54
|
-
return { status: 'recipe-mismatch', message: `dsh-ops ${packageVersion} is outside the 0.3.x recipe; inspect the package before patching` }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let workerBinary = options.workerBinary
|
|
58
|
-
if (workerBinary === undefined && typeof options.workerPath === 'string') {
|
|
59
|
-
try { workerBinary = readFileSync(options.workerPath) } catch (error) {
|
|
60
|
-
return { status: 'needs-native-build', target: TARGET, command: BUILD_COMMAND, message: `cannot read worker: ${String(error?.message ?? error)}` }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (workerBinary === undefined) {
|
|
64
|
-
return {
|
|
65
|
-
status: 'needs-native-build',
|
|
66
|
-
target: TARGET,
|
|
67
|
-
command: BUILD_COMMAND,
|
|
68
|
-
message: 'dsh-ops needs a Linux arm64 worker; build it and pass workerPath to compat_convert',
|
|
69
|
-
notes: [
|
|
70
|
-
'install a Rust toolchain and a musl aarch64 cross linker, or use cross/cargo-zigbuild',
|
|
71
|
-
'stage the result at runtime/linux-arm64/dsh-ops-worker',
|
|
72
|
-
],
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (!isAarch64Elf(workerBinary)) {
|
|
76
|
-
return { status: 'invalid-worker', message: 'worker is not an AArch64 ELF shared/executable image' }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const resolved = await resolveSpec(spec, { signal: options.signal })
|
|
80
|
-
const archive = await loadArchive(resolved, { signal: options.signal })
|
|
81
|
-
const { entries } = extractTar(archive)
|
|
82
|
-
const prefix = findRootPrefix(entries)
|
|
83
|
-
const top = entries
|
|
84
|
-
.filter(entry => entry.type === 'file')
|
|
85
|
-
.map(entry => ({ ...entry, path: prefix !== '' && entry.path.startsWith(prefix) ? entry.path.slice(prefix.length) : entry.path }))
|
|
86
|
-
const root = selectRoot(top)
|
|
87
|
-
const files = top
|
|
88
|
-
.filter(entry => entry.path.startsWith(root))
|
|
89
|
-
.map(entry => ({ ...entry, path: entry.path.slice(root.length) }))
|
|
90
|
-
const byPath = new Map(files.map(file => [file.path, file]))
|
|
91
|
-
const getText = path => byPath.get(path)?.data?.toString('utf8')
|
|
92
|
-
|
|
93
|
-
const runtime = getText('lib/ptc/runtime.js')
|
|
94
|
-
if (typeof runtime !== 'string' || !runtime.includes("export const PLATFORMS = Object.freeze(['win32-x64'])")) {
|
|
95
|
-
return { status: 'recipe-mismatch', message: 'lib/ptc/runtime.js does not match the 0.3.x recipe' }
|
|
96
|
-
}
|
|
97
|
-
let patchedRuntime = runtime.replace(
|
|
98
|
-
"export const PLATFORMS = Object.freeze(['win32-x64'])",
|
|
99
|
-
"export const PLATFORMS = Object.freeze(['win32-x64', 'linux-arm64'])",
|
|
100
|
-
)
|
|
101
|
-
patchedRuntime = patchedRuntime.replaceAll('${platform}-${arch}', 'target')
|
|
102
|
-
const signature = 'export function resolveWorker(config = {}, { packageRoot = PACKAGE_ROOT, env = process.env, platform = process.platform, arch = process.arch } = {}) {'
|
|
103
|
-
if (!patchedRuntime.includes(signature)) return { status: 'recipe-mismatch', message: 'resolveWorker signature changed' }
|
|
104
|
-
patchedRuntime = patchedRuntime.replace(
|
|
105
|
-
signature,
|
|
106
|
-
`${signature}\n const target = platform === 'openharmony' || (platform === 'linux' && arch === 'arm64') ? 'linux-arm64' : \`\${platform}-\${arch}\``,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
const fileWorker = getText('lib/file/worker.js')
|
|
110
|
-
if (typeof fileWorker !== 'string' || !fileWorker.includes('const argv = [file]')) {
|
|
111
|
-
return { status: 'recipe-mismatch', message: 'lib/file/worker.js spawn call changed' }
|
|
112
|
-
}
|
|
113
|
-
const patchedFileWorker = fileWorker.replace(
|
|
114
|
-
'const argv = [file]',
|
|
115
|
-
"const argv = ['bash', '-c', 'exec \"$0\" \"$@\"', file]",
|
|
116
|
-
)
|
|
117
|
-
const ptcWorker = getText('lib/ptc/worker.js')
|
|
118
|
-
let patchedPtcWorker = ptcWorker
|
|
119
|
-
if (typeof ptcWorker === 'string' && ptcWorker.includes('let argv = [file]')) {
|
|
120
|
-
patchedPtcWorker = ptcWorker.replace(
|
|
121
|
-
'let argv = [file]',
|
|
122
|
-
"let argv = ['bash', '-c', 'exec \"$0\" \"$@\"', file]",
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const pkgFile = byPath.get('package.json')
|
|
127
|
-
if (pkgFile?.data === undefined) return { status: 'invalid-package', message: 'package.json is missing' }
|
|
128
|
-
const pkg = JSON.parse(pkgFile.data.toString('utf8'))
|
|
129
|
-
delete pkg.os
|
|
130
|
-
delete pkg.cpu
|
|
131
|
-
// DSHM core is a prerelease (e.g. 0.2.1-alpha.4+dshm.56). The upstream
|
|
132
|
-
// ^0.2.0-rc.2 range does not match 0.2.1-*, and the bundle loader would throw
|
|
133
|
-
// on the next boot. "workspace:*" follows the running core instead.
|
|
134
|
-
if (pkg.peerDependencies !== null && typeof pkg.peerDependencies === 'object' && !Array.isArray(pkg.peerDependencies)) {
|
|
135
|
-
if (typeof pkg.peerDependencies['@deepseek-ai/dsh'] === 'string') {
|
|
136
|
-
pkg.peerDependencies['@deepseek-ai/dsh'] = 'workspace:*'
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (pkg.optionalDependencies !== null && typeof pkg.optionalDependencies === 'object') {
|
|
140
|
-
delete pkg.optionalDependencies['@dsh-ops/bash-win32-x64']
|
|
141
|
-
delete pkg.optionalDependencies['@dsh-ops/pwsh-win32-x64']
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const pluginFile = byPath.get('dsh-plugin.json')
|
|
145
|
-
if (pluginFile?.data !== undefined) {
|
|
146
|
-
const manifest = JSON.parse(pluginFile.data.toString('utf8'))
|
|
147
|
-
const components = manifest?.contributes?.['x-components']
|
|
148
|
-
if (Array.isArray(components)) {
|
|
149
|
-
for (const component of components) {
|
|
150
|
-
if (component === null || typeof component !== 'object') continue
|
|
151
|
-
if (Array.isArray(component.platforms) && !component.platforms.includes('openharmony')) component.platforms.push('openharmony')
|
|
152
|
-
if (Array.isArray(component.targets) && !component.targets.includes('linux-arm64')) component.targets.push('linux-arm64')
|
|
153
|
-
if (Array.isArray(component.architectures) && !component.architectures.includes('arm64')) component.architectures.push('arm64')
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
pluginFile.data = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const runtimeDir = `runtime/linux-arm64`
|
|
160
|
-
const workerPath = `${runtimeDir}/dsh-ops-worker`
|
|
161
|
-
const provenancePath = `${runtimeDir}/provenance.json`
|
|
162
|
-
const sha256 = createHash('sha256').update(workerBinary).digest('hex')
|
|
163
|
-
const provenance = {
|
|
164
|
-
version: typeof pkg.version === 'string' ? pkg.version : '0.0.0',
|
|
165
|
-
target: 'linux-arm64',
|
|
166
|
-
executable: 'dsh-ops-worker',
|
|
167
|
-
sha256,
|
|
168
|
-
source: 'external aarch64-unknown-linux-musl build supplied by the caller',
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** @type {string[]} */
|
|
172
|
-
const changed = [
|
|
173
|
-
'added linux-arm64 to PLATFORMS and mapped openharmony/linux-arm64 to it',
|
|
174
|
-
'wrapped the file worker spawn in bash -c so dshm-vm-bash routes it to the guest',
|
|
175
|
-
'removed package.json os/cpu gates and win32 optional payloads',
|
|
176
|
-
'relaxed @deepseek-ai/dsh peer range to workspace:* for the DSHM core',
|
|
177
|
-
'added dsh-plugin.json openharmony/linux-arm64 targets',
|
|
178
|
-
`staged ${workerPath}`,
|
|
179
|
-
]
|
|
180
|
-
if (patchedPtcWorker !== ptcWorker) changed.push('wrapped the PTC worker spawn in bash -c')
|
|
181
|
-
|
|
182
|
-
const output = files.map(file => {
|
|
183
|
-
if (file.path === 'lib/ptc/runtime.js') return { path: file.path, data: Buffer.from(patchedRuntime, 'utf8'), mode: file.mode }
|
|
184
|
-
if (file.path === 'lib/file/worker.js') return { path: file.path, data: Buffer.from(patchedFileWorker, 'utf8'), mode: file.mode }
|
|
185
|
-
if (file.path === 'lib/ptc/worker.js' && patchedPtcWorker !== ptcWorker) return { path: file.path, data: Buffer.from(patchedPtcWorker, 'utf8'), mode: file.mode }
|
|
186
|
-
if (file.path === 'package.json') return { path: file.path, data: Buffer.from(`${JSON.stringify(pkg, null, 2)}\n`, 'utf8'), mode: file.mode }
|
|
187
|
-
return { path: file.path, data: file.data, mode: file.mode }
|
|
188
|
-
})
|
|
189
|
-
output.push({ path: workerPath, data: workerBinary, mode: 0o755 })
|
|
190
|
-
output.push({ path: provenancePath, data: Buffer.from(`${JSON.stringify(provenance, null, 2)}\n`, 'utf8'), mode: 0o644 })
|
|
191
|
-
|
|
192
|
-
const packed = packTarGz(output)
|
|
193
|
-
const outputDir = join(compatDir(process.env), 'builds')
|
|
194
|
-
mkdirSync(outputDir, { recursive: true })
|
|
195
|
-
const tarball = join(outputDir, `dsh-ops-${pkg.version ?? '0.0.0'}-dshm.tgz`)
|
|
196
|
-
writeFileSync(tarball, packed)
|
|
197
|
-
const validation = validateTarball(tarball)
|
|
198
|
-
if (!validation.ok) return { status: 'invalid-output', tarball, errors: validation.errors }
|
|
199
|
-
return {
|
|
200
|
-
status: 'ok',
|
|
201
|
-
tarball,
|
|
202
|
-
bytes: packed.length,
|
|
203
|
-
sha256: createHash('sha256').update(packed).digest('hex'),
|
|
204
|
-
changed,
|
|
205
|
-
warnings: validation.warnings,
|
|
206
|
-
validation,
|
|
207
|
-
worker: { target: TARGET, sha256 },
|
|
208
|
-
}
|
|
209
|
-
}
|