@owear/cli 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.
- package/package.json +10 -8
- package/src/ow.js +548 -25
- package/template/README.md +34 -28
- package/template/app/main.ts +273 -4
- package/template/index.html +117 -28
- package/template/package.json +1 -1
- package/template/public/favicon.svg +4 -0
- package/template/src/assets/dancing-script.woff2 +0 -0
- package/template/src/assets/jetbrains-mono.woff2 +0 -0
- package/template/src/ow.d.ts +9 -1
- package/template/src/renderer.ts +489 -1
- package/template/src/style.css +280 -56
- package/template-installer/README.md +38 -0
- package/template-installer/app/main.ts +22 -0
- package/template-installer/index.html +70 -0
- package/template-installer/package.json +22 -0
- package/template-installer/src/assets/dancing-script.woff2 +0 -0
- package/template-installer/src/assets/jetbrains-mono.woff2 +0 -0
- package/template-installer/src/installer.ts +171 -0
- package/template-installer/src/style.css +442 -0
- package/template-installer/tsconfig.json +13 -0
- package/template-installer/uninstaller/app/main.ts +16 -0
- package/template-installer/uninstaller/index.html +54 -0
- package/template-installer/uninstaller/package.json +22 -0
- package/template-installer/uninstaller/src/uninstaller.ts +101 -0
- package/template-installer/uninstaller/vite.config.ts +10 -0
- package/template-installer/vite.config.ts +15 -0
- package/tools/owear-installer.mjs +86 -0
- package/tools/owear-linux.mjs +158 -0
- package/tools/owear-msi.mjs +113 -0
- package/tools/owear-pack.mjs +93 -0
- package/tools/owear-sign.mjs +204 -0
- package/tools/owear-update.mjs +150 -0
- package/LICENSE +0 -201
package/src/ow.js
CHANGED
|
@@ -65,12 +65,16 @@ function runtimePackageDir() {
|
|
|
65
65
|
* - monorepo: cada build/<preset>/api/<nombre>/ (el loader NO recursiona)
|
|
66
66
|
*/
|
|
67
67
|
function stockModulesPath() {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
// Dev: si el usuario fija el kernel (OW_KERNEL_BIN), sus módulos viven junto
|
|
69
|
+
// a él (<exe>/modules). Es la fuente correcta: kernel y módulos del mismo build.
|
|
70
|
+
const kb = process.env.OW_KERNEL_BIN
|
|
71
|
+
if (kb) {
|
|
72
|
+
const m = path.join(path.dirname(kb), 'modules')
|
|
71
73
|
if (fs.existsSync(m)) return m
|
|
72
74
|
}
|
|
73
|
-
|
|
75
|
+
// Dev en el monorepo: preferir el BUILD LOCAL (mismo build que el kernel)
|
|
76
|
+
// ANTES del runtime package, que puede ser un artefacto prebuilt antiguo.
|
|
77
|
+
const api = path.resolve(__dirname, '../../../build', platformPreset(), 'src', 'api')
|
|
74
78
|
if (fs.existsSync(api)) {
|
|
75
79
|
const dirs = fs
|
|
76
80
|
.readdirSync(api, { withFileTypes: true })
|
|
@@ -78,6 +82,11 @@ function stockModulesPath() {
|
|
|
78
82
|
.map((d) => path.join(api, d.name))
|
|
79
83
|
if (dirs.length) return dirs.join(path.delimiter)
|
|
80
84
|
}
|
|
85
|
+
const rt = runtimePackageDir()
|
|
86
|
+
if (rt) {
|
|
87
|
+
const m = path.join(rt, 'bin', 'modules')
|
|
88
|
+
if (fs.existsSync(m)) return m
|
|
89
|
+
}
|
|
81
90
|
return ''
|
|
82
91
|
}
|
|
83
92
|
|
|
@@ -139,6 +148,7 @@ async function main() {
|
|
|
139
148
|
case 'dev': return cmdDev(rest)
|
|
140
149
|
case 'build': return cmdBuild(rest)
|
|
141
150
|
case 'api': return cmdApi(rest)
|
|
151
|
+
case 'update': return cmdUpdate(rest)
|
|
142
152
|
default:
|
|
143
153
|
die(`comando desconocido: ${cmd} (usa --help)`)
|
|
144
154
|
}
|
|
@@ -148,35 +158,64 @@ function printHelp() {
|
|
|
148
158
|
console.log(`
|
|
149
159
|
${C.cyan}owear${C.reset} — framework desktop nativo
|
|
150
160
|
|
|
151
|
-
${C.green}ow create <dir>${C.reset}
|
|
152
|
-
${C.green}ow
|
|
153
|
-
${C.green}ow
|
|
154
|
-
${C.green}ow
|
|
155
|
-
${C.green}ow
|
|
161
|
+
${C.green}ow create <dir>${C.reset} crea una app nueva
|
|
162
|
+
${C.green}ow create installer <dir>${C.reset} scaffoldea el instalador
|
|
163
|
+
${C.green}ow create uninstaller <dir>${C.reset} scaffoldea el desinstalador
|
|
164
|
+
${C.green}ow dev${C.reset} desarrollo: vite + kernel + sidecar node
|
|
165
|
+
${C.green}ow build${C.reset} build de producción (dist/)
|
|
166
|
+
${C.green}ow build app${C.reset} payload de la app (--format binary|deb|appimage)
|
|
167
|
+
${C.green}ow build installer${C.reset} binario instalador (linux) / .exe (win)
|
|
168
|
+
${C.green}ow build uninstaller${C.reset} binario desinstalador
|
|
169
|
+
${C.green}ow api list${C.reset} lista las APIs del repo y sus manifiestos
|
|
170
|
+
${C.green}ow api new <nombre>${C.reset} scaffoldea una API (api/<nombre>/ + manifiesto)
|
|
171
|
+
${C.green}ow update --file <bin> --version <v>${C.reset} publica un update (blockmap + manifiesto YAML firmado)
|
|
172
|
+
${C.green}ow update --gen-key <ruta>${C.reset} genera un par de claves Ed25519 para firmar
|
|
156
173
|
|
|
157
174
|
Variables útiles:
|
|
158
175
|
OW_KERNEL_BIN ruta al binario owear
|
|
176
|
+
OW_SIGN_KEY clave privada Ed25519 para firmar artefactos (→ <file>.sig)
|
|
177
|
+
OW_SIGN_PFX certificado PKCS#12 para Authenticode (Windows PE/MSI)
|
|
178
|
+
OW_SIGN_PFX_PASSWORD contraseña del PFX (o --pfx-password-env)
|
|
179
|
+
|
|
180
|
+
Firma (ow build app|installer|uninstaller):
|
|
181
|
+
--sign-key <pem> firma Ed25519 desprendida del artefacto
|
|
182
|
+
--pfx <p12> Authenticode (requiere osslsigncode/signtool)
|
|
183
|
+
--timestamp <url> sellado de tiempo Authenticode
|
|
184
|
+
--require-sign falla el build si no se firma
|
|
159
185
|
`)
|
|
160
186
|
}
|
|
161
187
|
|
|
162
188
|
function cmdCreate(args) {
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
189
|
+
const sub = args[0]
|
|
190
|
+
if (sub === 'installer') {
|
|
191
|
+
return scaffold('template-installer', args[1] ?? 'installer', 'instalador')
|
|
192
|
+
}
|
|
193
|
+
if (sub === 'uninstaller') {
|
|
194
|
+
return scaffold('template-installer/uninstaller', args[1] ?? 'uninstaller', 'desinstalador')
|
|
195
|
+
}
|
|
196
|
+
const dir = sub
|
|
197
|
+
if (!dir) die('uso: ow create [installer|uninstaller] <dir>')
|
|
198
|
+
const target = scaffold('template', dir, 'app')
|
|
199
|
+
log('siguientes pasos:')
|
|
200
|
+
console.log(` cd ${path.basename(target)}`)
|
|
201
|
+
console.log(' npm install')
|
|
202
|
+
console.log(' npm run dev')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Copia un template del CLI a `dir` y sustituye placeholders. */
|
|
206
|
+
function scaffold(templateRel, dir, what) {
|
|
165
207
|
const target = path.resolve(dir)
|
|
166
208
|
if (fs.existsSync(target) && fs.readdirSync(target).length) {
|
|
167
209
|
die(`el directorio ya existe y no está vacío: ${target}`)
|
|
168
210
|
}
|
|
169
|
-
const templateDir = path.resolve(__dirname, '
|
|
211
|
+
const templateDir = path.resolve(__dirname, '..', templateRel)
|
|
170
212
|
if (!fs.existsSync(templateDir)) {
|
|
171
213
|
die(`template no encontrado en ${templateDir} (instala @owear/cli completo)`)
|
|
172
214
|
}
|
|
173
215
|
fs.cpSync(templateDir, target, { recursive: true })
|
|
174
216
|
applyAppName(target)
|
|
175
|
-
log(
|
|
176
|
-
|
|
177
|
-
console.log(` cd ${path.basename(target)}`)
|
|
178
|
-
console.log(' npm install')
|
|
179
|
-
console.log(' npm run dev')
|
|
217
|
+
log(`${what} en ${target}`)
|
|
218
|
+
return target
|
|
180
219
|
}
|
|
181
220
|
|
|
182
221
|
/**
|
|
@@ -228,7 +267,7 @@ function cmdApi(args) {
|
|
|
228
267
|
}
|
|
229
268
|
|
|
230
269
|
function readApiManifests(root) {
|
|
231
|
-
const apiDir = path.join(root, 'api')
|
|
270
|
+
const apiDir = path.join(root, 'src', 'api')
|
|
232
271
|
const out = []
|
|
233
272
|
for (const d of fs.readdirSync(apiDir, { withFileTypes: true })) {
|
|
234
273
|
if (!d.isDirectory()) continue
|
|
@@ -260,7 +299,7 @@ function cmdApiList(root) {
|
|
|
260
299
|
function cmdApiNew(root, name) {
|
|
261
300
|
if (!name) die('uso: ow api new <nombre>')
|
|
262
301
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) die('nombre inválido (minúsculas, dígitos y guiones; debe empezar por letra)')
|
|
263
|
-
const dir = path.join(root, 'api', name)
|
|
302
|
+
const dir = path.join(root, 'src', 'api', name)
|
|
264
303
|
if (fs.existsSync(dir) && fs.readdirSync(dir).length) die(`api/${name}/ ya existe y no está vacío`)
|
|
265
304
|
|
|
266
305
|
fs.mkdirSync(path.join(dir, 'src'), { recursive: true })
|
|
@@ -353,7 +392,7 @@ function findMainEntry(cwd) {
|
|
|
353
392
|
* así que compilamos con esbuild (viene con vite). De este modo el runtime del
|
|
354
393
|
* sistema sirve y el arranque no depende de la versión que tenga el usuario.
|
|
355
394
|
*/
|
|
356
|
-
function prepareMain(cwd, outDir) {
|
|
395
|
+
function prepareMain(cwd, outDir, bundle = false) {
|
|
357
396
|
const entry = findMainEntry(cwd)
|
|
358
397
|
if (!entry) return null
|
|
359
398
|
if (entry.endsWith('.js') || entry.endsWith('.mjs')) return entry
|
|
@@ -366,7 +405,11 @@ function prepareMain(cwd, outDir) {
|
|
|
366
405
|
[
|
|
367
406
|
'esbuild', entry,
|
|
368
407
|
'--bundle', '--platform=node', '--format=esm',
|
|
369
|
-
|
|
408
|
+
// En producción se EMBEBE @owear/core (y deps) en main.js: el payload de
|
|
409
|
+
// un single-binary/instalador no lleva node_modules. En dev se dejan
|
|
410
|
+
// externas (node_modules está presente).
|
|
411
|
+
...(bundle ? [] : ['--packages=external']),
|
|
412
|
+
`--outfile=${out}`, '--log-level=warning',
|
|
370
413
|
],
|
|
371
414
|
{ cwd, stdio: 'inherit', shell: process.platform === 'win32' }
|
|
372
415
|
)
|
|
@@ -376,6 +419,59 @@ function prepareMain(cwd, outDir) {
|
|
|
376
419
|
return out
|
|
377
420
|
}
|
|
378
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Entries de worker de la app: `app/workers/**\/*.{ts,mts,js,mjs}`.
|
|
424
|
+
* Se compilan a <outDir>/workers/ conservando la ruta relativa, y el kernel
|
|
425
|
+
* expone el directorio al main vía OW_APP_WORKERS para `app.forkWorker()`.
|
|
426
|
+
*/
|
|
427
|
+
function findWorkerEntries(cwd) {
|
|
428
|
+
const dir = path.join(cwd, 'app', 'workers')
|
|
429
|
+
if (!fs.existsSync(dir)) return []
|
|
430
|
+
const out = []
|
|
431
|
+
const walk = (d) => {
|
|
432
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
433
|
+
const p = path.join(d, e.name)
|
|
434
|
+
if (e.isDirectory()) walk(p)
|
|
435
|
+
else if (/\.(ts|mts|js|mjs)$/.test(e.name)) out.push(p)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
walk(dir)
|
|
439
|
+
return out
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function prepareWorkers(cwd, outDir) {
|
|
443
|
+
const entries = findWorkerEntries(cwd)
|
|
444
|
+
if (!entries.length) return null
|
|
445
|
+
|
|
446
|
+
const root = path.join(cwd, 'app', 'workers')
|
|
447
|
+
const workersDir = path.join(outDir, 'workers')
|
|
448
|
+
const built = []
|
|
449
|
+
|
|
450
|
+
for (const entry of entries) {
|
|
451
|
+
const rel = path.relative(root, entry)
|
|
452
|
+
const out = path.join(workersDir, rel.replace(/\.(ts|mts)$/, '.js'))
|
|
453
|
+
fs.mkdirSync(path.dirname(out), { recursive: true })
|
|
454
|
+
|
|
455
|
+
if (entry.endsWith('.js') || entry.endsWith('.mjs')) {
|
|
456
|
+
fs.copyFileSync(entry, out)
|
|
457
|
+
} else {
|
|
458
|
+
log(`compilando worker ${path.relative(cwd, entry)}…`)
|
|
459
|
+
const r = spawnSync(
|
|
460
|
+
'npx',
|
|
461
|
+
[
|
|
462
|
+
'esbuild', entry,
|
|
463
|
+
'--bundle', '--platform=node', '--format=esm',
|
|
464
|
+
'--packages=external', `--outfile=${out}`, '--log-level=warning',
|
|
465
|
+
],
|
|
466
|
+
{ cwd, stdio: 'inherit', shell: process.platform === 'win32' }
|
|
467
|
+
)
|
|
468
|
+
if (r.status !== 0) die(`no se pudo compilar el worker ${path.relative(cwd, entry)}`)
|
|
469
|
+
}
|
|
470
|
+
built.push(out)
|
|
471
|
+
}
|
|
472
|
+
return workersDir
|
|
473
|
+
}
|
|
474
|
+
|
|
379
475
|
async function cmdDev() {
|
|
380
476
|
const cwd = process.cwd()
|
|
381
477
|
if (!fs.existsSync(path.join(cwd, 'package.json'))) die('ejecuta dentro de tu app')
|
|
@@ -411,6 +507,9 @@ async function cmdDev() {
|
|
|
411
507
|
const mainJs = prepareMain(cwd, path.join(cwd, '.owear'))
|
|
412
508
|
if (mainJs) log(`proceso principal: ${path.relative(cwd, mainJs)}`)
|
|
413
509
|
|
|
510
|
+
const workersDir = prepareWorkers(cwd, path.join(cwd, '.owear'))
|
|
511
|
+
if (workersDir) log(`workers: ${path.relative(cwd, workersDir)}`)
|
|
512
|
+
|
|
414
513
|
log(`lanzando kernel: ${kernelBin}`)
|
|
415
514
|
const kernel = spawn(kernelBin, [], {
|
|
416
515
|
stdio: 'inherit',
|
|
@@ -418,7 +517,8 @@ async function cmdDev() {
|
|
|
418
517
|
...process.env,
|
|
419
518
|
OW_APP_NAME: JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')).name ?? 'Owear App',
|
|
420
519
|
OW_DEV_SERVER_URL: 'http://localhost:5173/',
|
|
421
|
-
...(mainJs ? { OW_APP_MAIN: mainJs } : {}),
|
|
520
|
+
...(mainJs ? { OW_APP_MAIN: mainJs } : { OW_START_URL: 'http://localhost:5173/' }),
|
|
521
|
+
...(workersDir ? { OW_APP_WORKERS: workersDir } : {}),
|
|
422
522
|
...(() => {
|
|
423
523
|
// módulos stock (fs/path/…) + los nativos de la app, si los hay
|
|
424
524
|
const dirs = [stockModulesPath(), modulesDir].filter(Boolean)
|
|
@@ -438,13 +538,26 @@ async function cmdDev() {
|
|
|
438
538
|
})
|
|
439
539
|
}
|
|
440
540
|
|
|
441
|
-
|
|
442
|
-
|
|
541
|
+
// ── ow build [app|installer|uninstaller] ─────────────────────────────────────
|
|
542
|
+
|
|
543
|
+
async function cmdBuild(args) {
|
|
544
|
+
const sub = args[0]
|
|
545
|
+
if (sub === 'installer') return cmdBuildInstaller(process.cwd(), args.slice(1), false)
|
|
546
|
+
if (sub === 'uninstaller') return cmdBuildInstaller(process.cwd(), args.slice(1), true)
|
|
547
|
+
if (sub === 'app') return cmdBuildApp(process.cwd(), args.slice(1))
|
|
548
|
+
return cmdBuildBundle(process.cwd())
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Build de la app → dist/ (vite + main.js + workers + módulos nativos). */
|
|
552
|
+
async function cmdBuildBundle(cwd) {
|
|
443
553
|
log('vite build…')
|
|
444
554
|
const code = await runProc('npx', ['vite', 'build'], { cwd, shell: process.platform === 'win32' })
|
|
445
555
|
if (code !== 0) die('vite build falló')
|
|
446
556
|
|
|
447
|
-
const mainJs = prepareMain(cwd, path.join(cwd, 'dist'))
|
|
557
|
+
const mainJs = prepareMain(cwd, path.join(cwd, 'dist'), true)
|
|
558
|
+
|
|
559
|
+
const workersDir = prepareWorkers(cwd, path.join(cwd, 'dist'))
|
|
560
|
+
if (workersDir) log(`workers: ${path.relative(cwd, workersDir)}`)
|
|
448
561
|
|
|
449
562
|
const nativeDir = path.join(cwd, 'native')
|
|
450
563
|
if (fs.existsSync(nativeDir)) {
|
|
@@ -466,8 +579,418 @@ async function cmdBuild() {
|
|
|
466
579
|
.join(path.delimiter)
|
|
467
580
|
console.log(
|
|
468
581
|
` OW_ASSETS_DIR=dist${mainJs ? ' OW_APP_MAIN=dist/main.js' : ''}` +
|
|
582
|
+
`${workersDir ? ' OW_APP_WORKERS=dist/workers' : ''}` +
|
|
469
583
|
`${runMods ? ` OW_MODULES_DIR="${runMods}"` : ''} ./owear`
|
|
470
584
|
)
|
|
585
|
+
return { dist: path.join(cwd, 'dist'), mainJs, workersDir }
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ── empaquetado (D1) ─────────────────────────────────────────────────────────
|
|
589
|
+
|
|
590
|
+
/** Ruta a un script de empaquetado incluido en el paquete @owear/cli. */
|
|
591
|
+
function packTool(name) {
|
|
592
|
+
const local = path.resolve(__dirname, '..', 'tools', name)
|
|
593
|
+
if (fs.existsSync(local)) return local
|
|
594
|
+
const root = owearRepoRoot()
|
|
595
|
+
if (root && fs.existsSync(path.join(root, 'tools', name))) {
|
|
596
|
+
return path.join(root, 'tools', name)
|
|
597
|
+
}
|
|
598
|
+
die(`no encuentro ${name} (reinstala @owear/cli)`)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Plataforma del destino (para elegir target del bridge). */
|
|
602
|
+
function platformKey() {
|
|
603
|
+
return process.platform === 'win32' ? 'win' : process.platform === 'darwin' ? 'mac' : 'linux'
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Slug del proyecto (nombre de package.json sin scope). */
|
|
607
|
+
function appSlug(cwd) {
|
|
608
|
+
try {
|
|
609
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'))
|
|
610
|
+
return (pkg.name ?? 'app').replace(/^@[^/]+\//, '')
|
|
611
|
+
} catch {
|
|
612
|
+
return path.basename(cwd)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function flag(args, name, def = null) {
|
|
617
|
+
return args.includes(name) ? args[args.indexOf(name) + 1] : def
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function readPackConfig(cwd) {
|
|
621
|
+
const p = path.join(cwd, 'owear.pack.json')
|
|
622
|
+
if (!fs.existsSync(p)) return {}
|
|
623
|
+
try {
|
|
624
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
625
|
+
} catch (e) {
|
|
626
|
+
die(`owear.pack.json inválido: ${e.message}`)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** Ensambla el bundle `{app/,modules/,manifest.json}` que espera el kernel. */
|
|
631
|
+
function assembleAppBundle(cwd, built) {
|
|
632
|
+
const bundle = path.join(cwd, '.owear', 'app-bundle')
|
|
633
|
+
fs.rmSync(bundle, { recursive: true, force: true })
|
|
634
|
+
fs.mkdirSync(bundle, { recursive: true })
|
|
635
|
+
fs.cpSync(built.dist, path.join(bundle, 'app'), { recursive: true })
|
|
636
|
+
const mods = path.join(built.dist, 'modules')
|
|
637
|
+
if (fs.existsSync(mods)) fs.cpSync(mods, path.join(bundle, 'modules'), { recursive: true })
|
|
638
|
+
fs.writeFileSync(
|
|
639
|
+
path.join(bundle, 'manifest.json'),
|
|
640
|
+
JSON.stringify({ name: appSlug(cwd), createdAt: Date.now() }, null, 2) + '\n'
|
|
641
|
+
)
|
|
642
|
+
return bundle
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function packDir(kernel, inDir, out) {
|
|
646
|
+
const r = spawnSync(
|
|
647
|
+
process.execPath,
|
|
648
|
+
[packTool('owear-pack.mjs'), '--kernel', kernel, '--in', inDir, '--out', out],
|
|
649
|
+
{ stdio: 'inherit' }
|
|
650
|
+
)
|
|
651
|
+
if (r.status !== 0) die('falló owear-pack')
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** Compila `owear.bridge.ts` → `.owear/bridge.json` y devuelve el objeto. */
|
|
655
|
+
async function buildBridge(cwd, optional = false) {
|
|
656
|
+
const { pathToFileURL } = await import('node:url')
|
|
657
|
+
const src = ['owear.bridge.ts', 'owear.bridge.mts', 'owear.bridge.js', 'owear.bridge.mjs']
|
|
658
|
+
.map((n) => path.join(cwd, n))
|
|
659
|
+
.find((p) => fs.existsSync(p))
|
|
660
|
+
if (!src) {
|
|
661
|
+
if (optional) return null
|
|
662
|
+
die('no encuentro owear.bridge.ts — el instalador necesita el bridge de la app')
|
|
663
|
+
}
|
|
664
|
+
const outMjs = path.join(cwd, '.owear', 'bridge.mjs')
|
|
665
|
+
fs.mkdirSync(path.dirname(outMjs), { recursive: true })
|
|
666
|
+
log(`compilando ${path.basename(src)}…`)
|
|
667
|
+
const r = spawnSync(
|
|
668
|
+
'npx',
|
|
669
|
+
['esbuild', src, '--bundle', '--platform=node', '--format=esm', '--packages=external',
|
|
670
|
+
`--outfile=${outMjs}`, '--log-level=warning'],
|
|
671
|
+
{ cwd, stdio: 'inherit', shell: process.platform === 'win32' }
|
|
672
|
+
)
|
|
673
|
+
if (r.status !== 0) die('no se pudo compilar owear.bridge')
|
|
674
|
+
const mod = await import(`${pathToFileURL(outMjs).href}?t=${Date.now()}`)
|
|
675
|
+
const obj = mod.default ?? mod
|
|
676
|
+
const jsonPath = path.join(cwd, '.owear', 'bridge.json')
|
|
677
|
+
fs.writeFileSync(jsonPath, JSON.stringify(obj, null, 2) + '\n')
|
|
678
|
+
return { obj, jsonPath }
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Compila la UI del instalador/desinstalador → `ui-dist/` (+ sidecar main.js). */
|
|
682
|
+
async function buildInstallerUI(cwd, which) {
|
|
683
|
+
const dir =
|
|
684
|
+
which === 'uninstaller' ? path.join(cwd, 'installer', 'uninstaller') : path.join(cwd, 'installer')
|
|
685
|
+
if (!fs.existsSync(dir)) {
|
|
686
|
+
die(`no existe ${path.relative(cwd, dir)} — créalo con: ow create installer`)
|
|
687
|
+
}
|
|
688
|
+
log(`vite build (${which})…`)
|
|
689
|
+
const code = await runProc('npx', ['vite', 'build'], { cwd: dir, shell: process.platform === 'win32' })
|
|
690
|
+
if (code !== 0) die(`vite build del ${which} falló`)
|
|
691
|
+
const uiDist = path.join(dir, 'ui-dist')
|
|
692
|
+
|
|
693
|
+
// sidecar del instalador (Node): se empaqueta dentro del propio ui/
|
|
694
|
+
const entry = path.join(dir, 'app', 'main.ts')
|
|
695
|
+
if (fs.existsSync(entry)) {
|
|
696
|
+
const out = path.join(uiDist, 'main.js')
|
|
697
|
+
log(`compilando ${which}/app/main.ts…`)
|
|
698
|
+
const r = spawnSync(
|
|
699
|
+
'npx',
|
|
700
|
+
['esbuild', entry, '--bundle', '--platform=node', '--format=esm', `--outfile=${out}`, '--log-level=warning'],
|
|
701
|
+
{ cwd: dir, stdio: 'inherit', shell: process.platform === 'win32' }
|
|
702
|
+
)
|
|
703
|
+
if (r.status !== 0) die(`no se pudo compilar ${which}/app/main.ts`)
|
|
704
|
+
}
|
|
705
|
+
return uiDist
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** Ejecuta un script de tools/ del repo con Node. */
|
|
709
|
+
function runTool(name, args) {
|
|
710
|
+
const r = spawnSync(process.execPath, [packTool(name), ...args], { stdio: 'inherit' })
|
|
711
|
+
if (r.status !== 0) die(`falló ${name}`)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Firma un artefacto recién construido. Authenticode si hay `--pfx`
|
|
716
|
+
* (`OW_SIGN_PFX`), Ed25519 desprendida (`<file>.sig`) si hay `--sign-key`
|
|
717
|
+
* (`OW_SIGN_KEY`). Nunca rompe el build: sin material de firma no hace nada;
|
|
718
|
+
* con `--require-sign` la firma pasa a ser obligatoria.
|
|
719
|
+
*/
|
|
720
|
+
function signArtifact(args, file) {
|
|
721
|
+
const key = flag(args, '--sign-key', process.env.OW_SIGN_KEY ?? '')
|
|
722
|
+
const pfx = flag(args, '--pfx', process.env.OW_SIGN_PFX ?? '')
|
|
723
|
+
if (!key && !pfx) return
|
|
724
|
+
const extra = []
|
|
725
|
+
if (key) extra.push('--ed25519-key', key)
|
|
726
|
+
if (pfx) {
|
|
727
|
+
extra.push('--pfx', pfx, '--pfx-password-env', flag(args, '--pfx-password-env', 'OW_SIGN_PFX_PASSWORD'))
|
|
728
|
+
}
|
|
729
|
+
const ts = flag(args, '--timestamp', process.env.OW_SIGN_TIMESTAMP ?? '')
|
|
730
|
+
if (ts) extra.push('--timestamp', ts)
|
|
731
|
+
if (args.includes('--require-sign')) extra.push('--require')
|
|
732
|
+
runTool('owear-sign.mjs', ['--file', file, ...extra])
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* ow update — publica un update del auto-updater: calcula el blockmap (delta
|
|
737
|
+
* por bloques), genera el manifiesto YAML del canal y lo firma (Ed25519).
|
|
738
|
+
* Delega en tools/owear-update.mjs (mismos flags).
|
|
739
|
+
*/
|
|
740
|
+
function cmdUpdate(args) {
|
|
741
|
+
if (!args.length) {
|
|
742
|
+
console.log(`
|
|
743
|
+
${C.cyan}ow update${C.reset} — publica un update (delta + manifiesto YAML firmado)
|
|
744
|
+
|
|
745
|
+
${C.green}ow update --gen-key owear-signing${C.reset}
|
|
746
|
+
genera el par de claves Ed25519 (privada <ruta>.pem; pública <ruta>.pub.b64,
|
|
747
|
+
que va en el bridge como ${C.dim}updater.publicKey${C.reset})
|
|
748
|
+
|
|
749
|
+
${C.green}ow update --file release/MiApp-1.4.0 --version 1.4.0 --key owear-signing.pem${C.reset} \\
|
|
750
|
+
[--channel latest] [--url https://up.miapp.dev] [--notes "…"|--notes @notas.md] \\
|
|
751
|
+
[--block-size 262144] [--mandatory] [--out-dir release]
|
|
752
|
+
|
|
753
|
+
Salidas: ${C.dim}<file>.blockmap${C.reset} (bloques sha256) y ${C.dim}<channel>.yml${C.reset} (manifiesto YAML).
|
|
754
|
+
`)
|
|
755
|
+
return
|
|
756
|
+
}
|
|
757
|
+
runTool('owear-update.mjs', args)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function readPkg(cwd) {
|
|
761
|
+
try {
|
|
762
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'))
|
|
763
|
+
} catch {
|
|
764
|
+
return {}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function appMeta(cwd) {
|
|
769
|
+
const bridge = await buildBridge(cwd, true)
|
|
770
|
+
const pkg = readPkg(cwd)
|
|
771
|
+
const slug = appSlug(cwd)
|
|
772
|
+
return {
|
|
773
|
+
bridge,
|
|
774
|
+
slug,
|
|
775
|
+
appId: bridge?.obj?.app?.id ?? slug,
|
|
776
|
+
appName: bridge?.obj?.app?.name ?? pkg.name ?? slug,
|
|
777
|
+
version: bridge?.obj?.app?.version ?? pkg.version ?? '0.0.0',
|
|
778
|
+
publisher: bridge?.obj?.app?.publisher ?? 'unknown',
|
|
779
|
+
icon: bridge?.obj?.app?.icon ?? null,
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/** Copia kernel + dist + modules + main/workers a `dir` (layout de app). */
|
|
784
|
+
function stageAppLayout(cwd, built, dir, kernel) {
|
|
785
|
+
fs.rmSync(dir, { recursive: true, force: true })
|
|
786
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
787
|
+
const exe = process.platform === 'win32' ? 'owear.exe' : 'owear'
|
|
788
|
+
fs.copyFileSync(kernel, path.join(dir, exe))
|
|
789
|
+
fs.chmodSync(path.join(dir, exe), 0o755)
|
|
790
|
+
fs.cpSync(built.dist, path.join(dir, 'app'), { recursive: true })
|
|
791
|
+
const mods = path.join(built.dist, 'modules')
|
|
792
|
+
if (fs.existsSync(mods)) fs.cpSync(mods, path.join(dir, 'modules'), { recursive: true })
|
|
793
|
+
if (built.mainJs) fs.copyFileSync(built.mainJs, path.join(dir, 'main.js'))
|
|
794
|
+
const workers = path.join(built.dist, 'workers')
|
|
795
|
+
if (fs.existsSync(workers)) fs.cpSync(workers, path.join(dir, 'workers'), { recursive: true })
|
|
796
|
+
return exe
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function desktopEntry(meta, execLine, iconName) {
|
|
800
|
+
return (
|
|
801
|
+
`[Desktop Entry]\nType=Application\nName=${meta.appName}\n` +
|
|
802
|
+
`Comment=${meta.appName}\nExec=${execLine}\n` +
|
|
803
|
+
(iconName ? `Icon=${iconName}\n` : '') +
|
|
804
|
+
`Terminal=false\nCategories=Utility;\n`
|
|
805
|
+
)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function copyIcon(cwd, meta, dstBase) {
|
|
809
|
+
if (!meta.icon) return null
|
|
810
|
+
const src = path.isAbsolute(meta.icon) ? meta.icon : path.join(cwd, meta.icon)
|
|
811
|
+
if (!fs.existsSync(src)) return null
|
|
812
|
+
const ext = path.extname(src) || '.png'
|
|
813
|
+
const dst = dstBase + ext
|
|
814
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true })
|
|
815
|
+
fs.copyFileSync(src, dst)
|
|
816
|
+
return path.basename(dst)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/** `ow build app --format binary|deb|appimage|msi` (payload de la app). */
|
|
820
|
+
async function cmdBuildApp(cwd, args) {
|
|
821
|
+
const fmt = flag(args, '--format', 'binary')
|
|
822
|
+
const built = await cmdBuildBundle(cwd)
|
|
823
|
+
const meta = await appMeta(cwd)
|
|
824
|
+
const slug = meta.slug
|
|
825
|
+
const outDir = path.join(cwd, 'release')
|
|
826
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
827
|
+
const kernel = ensureKernelBuilt(cwd)
|
|
828
|
+
const isWin = process.platform === 'win32'
|
|
829
|
+
|
|
830
|
+
if (fmt === 'binary') {
|
|
831
|
+
const bundle = assembleAppBundle(cwd, built)
|
|
832
|
+
const exe = isWin ? `${slug}.exe` : slug
|
|
833
|
+
const out = path.join(outDir, exe)
|
|
834
|
+
packDir(kernel, bundle, out)
|
|
835
|
+
log(`binario único: ${out}`)
|
|
836
|
+
signArtifact(args, out)
|
|
837
|
+
return
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (fmt === 'deb') {
|
|
841
|
+
const stage = path.join(cwd, '.owear', 'linux-stage')
|
|
842
|
+
const opt = path.join(stage, 'opt', slug)
|
|
843
|
+
stageAppLayout(cwd, built, opt, kernel)
|
|
844
|
+
const bin = path.join(stage, 'usr', 'bin', slug)
|
|
845
|
+
fs.mkdirSync(path.dirname(bin), { recursive: true })
|
|
846
|
+
fs.writeFileSync(
|
|
847
|
+
bin,
|
|
848
|
+
`#!/bin/sh\nAPPDIR="/opt/${slug}"\n` +
|
|
849
|
+
`export OW_ASSETS_DIR="$APPDIR/app"\n` +
|
|
850
|
+
`[ -f "$APPDIR/main.js" ] && export OW_APP_MAIN="$APPDIR/main.js"\n` +
|
|
851
|
+
`[ -d "$APPDIR/workers" ] && export OW_APP_WORKERS="$APPDIR/workers"\n` +
|
|
852
|
+
`[ -d "$APPDIR/modules" ] && export OW_MODULES_DIR="$APPDIR/modules"\n` +
|
|
853
|
+
`exec "$APPDIR/owear" "$@"\n`,
|
|
854
|
+
)
|
|
855
|
+
fs.chmodSync(bin, 0o755)
|
|
856
|
+
const apps = path.join(stage, 'usr', 'share', 'applications')
|
|
857
|
+
fs.mkdirSync(apps, { recursive: true })
|
|
858
|
+
const icons = path.join(stage, 'usr', 'share', 'icons', 'hicolor', '256x256', 'apps')
|
|
859
|
+
const iconName = copyIcon(cwd, meta, path.join(icons, meta.appId))
|
|
860
|
+
fs.writeFileSync(path.join(apps, `${meta.appId}.desktop`), desktopEntry(meta, slug, iconName ? meta.appId : null))
|
|
861
|
+
const out = path.join(outDir, `${slug}_${meta.version}_amd64.deb`)
|
|
862
|
+
runTool('owear-linux.mjs', ['deb', '--stage', stage, '--out', out, '--name', slug,
|
|
863
|
+
'--version', meta.version, '--maintainer', meta.publisher, '--description', meta.appName])
|
|
864
|
+
log(`.deb: ${out}`)
|
|
865
|
+
signArtifact(args, out)
|
|
866
|
+
return
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (fmt === 'appimage') {
|
|
870
|
+
const appDir = path.join(cwd, '.owear', `${slug}.AppDir`)
|
|
871
|
+
fs.rmSync(appDir, { recursive: true, force: true })
|
|
872
|
+
fs.mkdirSync(appDir, { recursive: true })
|
|
873
|
+
const usr = path.join(appDir, 'usr')
|
|
874
|
+
stageAppLayout(cwd, built, usr, kernel)
|
|
875
|
+
fs.writeFileSync(
|
|
876
|
+
path.join(appDir, 'AppRun'),
|
|
877
|
+
`#!/bin/sh\nAPPDIR="$(dirname "$(readlink -f "$0")")"\n` +
|
|
878
|
+
`export OW_ASSETS_DIR="$APPDIR/usr/app"\n` +
|
|
879
|
+
`[ -f "$APPDIR/usr/main.js" ] && export OW_APP_MAIN="$APPDIR/usr/main.js"\n` +
|
|
880
|
+
`[ -d "$APPDIR/usr/workers" ] && export OW_APP_WORKERS="$APPDIR/usr/workers"\n` +
|
|
881
|
+
`[ -d "$APPDIR/usr/modules" ] && export OW_MODULES_DIR="$APPDIR/usr/modules"\n` +
|
|
882
|
+
`exec "$APPDIR/usr/owear" "$@"\n`,
|
|
883
|
+
)
|
|
884
|
+
fs.chmodSync(path.join(appDir, 'AppRun'), 0o755)
|
|
885
|
+
const iconName = copyIcon(cwd, meta, path.join(appDir, meta.appId))
|
|
886
|
+
fs.writeFileSync(path.join(appDir, `${meta.appId}.desktop`), desktopEntry(meta, slug, iconName ? meta.appId : null))
|
|
887
|
+
const out = path.join(outDir, `${slug}-${meta.version}-x86_64.AppImage`)
|
|
888
|
+
runTool('owear-linux.mjs', ['appimage', '--appdir', appDir, '--out', out, '--arch', 'x86_64'])
|
|
889
|
+
log(`AppImage: ${out}`)
|
|
890
|
+
signArtifact(args, out)
|
|
891
|
+
return
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (fmt === 'msi') {
|
|
895
|
+
const stage = path.join(cwd, '.owear', 'win-stage')
|
|
896
|
+
stageAppLayout(cwd, built, stage, kernel)
|
|
897
|
+
const out = path.join(outDir, `${slug}-${meta.version}.msi`)
|
|
898
|
+
runTool('owear-msi.mjs', ['--stage', stage, '--out', out, '--name', slug,
|
|
899
|
+
'--version', meta.version, '--publisher', meta.publisher, '--appId', meta.appId])
|
|
900
|
+
log(`.MSI: ${out}`)
|
|
901
|
+
signArtifact(args, out)
|
|
902
|
+
return
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
die(`formato desconocido: ${fmt} (binary|deb|appimage|msi)`)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/** `ow build installer|uninstaller` → binario instalador/desinstalador. */
|
|
909
|
+
async function cmdBuildInstaller(cwd, args, isUninstaller) {
|
|
910
|
+
const kernel = ensureKernelBuilt(cwd)
|
|
911
|
+
const slug = appSlug(cwd)
|
|
912
|
+
const pack = readPackConfig(cwd)
|
|
913
|
+
const bridge = await buildBridge(cwd)
|
|
914
|
+
const target = bridge.obj?.targets?.[platformKey()] ?? {}
|
|
915
|
+
const mode = flag(args, '--mode', target.layout ?? pack.modes?.default ?? 'minimal')
|
|
916
|
+
|
|
917
|
+
const exe = process.platform === 'win32' ? '.exe' : ''
|
|
918
|
+
const outDir = path.join(cwd, 'release')
|
|
919
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
920
|
+
|
|
921
|
+
// 1) payload de la app (sólo para el instalador)
|
|
922
|
+
let payloadDir = null
|
|
923
|
+
if (!isUninstaller) {
|
|
924
|
+
const built = await cmdBuildBundle(cwd)
|
|
925
|
+
payloadDir = path.join(cwd, '.owear', 'installer-payload', 'payload')
|
|
926
|
+
fs.rmSync(payloadDir, { recursive: true, force: true })
|
|
927
|
+
const bundle = assembleAppBundle(cwd, built)
|
|
928
|
+
if (mode === 'minimal') {
|
|
929
|
+
fs.mkdirSync(payloadDir, { recursive: true })
|
|
930
|
+
packDir(kernel, bundle, path.join(payloadDir, `${slug}${exe}`))
|
|
931
|
+
} else {
|
|
932
|
+
fs.cpSync(bundle, payloadDir, { recursive: true })
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// 1b) desinstalador (si existe installer/uninstaller) → se instala junto a la app
|
|
936
|
+
if (fs.existsSync(path.join(cwd, 'installer', 'uninstaller'))) {
|
|
937
|
+
log('build del desinstalador…')
|
|
938
|
+
// propaga solo los flags de firma (no --mode/--format, que no aplican)
|
|
939
|
+
const signArgs = []
|
|
940
|
+
for (const f of ['--sign-key', '--pfx', '--pfx-password-env', '--timestamp']) {
|
|
941
|
+
const v = flag(args, f, null)
|
|
942
|
+
if (v) signArgs.push(f, v)
|
|
943
|
+
}
|
|
944
|
+
if (args.includes('--require-sign')) signArgs.push('--require-sign')
|
|
945
|
+
await cmdBuildInstaller(cwd, signArgs, true)
|
|
946
|
+
const un = path.join(outDir, `${slug}-uninstaller${exe}`)
|
|
947
|
+
if (fs.existsSync(un)) fs.copyFileSync(un, path.join(payloadDir, path.basename(un)))
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// 2) UI del instalador/desinstalador
|
|
952
|
+
const ui = await buildInstallerUI(cwd, isUninstaller ? 'uninstaller' : 'installer')
|
|
953
|
+
|
|
954
|
+
// 3) metadatos (installer.json / uninstaller.json) — el kernel los detecta
|
|
955
|
+
const iconRel = bridge.obj?.app?.icon
|
|
956
|
+
const iconAbs = iconRel ? path.resolve(cwd, iconRel) : ''
|
|
957
|
+
const iconOk = Boolean(iconAbs) && fs.existsSync(iconAbs)
|
|
958
|
+
const meta = {
|
|
959
|
+
appId: bridge.obj?.app?.id ?? slug,
|
|
960
|
+
appName: bridge.obj?.app?.name ?? slug,
|
|
961
|
+
version: bridge.obj?.app?.version ?? '0.0.0',
|
|
962
|
+
publisher: bridge.obj?.app?.publisher ?? null,
|
|
963
|
+
icon: iconOk ? 'icon' + path.extname(iconAbs) : null,
|
|
964
|
+
mode: isUninstaller ? 'uninstaller' : mode,
|
|
965
|
+
layout: target.layout ?? mode,
|
|
966
|
+
order: bridge.obj?.order ?? null,
|
|
967
|
+
protect: bridge.obj?.protect ?? null,
|
|
968
|
+
hooks: bridge.obj?.hooks ?? null,
|
|
969
|
+
node: bridge.obj?.node ?? null,
|
|
970
|
+
targets: bridge.obj?.targets ?? null,
|
|
971
|
+
}
|
|
972
|
+
const metaPath = path.join(cwd, '.owear', isUninstaller ? 'uninstaller.json' : 'installer.json')
|
|
973
|
+
fs.mkdirSync(path.dirname(metaPath), { recursive: true })
|
|
974
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + '\n')
|
|
975
|
+
|
|
976
|
+
// 4) ensambla el binario (kernel + payload + footer OWPK1)
|
|
977
|
+
const kind = isUninstaller ? 'uninstaller' : 'installer'
|
|
978
|
+
const out = path.join(outDir, `${slug}-${kind}${exe}`)
|
|
979
|
+
const toolArgs = [
|
|
980
|
+
packTool('owear-installer.mjs'),
|
|
981
|
+
'--kernel', kernel,
|
|
982
|
+
'--out', out,
|
|
983
|
+
'--ui', ui,
|
|
984
|
+
'--bridge', bridge.jsonPath,
|
|
985
|
+
'--meta', metaPath,
|
|
986
|
+
'--name', slug,
|
|
987
|
+
]
|
|
988
|
+
if (payloadDir) toolArgs.push('--payload', payloadDir)
|
|
989
|
+
if (iconOk) toolArgs.push('--icon', iconAbs)
|
|
990
|
+
const r = spawnSync(process.execPath, toolArgs, { stdio: 'inherit' })
|
|
991
|
+
if (r.status !== 0) die(`falló owear-installer (${kind})`)
|
|
992
|
+
log(`${kind}: ${out}`)
|
|
993
|
+
signArtifact(args, out)
|
|
471
994
|
}
|
|
472
995
|
|
|
473
996
|
main().catch((e) => die(e?.stack ?? String(e)))
|