dshm-compat 0.1.2 → 0.1.4

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.
@@ -1,200 +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
- if (pkg.optionalDependencies !== null && typeof pkg.optionalDependencies === 'object') {
132
- delete pkg.optionalDependencies['@dsh-ops/bash-win32-x64']
133
- delete pkg.optionalDependencies['@dsh-ops/pwsh-win32-x64']
134
- }
135
-
136
- const pluginFile = byPath.get('dsh-plugin.json')
137
- if (pluginFile?.data !== undefined) {
138
- const manifest = JSON.parse(pluginFile.data.toString('utf8'))
139
- const components = manifest?.contributes?.['x-components']
140
- if (Array.isArray(components)) {
141
- for (const component of components) {
142
- if (component === null || typeof component !== 'object') continue
143
- if (Array.isArray(component.platforms) && !component.platforms.includes('openharmony')) component.platforms.push('openharmony')
144
- if (Array.isArray(component.targets) && !component.targets.includes('linux-arm64')) component.targets.push('linux-arm64')
145
- if (Array.isArray(component.architectures) && !component.architectures.includes('arm64')) component.architectures.push('arm64')
146
- }
147
- }
148
- pluginFile.data = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
149
- }
150
-
151
- const runtimeDir = `runtime/linux-arm64`
152
- const workerPath = `${runtimeDir}/dsh-ops-worker`
153
- const provenancePath = `${runtimeDir}/provenance.json`
154
- const sha256 = createHash('sha256').update(workerBinary).digest('hex')
155
- const provenance = {
156
- version: typeof pkg.version === 'string' ? pkg.version : '0.0.0',
157
- target: 'linux-arm64',
158
- executable: 'dsh-ops-worker',
159
- sha256,
160
- source: 'external aarch64-unknown-linux-musl build supplied by the caller',
161
- }
162
-
163
- /** @type {string[]} */
164
- const changed = [
165
- 'added linux-arm64 to PLATFORMS and mapped openharmony/linux-arm64 to it',
166
- 'wrapped the file worker spawn in bash -c so dshm-vm-bash routes it to the guest',
167
- 'removed package.json os/cpu gates and win32 optional payloads',
168
- 'added dsh-plugin.json openharmony/linux-arm64 targets',
169
- `staged ${workerPath}`,
170
- ]
171
- if (patchedPtcWorker !== ptcWorker) changed.push('wrapped the PTC worker spawn in bash -c')
172
-
173
- const output = files.map(file => {
174
- if (file.path === 'lib/ptc/runtime.js') return { path: file.path, data: Buffer.from(patchedRuntime, 'utf8'), mode: file.mode }
175
- if (file.path === 'lib/file/worker.js') return { path: file.path, data: Buffer.from(patchedFileWorker, 'utf8'), mode: file.mode }
176
- if (file.path === 'lib/ptc/worker.js' && patchedPtcWorker !== ptcWorker) return { path: file.path, data: Buffer.from(patchedPtcWorker, 'utf8'), mode: file.mode }
177
- if (file.path === 'package.json') return { path: file.path, data: Buffer.from(`${JSON.stringify(pkg, null, 2)}\n`, 'utf8'), mode: file.mode }
178
- return { path: file.path, data: file.data, mode: file.mode }
179
- })
180
- output.push({ path: workerPath, data: workerBinary, mode: 0o755 })
181
- output.push({ path: provenancePath, data: Buffer.from(`${JSON.stringify(provenance, null, 2)}\n`, 'utf8'), mode: 0o644 })
182
-
183
- const packed = packTarGz(output)
184
- const outputDir = join(compatDir(process.env), 'builds')
185
- mkdirSync(outputDir, { recursive: true })
186
- const tarball = join(outputDir, `dsh-ops-${pkg.version ?? '0.0.0'}-dshm.tgz`)
187
- writeFileSync(tarball, packed)
188
- const validation = validateTarball(tarball)
189
- if (!validation.ok) return { status: 'invalid-output', tarball, errors: validation.errors }
190
- return {
191
- status: 'ok',
192
- tarball,
193
- bytes: packed.length,
194
- sha256: createHash('sha256').update(packed).digest('hex'),
195
- changed,
196
- warnings: validation.warnings,
197
- validation,
198
- worker: { target: TARGET, sha256 },
199
- }
200
- }