@owear/cli 0.1.2 → 0.1.3
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/package.json +7 -5
- package/src/ow.js +466 -22
- package/template-installer/README.md +38 -0
- package/template-installer/app/main.ts +22 -0
- package/template-installer/index.html +60 -0
- package/template-installer/package.json +22 -0
- package/template-installer/src/installer.ts +152 -0
- package/template-installer/src/style.css +66 -0
- package/template-installer/tsconfig.json +13 -0
- package/template-installer/uninstaller/app/main.ts +16 -0
- package/template-installer/uninstaller/index.html +48 -0
- package/template-installer/uninstaller/package.json +22 -0
- package/template-installer/uninstaller/src/uninstaller.ts +76 -0
- package/template-installer/uninstaller/vite.config.ts +10 -0
- package/template-installer/vite.config.ts +15 -0
- package/tools/owear-installer.mjs +81 -0
- package/tools/owear-linux.mjs +158 -0
- package/tools/owear-msi.mjs +113 -0
- package/tools/owear-pack.mjs +93 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright 2026 Owear Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
//
|
|
5
|
+
// tools/owear-linux.mjs — empaquetado Linux de la app (D1).
|
|
6
|
+
//
|
|
7
|
+
// deb → .deb (ar + control.tar.gz + data.tar.gz), sin deps externas.
|
|
8
|
+
// appimage → AppDir + appimagetool (si está en PATH).
|
|
9
|
+
//
|
|
10
|
+
// Uso:
|
|
11
|
+
// node tools/owear-linux.mjs deb --stage <dataRoot> --out <x.deb> \
|
|
12
|
+
// --name <slug> --version <v> [--arch amd64] [--maintainer <m>] [--description <d>]
|
|
13
|
+
// node tools/owear-linux.mjs appimage --appdir <AppDir> --out <x.AppImage>
|
|
14
|
+
//
|
|
15
|
+
import * as fs from 'node:fs'
|
|
16
|
+
import * as path from 'node:path'
|
|
17
|
+
import * as zlib from 'node:zlib'
|
|
18
|
+
import { spawnSync } from 'node:child_process'
|
|
19
|
+
|
|
20
|
+
function die(m) {
|
|
21
|
+
console.error(`[owear-linux] ✗ ${m}`)
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
24
|
+
const argv = process.argv.slice(2)
|
|
25
|
+
const cmd = argv[0]
|
|
26
|
+
const arg = (n, def = null) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : def)
|
|
27
|
+
|
|
28
|
+
// ── tar (ustar) + gzip ──────────────────────────────────────────────────────
|
|
29
|
+
function oct(n, len) {
|
|
30
|
+
return n.toString(8).padStart(len - 1, '0') + '\0'
|
|
31
|
+
}
|
|
32
|
+
function tarHeader(name, size, mode, typeflag, mtime = 0) {
|
|
33
|
+
const h = Buffer.alloc(512, 0)
|
|
34
|
+
const nm = name.length > 100 ? name.slice(0, 100) : name
|
|
35
|
+
h.write(nm, 0, 100, 'utf8')
|
|
36
|
+
h.write(oct(mode, 8), 100)
|
|
37
|
+
h.write(oct(0, 8), 108)
|
|
38
|
+
h.write(oct(0, 8), 116)
|
|
39
|
+
h.write(oct(size, 12), 124)
|
|
40
|
+
h.write(oct(mtime, 12), 136)
|
|
41
|
+
h.write(' ', 148)
|
|
42
|
+
h.write(typeflag, 156)
|
|
43
|
+
h.write('ustar\0', 257)
|
|
44
|
+
h.write('00', 263)
|
|
45
|
+
let sum = 0
|
|
46
|
+
for (const b of h) sum += b
|
|
47
|
+
h.write(sum.toString(8).padStart(6, '0') + '\0 ', 148)
|
|
48
|
+
return h
|
|
49
|
+
}
|
|
50
|
+
function tarDir(dir) {
|
|
51
|
+
const chunks = []
|
|
52
|
+
const walk = (d, base) => {
|
|
53
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
54
|
+
const p = path.join(d, e.name)
|
|
55
|
+
const rel = base ? `${base}/${e.name}` : e.name
|
|
56
|
+
const st = fs.lstatSync(p)
|
|
57
|
+
if (st.isSymbolicLink()) {
|
|
58
|
+
chunks.push(tarHeader(rel, 0, 0o777, '2', Math.floor(st.mtimeMs / 1000)))
|
|
59
|
+
const link = fs.readlinkSync(p)
|
|
60
|
+
const b = Buffer.from(link)
|
|
61
|
+
chunks.push(b)
|
|
62
|
+
const pad = (512 - (b.length % 512)) % 512
|
|
63
|
+
if (pad) chunks.push(Buffer.alloc(pad, 0))
|
|
64
|
+
} else if (e.isDirectory()) {
|
|
65
|
+
chunks.push(tarHeader(rel + '/', 0, st.mode & 0o777, '5', Math.floor(st.mtimeMs / 1000)))
|
|
66
|
+
walk(p, rel)
|
|
67
|
+
} else if (e.isFile()) {
|
|
68
|
+
const data = fs.readFileSync(p)
|
|
69
|
+
chunks.push(tarHeader(rel, data.length, st.mode & 0o777, '0', Math.floor(st.mtimeMs / 1000)), data)
|
|
70
|
+
const pad = (512 - (data.length % 512)) % 512
|
|
71
|
+
if (pad) chunks.push(Buffer.alloc(pad, 0))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
walk(dir, '')
|
|
76
|
+
chunks.push(Buffer.alloc(1024, 0))
|
|
77
|
+
return Buffer.concat(chunks)
|
|
78
|
+
}
|
|
79
|
+
const tarGz = (dir) => zlib.gzipSync(tarDir(dir), { level: 9 })
|
|
80
|
+
|
|
81
|
+
// ── ar (formato .deb) ───────────────────────────────────────────────────────
|
|
82
|
+
function ar(entries) {
|
|
83
|
+
const parts = [Buffer.from('!<arch>\n')]
|
|
84
|
+
for (const e of entries) {
|
|
85
|
+
const data = e.data
|
|
86
|
+
const h = Buffer.alloc(60, 0x20)
|
|
87
|
+
const name = (e.name + '/').slice(0, 16)
|
|
88
|
+
h.write(name, 0)
|
|
89
|
+
h.write(String(Math.floor(Date.now() / 1000)).padEnd(12), 16)
|
|
90
|
+
h.write('0'.padEnd(6), 28)
|
|
91
|
+
h.write('0'.padEnd(6), 34)
|
|
92
|
+
h.write('100644'.padEnd(8), 40)
|
|
93
|
+
h.write(String(data.length).padEnd(10), 48)
|
|
94
|
+
h.write('`\n', 58)
|
|
95
|
+
parts.push(h, data)
|
|
96
|
+
if (data.length % 2) parts.push(Buffer.from('\n'))
|
|
97
|
+
}
|
|
98
|
+
return Buffer.concat(parts)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildDeb() {
|
|
102
|
+
const stage = arg('--stage')
|
|
103
|
+
const out = arg('--out')
|
|
104
|
+
const name = arg('--name', 'app')
|
|
105
|
+
const version = arg('--version', '0.0.0')
|
|
106
|
+
const arch = arg('--arch', 'amd64')
|
|
107
|
+
const maintainer = arg('--maintainer', 'unknown <unknown@example.com>')
|
|
108
|
+
const description = arg('--description', name)
|
|
109
|
+
if (!stage || !out) die('uso: deb --stage <dataRoot> --out <x.deb> --name <slug> --version <v>')
|
|
110
|
+
if (!fs.existsSync(stage)) die(`no existe el stage: ${stage}`)
|
|
111
|
+
|
|
112
|
+
const controlDir = fs.mkdtempSync('/tmp/owear-deb-')
|
|
113
|
+
fs.writeFileSync(
|
|
114
|
+
path.join(controlDir, 'control'),
|
|
115
|
+
`Package: ${name}\nVersion: ${version}\nArchitecture: ${arch}\n` +
|
|
116
|
+
`Maintainer: ${maintainer}\nDescription: ${description}\n`,
|
|
117
|
+
)
|
|
118
|
+
fs.writeFileSync(
|
|
119
|
+
path.join(controlDir, 'postinst'),
|
|
120
|
+
`#!/bin/sh\nset -e\nif command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q || true; fi\nexit 0\n`,
|
|
121
|
+
)
|
|
122
|
+
fs.chmodSync(path.join(controlDir, 'postinst'), 0o755)
|
|
123
|
+
|
|
124
|
+
const deb = ar([
|
|
125
|
+
{ name: 'debian-binary', data: Buffer.from('2.0\n') },
|
|
126
|
+
{ name: 'control.tar.gz', data: tarGz(controlDir) },
|
|
127
|
+
{ name: 'data.tar.gz', data: tarGz(stage) },
|
|
128
|
+
])
|
|
129
|
+
fs.mkdirSync(path.dirname(out), { recursive: true })
|
|
130
|
+
fs.writeFileSync(out, deb)
|
|
131
|
+
fs.rmSync(controlDir, { recursive: true, force: true })
|
|
132
|
+
const mb = (deb.length / 1024 / 1024).toFixed(2)
|
|
133
|
+
console.log(`[owear-linux] .deb: ${out} (${mb} MB)`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildAppImage() {
|
|
137
|
+
const appDir = arg('--appdir')
|
|
138
|
+
const out = arg('--out')
|
|
139
|
+
if (!appDir || !out) die('uso: appimage --appdir <AppDir> --out <x.AppImage>')
|
|
140
|
+
if (!fs.existsSync(appDir)) die(`no existe el AppDir: ${appDir}`)
|
|
141
|
+
const tool = ['appimagetool', 'appimagetool-x86_64.AppImage'].find((t) => {
|
|
142
|
+
const r = spawnSync('sh', ['-c', `command -v ${t}`], { stdio: 'ignore' })
|
|
143
|
+
return r.status === 0
|
|
144
|
+
})
|
|
145
|
+
if (!tool) {
|
|
146
|
+
die(
|
|
147
|
+
'appimagetool no está en PATH. Instálalo (https://github.com/AppImage/appimagetool) ' +
|
|
148
|
+
`y reintenta. El AppDir queda listo en: ${appDir}`,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
const r = spawnSync(tool, [appDir, out], { stdio: 'inherit', env: { ...process.env, ARCH: arg('--arch', 'x86_64') } })
|
|
152
|
+
if (r.status !== 0) die('appimagetool falló')
|
|
153
|
+
console.log(`[owear-linux] AppImage: ${out}`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (cmd === 'deb') buildDeb()
|
|
157
|
+
else if (cmd === 'appimage') buildAppImage()
|
|
158
|
+
else die('uso: owear-linux <deb|appimage> …')
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright 2026 Owear Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
//
|
|
5
|
+
// tools/owear-msi.mjs — genera un .MSI de la app (D1) desde un stage dir.
|
|
6
|
+
//
|
|
7
|
+
// Genera un fuente WiX v3 y lo compila con `wixl` (msitools, Linux) o
|
|
8
|
+
// `wix`/`candle`+`light` (Windows). Si no hay toolchain, deja el .wxs listo.
|
|
9
|
+
//
|
|
10
|
+
// Uso:
|
|
11
|
+
// node tools/owear-msi.mjs --stage <dir> --out <x.msi> --name <slug> \
|
|
12
|
+
// --version <v> [--publisher <p>] [--appId <id>]
|
|
13
|
+
//
|
|
14
|
+
import * as fs from 'node:fs'
|
|
15
|
+
import * as path from 'node:path'
|
|
16
|
+
import * as crypto from 'node:crypto'
|
|
17
|
+
import { spawnSync } from 'node:child_process'
|
|
18
|
+
|
|
19
|
+
function die(m) {
|
|
20
|
+
console.error(`[owear-msi] ✗ ${m}`)
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
const argv = process.argv.slice(2)
|
|
24
|
+
const arg = (n, def = null) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : def)
|
|
25
|
+
const stage = arg('--stage')
|
|
26
|
+
const out = arg('--out')
|
|
27
|
+
const name = arg('--name', 'app')
|
|
28
|
+
const version = arg('--version', '0.0.0')
|
|
29
|
+
const publisher = arg('--publisher', 'unknown')
|
|
30
|
+
const appId = arg('--appId', name)
|
|
31
|
+
if (!stage || !out) die('uso: --stage <dir> --out <x.msi> --name <slug> --version <v>')
|
|
32
|
+
if (!fs.existsSync(stage)) die(`no existe el stage: ${stage}`)
|
|
33
|
+
|
|
34
|
+
/** GUID estable a partir de un string (UpgradeCode). */
|
|
35
|
+
function guidFrom(seed) {
|
|
36
|
+
const h = crypto.createHash('sha1').update(seed).digest()
|
|
37
|
+
const b = Buffer.from(h.subarray(0, 16))
|
|
38
|
+
b[6] = (b[6] & 0x0f) | 0x40 // version 4
|
|
39
|
+
b[8] = (b[8] & 0x3f) | 0x80 // variant
|
|
40
|
+
const s = b.toString('hex')
|
|
41
|
+
return `${s.slice(0, 8)}-${s.slice(8, 12)}-${s.slice(12, 16)}-${s.slice(16, 20)}-${s.slice(20, 32)}`.toUpperCase()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const xmlEsc = (s) =>
|
|
45
|
+
String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
46
|
+
|
|
47
|
+
// ── construye el árbol WiX ──────────────────────────────────────────────────
|
|
48
|
+
let idSeq = 0
|
|
49
|
+
const nextId = (p) => `${p}${++idSeq}`
|
|
50
|
+
|
|
51
|
+
function buildTree(dir, dirId, indent) {
|
|
52
|
+
let xml = ''
|
|
53
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
54
|
+
const p = path.join(dir, e.name)
|
|
55
|
+
if (e.isDirectory()) {
|
|
56
|
+
const childId = nextId('dir')
|
|
57
|
+
xml += `${indent}<Directory Id="${childId}" Name="${xmlEsc(e.name)}">\n`
|
|
58
|
+
xml += buildTree(p, childId, indent + ' ')
|
|
59
|
+
xml += `${indent}</Directory>\n`
|
|
60
|
+
} else if (e.isFile()) {
|
|
61
|
+
const compId = nextId('cmp')
|
|
62
|
+
const fileId = nextId('fil')
|
|
63
|
+
xml +=
|
|
64
|
+
`${indent}<Component Id="${compId}" Guid="*">\n` +
|
|
65
|
+
`${indent} <File Id="${fileId}" Source="${xmlEsc(p)}" KeyPath="yes" />\n` +
|
|
66
|
+
`${indent}</Component>\n`
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return xml
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const wxsPath = out.replace(/\.msi$/, '.wxs')
|
|
73
|
+
const wxs =
|
|
74
|
+
`<?xml version="1.0" encoding="UTF-8"?>\n` +
|
|
75
|
+
`<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">\n` +
|
|
76
|
+
` <Product Id="*" Name="${xmlEsc(name)}" Language="1033" Version="${xmlEsc(version)}"\n` +
|
|
77
|
+
` Manufacturer="${xmlEsc(publisher)}" UpgradeCode="${guidFrom(appId)}">\n` +
|
|
78
|
+
` <Package InstallerVersion="500" Compressed="yes" InstallScope="perUser" />\n` +
|
|
79
|
+
` <MajorUpgrade DowngradeErrorMessage="Ya hay una version mas reciente instalada." />\n` +
|
|
80
|
+
` <MediaTemplate EmbedCab="yes" />\n` +
|
|
81
|
+
` <Directory Id="TARGETDIR" Name="SourceDir">\n` +
|
|
82
|
+
` <Directory Id="LocalAppDataFolder">\n` +
|
|
83
|
+
` <Directory Id="INSTALLFOLDER" Name="${xmlEsc(name)}">\n` +
|
|
84
|
+
buildTree(stage, 'INSTALLFOLDER', ' ') +
|
|
85
|
+
` </Directory>\n` +
|
|
86
|
+
` </Directory>\n` +
|
|
87
|
+
` </Directory>\n` +
|
|
88
|
+
` </Product>\n` +
|
|
89
|
+
`</Wix>\n`
|
|
90
|
+
|
|
91
|
+
fs.mkdirSync(path.dirname(wxsPath), { recursive: true })
|
|
92
|
+
fs.writeFileSync(wxsPath, wxs)
|
|
93
|
+
|
|
94
|
+
const has = (cmd) => spawnSync('sh', ['-c', `command -v ${cmd}`], { stdio: 'ignore' }).status === 0
|
|
95
|
+
|
|
96
|
+
let ok = false
|
|
97
|
+
if (has('wixl')) {
|
|
98
|
+
const r = spawnSync('wixl', ['-o', out, wxsPath], { stdio: 'inherit' })
|
|
99
|
+
ok = r.status === 0
|
|
100
|
+
} else if (has('wix')) {
|
|
101
|
+
const r = spawnSync('wix', ['build', wxsPath, '-o', out], { stdio: 'inherit' })
|
|
102
|
+
ok = r.status === 0
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!ok) {
|
|
106
|
+
console.error(
|
|
107
|
+
`[owear-msi] sin toolchain (wixl/wix). Fuente WiX lista en: ${wxsPath}\n` +
|
|
108
|
+
` · Linux: sudo apt install msitools → wixl -o "${out}" "${wxsPath}"\n` +
|
|
109
|
+
` · Windows: dotnet tool install --global wix → wix build "${wxsPath}" -o "${out}"`,
|
|
110
|
+
)
|
|
111
|
+
process.exit(0) // no es un error fatal: el .wxs queda generado
|
|
112
|
+
}
|
|
113
|
+
console.log(`[owear-msi] MSI: ${out}`)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright 2026 Owear Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
//
|
|
5
|
+
// tools/owear-pack.mjs — empaqueta un directorio (bundle) DENTRO del binario del
|
|
6
|
+
// kernel → single binary. Formato:
|
|
7
|
+
// [ kernel ][ payload.tar.gz ][ "OWPK1\0\0\0" + offset(u64) + size(u64) ]
|
|
8
|
+
//
|
|
9
|
+
// node tools/owear-pack.mjs --kernel <owear> --in <bundleDir> --out <MiApp>
|
|
10
|
+
//
|
|
11
|
+
import * as fs from 'node:fs'
|
|
12
|
+
import * as path from 'node:path'
|
|
13
|
+
import * as zlib from 'node:zlib'
|
|
14
|
+
|
|
15
|
+
function die(m) {
|
|
16
|
+
console.error(`[owear-pack] ✗ ${m}`)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
const argv = process.argv.slice(2)
|
|
20
|
+
const arg = (n) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : null)
|
|
21
|
+
const kernel = arg('--kernel')
|
|
22
|
+
const inDir = arg('--in')
|
|
23
|
+
const out = arg('--out')
|
|
24
|
+
if (!kernel || !inDir || !out) die('uso: --kernel <owear> --in <bundle> --out <MiApp>')
|
|
25
|
+
|
|
26
|
+
// ── tar (ustar) mínimo ──────────────────────────────────────────────────────
|
|
27
|
+
function oct(n, len) {
|
|
28
|
+
return n.toString(8).padStart(len - 1, '0') + '\0'
|
|
29
|
+
}
|
|
30
|
+
function tarHeader(name, size, mode, typeflag) {
|
|
31
|
+
const h = Buffer.alloc(512, 0)
|
|
32
|
+
h.write(name.slice(0, 100), 0, 100, 'utf8')
|
|
33
|
+
h.write(oct(mode, 8), 100)
|
|
34
|
+
h.write(oct(0, 8), 108) // uid
|
|
35
|
+
h.write(oct(0, 8), 116) // gid
|
|
36
|
+
h.write(oct(size, 12), 124)
|
|
37
|
+
h.write(oct(0, 12), 136) // mtime
|
|
38
|
+
h.write(' ', 148) // checksum (espacios)
|
|
39
|
+
h.write(typeflag, 156)
|
|
40
|
+
h.write('ustar\0', 257)
|
|
41
|
+
h.write('00', 263)
|
|
42
|
+
let sum = 0
|
|
43
|
+
for (const b of h) sum += b
|
|
44
|
+
h.write(sum.toString(8).padStart(6, '0') + '\0 ', 148)
|
|
45
|
+
return h
|
|
46
|
+
}
|
|
47
|
+
function walk(dir, base = '') {
|
|
48
|
+
const out = []
|
|
49
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
50
|
+
const p = path.join(dir, e.name)
|
|
51
|
+
const rel = base ? `${base}/${e.name}` : e.name
|
|
52
|
+
if (e.isDirectory()) {
|
|
53
|
+
out.push({ name: rel + '/', dir: true })
|
|
54
|
+
out.push(...walk(p, rel))
|
|
55
|
+
} else if (e.isFile()) {
|
|
56
|
+
out.push({ name: rel, dir: false, path: p })
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
function makeTar(dir) {
|
|
62
|
+
const chunks = []
|
|
63
|
+
for (const e of walk(dir)) {
|
|
64
|
+
if (e.dir) {
|
|
65
|
+
chunks.push(tarHeader(e.name, 0, 0o755, '5'))
|
|
66
|
+
} else {
|
|
67
|
+
const data = fs.readFileSync(e.path)
|
|
68
|
+
chunks.push(tarHeader(e.name, data.length, 0o644, '0'), data)
|
|
69
|
+
const pad = (512 - (data.length % 512)) % 512
|
|
70
|
+
if (pad) chunks.push(Buffer.alloc(pad, 0))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
chunks.push(Buffer.alloc(1024, 0)) // 2 bloques fin
|
|
74
|
+
return Buffer.concat(chunks)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── ensambla ────────────────────────────────────────────────────────────────
|
|
78
|
+
if (!fs.existsSync(kernel)) die(`no existe el kernel: ${kernel}`)
|
|
79
|
+
if (!fs.existsSync(inDir)) die(`no existe el bundle: ${inDir}`)
|
|
80
|
+
|
|
81
|
+
const payload = zlib.gzipSync(makeTar(inDir), { level: 9 })
|
|
82
|
+
const ker = fs.readFileSync(kernel)
|
|
83
|
+
const footer = Buffer.alloc(24)
|
|
84
|
+
footer.write('OWPK1\0\0\0', 0, 'binary')
|
|
85
|
+
footer.writeBigUInt64LE(BigInt(ker.length), 8)
|
|
86
|
+
footer.writeBigUInt64LE(BigInt(payload.length), 16)
|
|
87
|
+
fs.writeFileSync(out, Buffer.concat([ker, payload, footer]))
|
|
88
|
+
fs.chmodSync(out, 0o755)
|
|
89
|
+
|
|
90
|
+
const mb = (b) => (b / 1024 / 1024).toFixed(2)
|
|
91
|
+
console.log(
|
|
92
|
+
`[owear-pack] ${out} kernel=${mb(ker.length)}MB + payload=${mb(payload.length)}MB (${fs.readdirSync(inDir).length} items)`,
|
|
93
|
+
)
|