@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.
@@ -0,0 +1,60 @@
1
+ <!doctype html>
2
+ <!-- Copyright 2026 Owear Contributors
3
+ SPDX-License-Identifier: Apache-2.0 -->
4
+
5
+ <html lang="es">
6
+ <head>
7
+ <meta charset="UTF-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <title>Instalador — __APP_NAME__</title>
10
+ </head>
11
+ <body>
12
+ <header class="titlebar">
13
+ <div class="titlebar__drag" data-ow-drag>
14
+ <span class="brand__name">Instalador · __APP_NAME__</span>
15
+ </div>
16
+ <div class="titlebar__controls">
17
+ <button id="win-close" class="winbtn winbtn--close" title="Cerrar" aria-label="Cerrar">
18
+ <svg viewBox="0 0 12 12" width="12" height="12"><path d="M2.5 2.5l7 7M9.5 2.5l-7 7" /></svg>
19
+ </button>
20
+ </div>
21
+ </header>
22
+
23
+ <main class="page">
24
+ <section class="hero">
25
+ <span class="eyebrow">Owear Installer</span>
26
+ <h1>Instalar __APP_NAME__</h1>
27
+ <p class="lead" id="summary">Cargando metadatos…</p>
28
+ </section>
29
+
30
+ <section class="card">
31
+ <header class="card__head"><h2>Destino</h2></header>
32
+ <div class="row">
33
+ <input id="dir" class="input" type="text" spellcheck="false" />
34
+ <button id="pick" class="btn">Elegir…</button>
35
+ </div>
36
+ <div class="row">
37
+ <label class="opt"><input type="checkbox" id="shortcut" checked /> Acceso directo</label>
38
+ <label class="opt"><input type="checkbox" id="launch" checked /> Abrir al terminar</label>
39
+ </div>
40
+ </section>
41
+
42
+ <section class="card card--grow">
43
+ <header class="card__head">
44
+ <h2>Contenido</h2>
45
+ <span class="badge" id="count">—</span>
46
+ </header>
47
+ <pre id="plan" class="console">—</pre>
48
+ </section>
49
+
50
+ <footer class="actions">
51
+ <progress id="bar" max="100" value="0" hidden></progress>
52
+ <span id="status" class="status"></span>
53
+ <button id="install" class="btn btn--primary" disabled>Instalar</button>
54
+ <button id="cancel" class="btn btn--ghost" hidden>Cancelar</button>
55
+ </footer>
56
+ </main>
57
+
58
+ <script type="module" src="/src/installer.ts"></script>
59
+ </body>
60
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "__PKG_NAME__-installer",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Instalador de __APP_NAME__ — app Owear en modo installer.",
7
+ "scripts": {
8
+ "build": "ow build installer",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@owear/core": "^0.1.0"
13
+ },
14
+ "devDependencies": {
15
+ "@owear/cli": "^0.1.0",
16
+ "@owear/vite-plugin": "^0.1.0",
17
+ "@types/node": "^20.14.0",
18
+ "esbuild": "^0.25.0",
19
+ "typescript": "^5.6.0",
20
+ "vite": "^6.0.0"
21
+ }
22
+ }
@@ -0,0 +1,152 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // src/installer.ts — UI del instalador (vanilla). Corre en el renderer y usa
5
+ // la API nativa `installer` vía `window.ow` (inyectado por el kernel).
6
+ //
7
+ // Personalízalo a gusto (React, Vue, Tailwind…): esto es sólo el default.
8
+
9
+ import './style.css'
10
+
11
+ declare global {
12
+ interface Window {
13
+ __owWindowId: number
14
+ ow: { invoke<T = unknown>(module: string, fn: string, ...args: unknown[]): Promise<T> }
15
+ }
16
+ }
17
+
18
+ const $ = <T extends HTMLElement = HTMLElement>(sel: string): T =>
19
+ document.querySelector(sel) as T
20
+ const invoke = <T = unknown>(module: string, fn: string, ...args: unknown[]): Promise<T> =>
21
+ window.ow.invoke(module, fn, ...args) as Promise<T>
22
+
23
+ interface PayloadEntry {
24
+ rel: string
25
+ dst: string
26
+ size: number
27
+ dir: boolean
28
+ }
29
+ interface InstallerInfo {
30
+ appId?: string
31
+ appName?: string
32
+ version?: string
33
+ publisher?: string
34
+ }
35
+ interface Target {
36
+ layout?: 'minimal' | 'layout'
37
+ dir?: string
38
+ shortcuts?: string[]
39
+ }
40
+ interface Bridge {
41
+ app?: { id?: string; name?: string; version?: string; publisher?: string }
42
+ targets?: Record<string, Target>
43
+ order?: string[]
44
+ }
45
+
46
+ const platform: string = (() => {
47
+ const p = navigator.userAgent.toLowerCase()
48
+ return p.includes('win') ? 'win' : p.includes('mac') ? 'mac' : 'linux'
49
+ })()
50
+
51
+ let mode: 'minimal' | 'layout' = 'minimal'
52
+ let plan: PayloadEntry[] = []
53
+ let busy = false
54
+
55
+ function setStatus(msg: string, kind: 'ok' | 'err' | '' = ''): void {
56
+ const el = $('#status')
57
+ el.textContent = msg
58
+ el.dataset.kind = kind
59
+ }
60
+
61
+ async function boot(): Promise<void> {
62
+ // controles de ventana (titlebar propia)
63
+ const id = window.__owWindowId
64
+ $('#win-close').addEventListener('click', () => void invoke('ow-window', 'close', id))
65
+
66
+ const info = await invoke<InstallerInfo>('installer', 'info').catch(() => ({}))
67
+ const bridge = await invoke<Bridge | null>('installer', 'bridge').catch(() => null)
68
+ const t: Target = bridge?.targets?.[platform] ?? {}
69
+ mode = t.layout ?? 'minimal'
70
+
71
+ const name = info.appName ?? bridge?.app?.name ?? 'la app'
72
+ $('#summary').textContent =
73
+ `${name} ${info.version ?? bridge?.app?.version ?? ''} — modo ${mode} · ${platform}`
74
+
75
+ const value = t.dir ?? (info.appName ? `~/opt/${info.appName.toLowerCase()}` : '')
76
+ ;($('#dir') as HTMLInputElement).value = value
77
+
78
+ await refreshPlan()
79
+ $('#install').removeAttribute('disabled')
80
+ }
81
+
82
+ async function refreshPlan(): Promise<void> {
83
+ plan = await invoke<PayloadEntry[]>('installer', 'plan', {
84
+ mode,
85
+ layout: mode === 'minimal' ? 'flat' : 'tree',
86
+ order: (await invoke<Bridge | null>('installer', 'bridge').catch(() => null))?.order,
87
+ })
88
+ const total = plan.reduce((n, e) => n + e.size, 0)
89
+ $('#count').textContent = `${plan.length} entradas · ${(total / 1024).toFixed(1)} KiB`
90
+ $('#plan').textContent = plan
91
+ .slice(0, 300)
92
+ .map((e) => `${e.dir ? '📁' : '📄'} ${e.dst}${e.size ? ` (${e.size} B)` : ''}`)
93
+ .join('\n')
94
+ }
95
+
96
+ async function pickDir(): Promise<void> {
97
+ try {
98
+ const dir = await invoke<string | null>('dialog', 'open', 'openDirectory', 'Elegir carpeta')
99
+ if (dir) ($('#dir') as HTMLInputElement).value = dir
100
+ } catch {
101
+ /* cancelado o sin picker */
102
+ }
103
+ }
104
+
105
+ async function doInstall(): Promise<void> {
106
+ if (busy) return
107
+ busy = true
108
+ const dir = ($('#dir') as HTMLInputElement).value.trim()
109
+ if (!dir) {
110
+ setStatus('Elige un directorio de instalación', 'err')
111
+ busy = false
112
+ return
113
+ }
114
+ $('#install').setAttribute('disabled', 'true')
115
+ $('#cancel').removeAttribute('hidden')
116
+ $('#bar').removeAttribute('hidden')
117
+ setStatus('Instalando…')
118
+
119
+ try {
120
+ const res = await invoke<{ files: number; dir: string }>('installer', 'install', {
121
+ dir,
122
+ mode,
123
+ layout: mode === 'minimal' ? 'flat' : 'tree',
124
+ })
125
+ $('#bar').value = 100
126
+ setStatus(`Instalado en ${res.dir} (${res.files} ficheros)`, 'ok')
127
+
128
+ if (($('#shortcut') as HTMLInputElement).checked) {
129
+ const exec = plan.find((e) => !e.dir && !e.dst.includes('/'))?.dst ?? plan[0]?.dst
130
+ await invoke('installer', 'shortcuts', { execPath: `${dir}/${exec ?? ''}` }).catch(() => false)
131
+ }
132
+ if (($('#launch') as HTMLInputElement).checked) {
133
+ const exec = plan.find((e) => !e.dir && !e.dst.includes('/'))?.dst ?? plan[0]?.dst
134
+ await invoke('installer', 'launch', { path: `${dir}/${exec ?? ''}` }).catch(() => false)
135
+ const v = await invoke<{ elevated: boolean }>('installer', 'elevate').catch(() => ({ elevated: false }))
136
+ void v
137
+ await invoke('ow-window', 'close', window.__owWindowId)
138
+ }
139
+ } catch (e) {
140
+ setStatus(e instanceof Error ? e.message : String(e), 'err')
141
+ $('#install').removeAttribute('disabled')
142
+ } finally {
143
+ busy = false
144
+ $('#cancel').setAttribute('hidden', 'true')
145
+ }
146
+ }
147
+
148
+ $('#pick').addEventListener('click', () => void pickDir())
149
+ $('#install').addEventListener('click', () => void doInstall())
150
+ $('#cancel').addEventListener('click', () => void invoke('ow-window', 'close', window.__owWindowId))
151
+
152
+ void boot()
@@ -0,0 +1,66 @@
1
+ /* Copyright 2026 Owear Contributors
2
+ SPDX-License-Identifier: Apache-2.0 */
3
+ :root {
4
+ --bg: #0f1115;
5
+ --panel: #171a21;
6
+ --fg: #e8eaed;
7
+ --muted: #9aa0aa;
8
+ --accent: #4c8bf5;
9
+ --ok: #3fb950;
10
+ --err: #f85149;
11
+ color-scheme: dark;
12
+ }
13
+ * { box-sizing: border-box; }
14
+ html, body { height: 100%; margin: 0; }
15
+ body {
16
+ background: var(--bg);
17
+ color: var(--fg);
18
+ font: 14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
19
+ }
20
+
21
+ .titlebar {
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ height: 38px;
26
+ padding: 0 8px 0 14px;
27
+ background: #12141a;
28
+ border-bottom: 1px solid #232733;
29
+ }
30
+ .titlebar__drag { flex: 1; -webkit-app-region: drag; user-select: none; }
31
+ .brand__name { font-weight: 600; }
32
+ .winbtn {
33
+ width: 30px; height: 30px; border: 0; border-radius: 6px;
34
+ background: transparent; color: var(--muted); cursor: pointer;
35
+ }
36
+ .winbtn:hover { background: #232733; color: var(--fg); }
37
+ .winbtn--close:hover { background: #7d2c2c; color: #fff; }
38
+ .winbtn svg { fill: none; stroke: currentColor; stroke-width: 1.3; }
39
+
40
+ .page { max-width: 780px; margin: 0 auto; padding: 22px 18px 90px; display: grid; gap: 14px; }
41
+ .hero h1 { margin: 6px 0 4px; font-size: 24px; }
42
+ .eyebrow { color: var(--accent); text-transform: uppercase; letter-spacing: .08em; font-size: 11px; }
43
+ .lead { color: var(--muted); margin: 0; }
44
+
45
+ .card { background: var(--panel); border: 1px solid #232733; border-radius: 12px; padding: 14px; }
46
+ .card--grow { min-height: 160px; }
47
+ .card__head { display: flex; align-items: center; justify-content: space-between; }
48
+ .card__head h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0; }
49
+ .badge { background: #232733; border-radius: 999px; padding: 2px 10px; font-size: 12px; color: var(--muted); }
50
+
51
+ .row { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
52
+ .input { flex: 1; background: #0d0f13; border: 1px solid #2a2f3b; color: var(--fg); border-radius: 8px; padding: 8px 10px; }
53
+ .opt { display: flex; align-items: center; gap: 6px; color: var(--muted); }
54
+ .console { background: #0d0f13; border: 1px solid #232733; border-radius: 8px; padding: 10px; max-height: 260px; overflow: auto; font: 12px/1.5 ui-monospace, monospace; white-space: pre-wrap; color: var(--muted); }
55
+
56
+ .btn { background: #232733; color: var(--fg); border: 0; border-radius: 8px; padding: 8px 14px; cursor: pointer; }
57
+ .btn:hover { background: #2b3040; }
58
+ .btn--primary { background: var(--accent); color: #fff; }
59
+ .btn--primary:disabled { opacity: .5; cursor: default; }
60
+ .btn--ghost { background: transparent; border: 1px solid #2a2f3b; color: var(--muted); }
61
+
62
+ .actions { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 12px; align-items: center; justify-content: flex-end; padding: 12px 18px; background: #12141acc; backdrop-filter: blur(6px); border-top: 1px solid #232733; }
63
+ .status { margin-right: auto; color: var(--muted); }
64
+ .status[data-kind="ok"] { color: var(--ok); }
65
+ .status[data-kind="err"] { color: var(--err); }
66
+ progress { width: 160px; }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "types": ["node", "vite/client"],
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true
11
+ },
12
+ "include": ["src", "app"]
13
+ }
@@ -0,0 +1,16 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // app/main.ts — proceso principal del DESINSTALADOR (sidecar Node).
5
+ import { app, BrowserWindow } from '@owear/core'
6
+
7
+ app.whenReady().then(() => {
8
+ const win = new BrowserWindow({
9
+ title: 'Desinstalador',
10
+ width: 720,
11
+ height: 460,
12
+ titleBarStyle: 'custom',
13
+ url: process.env.OW_DEV_SERVER_URL ?? 'app://index.html',
14
+ })
15
+ win.on('closed', () => app.quit())
16
+ })
@@ -0,0 +1,48 @@
1
+ <!doctype html>
2
+ <!-- Copyright 2026 Owear Contributors
3
+ SPDX-License-Identifier: Apache-2.0 -->
4
+ <html lang="es">
5
+ <head>
6
+ <meta charset="UTF-8" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>Desinstalar — __APP_NAME__</title>
9
+ </head>
10
+ <body>
11
+ <header class="titlebar">
12
+ <div class="titlebar__drag" data-ow-drag>
13
+ <span class="brand__name">Desinstalador · __APP_NAME__</span>
14
+ </div>
15
+ <div class="titlebar__controls">
16
+ <button id="win-close" class="winbtn winbtn--close" title="Cerrar" aria-label="Cerrar">
17
+ <svg viewBox="0 0 12 12" width="12" height="12"><path d="M2.5 2.5l7 7M9.5 2.5l-7 7" /></svg>
18
+ </button>
19
+ </div>
20
+ </header>
21
+
22
+ <main class="page">
23
+ <section class="hero">
24
+ <span class="eyebrow">Owear Uninstaller</span>
25
+ <h1>Desinstalar __APP_NAME__</h1>
26
+ <p class="lead" id="summary">Localizando la instalación…</p>
27
+ </section>
28
+
29
+ <section class="card">
30
+ <header class="card__head"><h2>Instalación</h2></header>
31
+ <div class="row">
32
+ <input id="dir" class="input" type="text" spellcheck="false" />
33
+ <button id="pick" class="btn">Elegir…</button>
34
+ </div>
35
+ <div class="row">
36
+ <label class="opt"><input type="checkbox" id="keepData" checked /> Conservar mis datos</label>
37
+ </div>
38
+ </section>
39
+
40
+ <footer class="actions">
41
+ <span id="status" class="status"></span>
42
+ <button id="uninstall" class="btn btn--primary" disabled>Desinstalar</button>
43
+ </footer>
44
+ </main>
45
+
46
+ <script type="module" src="/src/uninstaller.ts"></script>
47
+ </body>
48
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "__PKG_NAME__-uninstaller",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Desinstalador de __APP_NAME__ — app Owear en modo uninstaller.",
7
+ "scripts": {
8
+ "build": "ow build uninstaller",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@owear/core": "^0.1.0"
13
+ },
14
+ "devDependencies": {
15
+ "@owear/cli": "^0.1.0",
16
+ "@owear/vite-plugin": "^0.1.0",
17
+ "@types/node": "^20.14.0",
18
+ "esbuild": "^0.25.0",
19
+ "typescript": "^5.6.0",
20
+ "vite": "^6.0.0"
21
+ }
22
+ }
@@ -0,0 +1,76 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // src/uninstaller.ts — UI del desinstalador (vanilla). Mismo bridge, pero
5
+ // invoca `installer.uninstall`.
6
+
7
+ import '../../src/style.css'
8
+
9
+ declare global {
10
+ interface Window {
11
+ __owWindowId: number
12
+ ow: { invoke<T = unknown>(module: string, fn: string, ...args: unknown[]): Promise<T> }
13
+ }
14
+ }
15
+
16
+ const $ = <T extends HTMLElement = HTMLElement>(sel: string): T =>
17
+ document.querySelector(sel) as T
18
+ const invoke = <T = unknown>(module: string, fn: string, ...args: unknown[]): Promise<T> =>
19
+ window.ow.invoke(module, fn, ...args) as Promise<T>
20
+
21
+ function setStatus(msg: string, kind: 'ok' | 'err' | '' = ''): void {
22
+ const el = $('#status')
23
+ el.textContent = msg
24
+ el.dataset.kind = kind
25
+ }
26
+
27
+ async function boot(): Promise<void> {
28
+ $('#win-close').addEventListener('click', () => void invoke('ow-window', 'close', window.__owWindowId))
29
+
30
+ const info = await invoke<{ appName?: string; dir?: string }>('installer', 'info').catch(() => ({}))
31
+ const st = await invoke<{ installed: boolean; version?: string; app?: string }>(
32
+ 'installer',
33
+ 'state',
34
+ { dir: info.dir ?? '' },
35
+ ).catch(() => ({ installed: false }))
36
+
37
+ $('#summary').textContent = st.installed
38
+ ? `${st.app ?? info.appName ?? 'La app'} ${st.version ?? ''} está instalada.`
39
+ : 'No se detectó una instalación en el directorio por defecto; elígelo.'
40
+ ;($('#dir') as HTMLInputElement).value = info.dir ?? ''
41
+ $('#uninstall').removeAttribute('disabled')
42
+ }
43
+
44
+ async function pickDir(): Promise<void> {
45
+ try {
46
+ const dir = await invoke<string | null>('dialog', 'open', 'openDirectory', 'Elegir carpeta')
47
+ if (dir) ($('#dir') as HTMLInputElement).value = dir
48
+ } catch {
49
+ /* cancelado */
50
+ }
51
+ }
52
+
53
+ async function doUninstall(): Promise<void> {
54
+ const dir = ($('#dir') as HTMLInputElement).value.trim()
55
+ if (!dir) {
56
+ setStatus('Elige la carpeta de instalación', 'err')
57
+ return
58
+ }
59
+ $('#uninstall').setAttribute('disabled', 'true')
60
+ setStatus('Desinstalando…')
61
+ try {
62
+ const res = await invoke<{ removed: number }>('installer', 'uninstall', {
63
+ dir,
64
+ keepData: ($('#keepData') as HTMLInputElement).checked,
65
+ })
66
+ setStatus(`Eliminados ${res.removed} ficheros.`, 'ok')
67
+ setTimeout(() => void invoke('ow-window', 'close', window.__owWindowId), 900)
68
+ } catch (e) {
69
+ setStatus(e instanceof Error ? e.message : String(e), 'err')
70
+ $('#uninstall').removeAttribute('disabled')
71
+ }
72
+ }
73
+
74
+ $('#pick').addEventListener('click', () => void pickDir())
75
+ $('#uninstall').addEventListener('click', () => void doUninstall())
76
+ void boot()
@@ -0,0 +1,10 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { defineConfig } from 'vite'
4
+ import owear from '@owear/vite-plugin'
5
+
6
+ export default defineConfig({
7
+ plugins: [owear()],
8
+ base: './',
9
+ build: { outDir: 'ui-dist', emptyOutDir: true },
10
+ })
@@ -0,0 +1,15 @@
1
+ // Copyright 2026 Owear Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ import { defineConfig } from 'vite'
5
+ import owear from '@owear/vite-plugin'
6
+
7
+ export default defineConfig({
8
+ plugins: [owear()],
9
+ base: './',
10
+ build: {
11
+ // `ow build installer` recoge este directorio como `ui/` del instalador.
12
+ outDir: 'ui-dist',
13
+ emptyOutDir: true,
14
+ },
15
+ })
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ // Copyright 2026 Owear Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+ // tools/owear-installer.mjs — ensambla el payload del INSTALADOR y lo empaqueta
6
+ // dentro del binario instalador (kernel + footer OWPK1).
7
+ //
8
+ // El instalador es un binario Owear aparte: al arrancar detecta `installer.json`
9
+ // (o `uninstaller.json`) y entra en modo installer/uninstaller (ver
10
+ // src/Core/App/Internal.cpp), sirviendo `ui/` como assets y exponiendo la API
11
+ // `installer` (src/api/installer).
12
+ //
13
+ // Layout del payload del instalador:
14
+ // installer.json metadatos de la app + modo/orden (del bridge)
15
+ // bridge.json contrato embebido (owear.bridge.ts compilado)
16
+ // ui/ renderer + sidecar del instalador
17
+ // payload/ lo que se instala (binario único [minimal] o árbol [layout])
18
+ // modules/ módulos .owm extra (opcional)
19
+ //
20
+ // Uso:
21
+ // node tools/owear-installer.mjs --kernel <owear> --out <MiAppInstaller> \
22
+ // --ui <distUI> --payload <dir> [--bridge <bridge.json>] [--meta <installer.json>] \
23
+ // [--modules <dir>] [--name <slug>]
24
+ //
25
+ import * as fs from 'node:fs'
26
+ import * as os from 'node:os'
27
+ import * as path from 'node:path'
28
+ import { spawnSync } from 'node:child_process'
29
+ import { fileURLToPath } from 'node:url'
30
+
31
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
32
+
33
+ function die(m) {
34
+ console.error(`[owear-installer] ✗ ${m}`)
35
+ process.exit(1)
36
+ }
37
+ const argv = process.argv.slice(2)
38
+ const arg = (n, def = null) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : def)
39
+
40
+ const kernel = arg('--kernel')
41
+ const out = arg('--out')
42
+ const ui = arg('--ui')
43
+ const payload = arg('--payload')
44
+ const bridge = arg('--bridge')
45
+ const meta = arg('--meta')
46
+ const modules = arg('--modules')
47
+ const name = arg('--name', 'app')
48
+
49
+ if (!kernel || !out || !ui) die('uso: --kernel <owear> --out <bin> --ui <distUI> [--payload <dir>]')
50
+ if (!fs.existsSync(kernel)) die(`no existe el kernel: ${kernel}`)
51
+ if (!fs.existsSync(ui)) die(`no existe el ui: ${ui}`)
52
+
53
+ const stage = fs.mkdtempSync(path.join(os.tmpdir(), 'owear-installer-'))
54
+ const copyInto = (src, dstRel) => {
55
+ const dst = path.join(stage, dstRel)
56
+ fs.mkdirSync(path.dirname(dst), { recursive: true })
57
+ fs.cpSync(src, dst, { recursive: true })
58
+ }
59
+
60
+ copyInto(ui, 'ui')
61
+ if (payload && fs.existsSync(payload)) copyInto(payload, 'payload')
62
+ if (bridge && fs.existsSync(bridge)) fs.copyFileSync(bridge, path.join(stage, 'bridge.json'))
63
+ if (meta && fs.existsSync(meta)) fs.copyFileSync(meta, path.join(stage, 'installer.json'))
64
+ if (modules && fs.existsSync(modules)) copyInto(modules, 'modules')
65
+
66
+ // manifiesto mínimo del payload del instalador (informativo)
67
+ fs.writeFileSync(
68
+ path.join(stage, 'manifest.json'),
69
+ JSON.stringify({ kind: 'installer', name, createdAt: Date.now() }, null, 2) + '\n',
70
+ )
71
+
72
+ // Empaqueta con el empaquetador existente (mismo formato OWPK1).
73
+ const packer = path.join(__dirname, 'owear-pack.mjs')
74
+ const r = spawnSync(
75
+ process.execPath,
76
+ [packer, '--kernel', kernel, '--in', stage, '--out', out],
77
+ { stdio: 'inherit' },
78
+ )
79
+ fs.rmSync(stage, { recursive: true, force: true })
80
+ if (r.status !== 0) die('falló owear-pack')
81
+ console.log(`[owear-installer] instalador: ${out}`)