@owear/cli 0.1.1 → 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.
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
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
+ )