@glassnote/client 2.4.0 → 2.4.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/INSTALACION.md +0 -0
- package/README.md +0 -0
- package/autoStart.js +0 -0
- package/autoupdate.js +0 -0
- package/autoupdate_renderer.js +14 -21
- package/bin/glassnote.js +172 -0
- package/images/demo01.png +0 -0
- package/images/dmg-background.png +0 -0
- package/images/dmg-background.svg +0 -0
- package/images/icon-16.png +0 -0
- package/images/icon-24.png +0 -0
- package/images/icon-512.png +0 -0
- package/images/icon.ico +0 -0
- package/images/icon.png +0 -0
- package/images/splash.svg +0 -0
- package/localserver.js +0 -0
- package/logUtilities.js +0 -0
- package/main.js +21 -46
- package/npmMode.js +0 -0
- package/npmUpdate.js +40 -8
- package/package.json +2 -1
- package/preload.js +0 -0
- package/rendererPaths.js +62 -0
- package/userData.js +0 -0
package/INSTALACION.md
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/autoStart.js
CHANGED
|
File without changes
|
package/autoupdate.js
CHANGED
|
File without changes
|
package/autoupdate_renderer.js
CHANGED
|
@@ -5,6 +5,7 @@ const https = require('https');
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const { exec } = require('child_process');
|
|
7
7
|
const userData = require('./userData');
|
|
8
|
+
const { baselineRendererDir, rendererVersionFrom } = require('./rendererPaths');
|
|
8
9
|
|
|
9
10
|
// El renderer se baja siempre del bucket de PRODUCCIÓN. Antes esto lo decidía un campo
|
|
10
11
|
// `stage` en package.json que elegía entre este bucket y `glassnotedev`: ese ambiente ya
|
|
@@ -23,29 +24,21 @@ class AutoUpdateRenderer {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
+
* La versión del renderer que está EN USO, que es contra la que hay que comparar el
|
|
28
|
+
* canal. En uso es la que elige loadRendererHtml: la más alta entre lo bajado a
|
|
29
|
+
* userData y el baseline que viaja con el paquete.
|
|
30
|
+
*
|
|
31
|
+
* Antes esto miraba solo userData y, si no había, `<paquete>/renderer/package.json`
|
|
32
|
+
* —una carpeta que dejó de existir cuando el renderer pasó a ser dependencia npm—.
|
|
33
|
+
* Devolvía 0.0.0, así que en cada instalación nueva cualquier versión del canal le
|
|
34
|
+
* ganaba: bajaba el zip de S3 aunque fuera MÁS VIEJO que el baseline, y después el
|
|
35
|
+
* loader se quedaba con el baseline igual: 1.1 MB tirados y una versión "actual"
|
|
36
|
+
* que no era la que se estaba usando.
|
|
27
37
|
*/
|
|
28
38
|
getCurrentRendererVersion() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (fs.existsSync(userDataRendererPath)) {
|
|
33
|
-
const packageJson = JSON.parse(fs.readFileSync(userDataRendererPath, 'utf8'));
|
|
34
|
-
return packageJson.version;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Si no existe en userData, verificar en la aplicación (versión por defecto)
|
|
38
|
-
const appRendererPath = path.join(__dirname, 'renderer', 'package.json');
|
|
39
|
-
if (fs.existsSync(appRendererPath)) {
|
|
40
|
-
const packageJson = JSON.parse(fs.readFileSync(appRendererPath, 'utf8'));
|
|
41
|
-
return packageJson.version;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return '0.0.0'; // Versión por defecto si no existe
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('Error reading renderer version:', error);
|
|
47
|
-
return '0.0.0';
|
|
48
|
-
}
|
|
39
|
+
const enUserData = rendererVersionFrom(path.join(userData.getUserDataDir(), 'renderer'));
|
|
40
|
+
const enBaseline = rendererVersionFrom(baselineRendererDir());
|
|
41
|
+
return this.compareVersions(enUserData, enBaseline) >= 0 ? enUserData : enBaseline;
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
/**
|
package/bin/glassnote.js
CHANGED
|
@@ -29,9 +29,58 @@ const MARK_B = '# <<< glassnote <<<';
|
|
|
29
29
|
function npmEnv() {
|
|
30
30
|
return Object.assign({}, process.env, {
|
|
31
31
|
PATH: `${mode.BIN_DIR}${path.delimiter}${process.env.PATH || ''}`,
|
|
32
|
+
// Cache propia, bajo ~/.glassnote. Un ~/.npm con archivos de root —herencia de
|
|
33
|
+
// cualquier `sudo npm` viejo— hace que npm muera con EACCES antes de bajar nada,
|
|
34
|
+
// y el usuario tiene que andar con chown. Se pisa a propósito el npm_config_cache
|
|
35
|
+
// heredado: cuando esto corre bajo npx, npm nos pasa el suyo en el entorno, que es
|
|
36
|
+
// justo el que puede estar roto. Para forzar otra: GLASSNOTE_NPM_CACHE.
|
|
37
|
+
npm_config_cache:
|
|
38
|
+
process.env.GLASSNOTE_NPM_CACHE || path.join(mode.HOME_DIR, 'npm-cache'),
|
|
32
39
|
});
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
// userData tira de `uuid`, y este CLI corre también donde el árbol de dependencias no
|
|
43
|
+
// está (un tarball recién desempacado, un install a medias). Por eso se carga tarde y
|
|
44
|
+
// tolerando el fallo: si no se puede dejar constancia, el peor caso es que la app
|
|
45
|
+
// reintente en el chequeo siguiente. Romper el CLI —que es quien instala— sería peor.
|
|
46
|
+
function conUserData(hacer) {
|
|
47
|
+
try {
|
|
48
|
+
return hacer(require('../userData'));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// La app mira esto antes de reintentar: sin memoria del fallo, un npm que falla siempre
|
|
55
|
+
// deja al cliente cerrándose y abriéndose cada veinte segundos (falla, se relanza,
|
|
56
|
+
// chequea a los 5 segundos, vuelve a fallar).
|
|
57
|
+
function recordarFalloDeUpdate(version, motivo) {
|
|
58
|
+
conUserData((userData) => userData.set('failedUpdate', { version, at: Date.now(), motivo }));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function olvidarFalloDeUpdate() {
|
|
62
|
+
conUserData((userData) => {
|
|
63
|
+
if (userData.get('failedUpdate')) userData.remove('failedUpdate');
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ¿Hay un cliente vivo? Levanta su servidor local en 48984 (localserver.js), así que su
|
|
68
|
+
// /uuid es la señal más barata. Node no tiene HTTP síncrono y el CLI sí lo es, así que la
|
|
69
|
+
// consulta va en un proceso aparte.
|
|
70
|
+
function clienteEnMarcha() {
|
|
71
|
+
const sonda =
|
|
72
|
+
"const http=require('http');" +
|
|
73
|
+
"const r=http.get('http://127.0.0.1:48984/uuid',(res)=>{res.resume();process.exit(res.statusCode===200?0:1)});" +
|
|
74
|
+
"r.on('error',()=>process.exit(1));" +
|
|
75
|
+
"r.setTimeout(700,()=>{r.destroy();process.exit(1)});";
|
|
76
|
+
const res = spawnSync(process.execPath, ['-e', sonda], {
|
|
77
|
+
env: Object.assign({}, process.env, { ELECTRON_RUN_AS_NODE: '1' }),
|
|
78
|
+
stdio: 'ignore',
|
|
79
|
+
timeout: 4000,
|
|
80
|
+
});
|
|
81
|
+
return res.status === 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
35
84
|
// Sueño síncrono sin dependencias: el ayudante de actualización tiene que esperar a que
|
|
36
85
|
// la app termine de cerrarse antes de que npm le reemplace los archivos.
|
|
37
86
|
function sleepSync(ms) {
|
|
@@ -242,6 +291,108 @@ function toSpec(arg) {
|
|
|
242
291
|
return looksLikeSpec ? arg : `${mode.PKG_NAME}@${arg}`;
|
|
243
292
|
}
|
|
244
293
|
|
|
294
|
+
const MAC_APP_NAME = 'Glassnote.app';
|
|
295
|
+
|
|
296
|
+
// Instalado por npm queda un comando, no una app: cerrada desde la bandeja no hay forma
|
|
297
|
+
// de volver a abrirla desde el Finder, Spotlight o Launchpad. Esto crea un .app minimo
|
|
298
|
+
// —un envoltorio que llama al CLI instalado— para tener de donde relanzarla.
|
|
299
|
+
function macAppDirs() {
|
|
300
|
+
return [
|
|
301
|
+
path.join('/Applications', MAC_APP_NAME),
|
|
302
|
+
path.join(os.homedir(), 'Applications', MAC_APP_NAME),
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function instalarAppMac() {
|
|
307
|
+
if (process.platform !== 'darwin') return null;
|
|
308
|
+
|
|
309
|
+
// /Applications pide ser admin; si no se puede escribir, la de usuario sirve igual y
|
|
310
|
+
// Spotlight la indexa lo mismo. Nada de sudo: la instalacion entera va sin root.
|
|
311
|
+
const [global, local] = macAppDirs();
|
|
312
|
+
let appDir = local;
|
|
313
|
+
try {
|
|
314
|
+
fs.accessSync('/Applications', fs.constants.W_OK);
|
|
315
|
+
appDir = global;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
/* sin permiso: se usa ~/Applications */
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const contents = path.join(appDir, 'Contents');
|
|
321
|
+
const cli = path.join(mode.BIN_DIR, mode.BIN_NAME);
|
|
322
|
+
try {
|
|
323
|
+
fs.mkdirSync(path.join(contents, 'MacOS'), { recursive: true });
|
|
324
|
+
fs.mkdirSync(path.join(contents, 'Resources'), { recursive: true });
|
|
325
|
+
|
|
326
|
+
// El ejecutable del bundle solo delega en el CLI, que ya sabe encontrar electron y
|
|
327
|
+
// arrancar desprendido. Asi el .app no se queda viejo cuando el cliente se actualiza.
|
|
328
|
+
const exe = path.join(contents, 'MacOS', 'Glassnote');
|
|
329
|
+
fs.writeFileSync(
|
|
330
|
+
exe,
|
|
331
|
+
[
|
|
332
|
+
'#!/bin/sh',
|
|
333
|
+
"# Generado por 'glassnote install': se reescribe en cada instalacion.",
|
|
334
|
+
`exec "${cli}" start`,
|
|
335
|
+
'',
|
|
336
|
+
].join('\n')
|
|
337
|
+
);
|
|
338
|
+
fs.chmodSync(exe, 0o755);
|
|
339
|
+
|
|
340
|
+
// El icono del bundle tiene que ser .icns; sips viene con macOS. Si falla, el .app
|
|
341
|
+
// queda con el icono generico: molesta, pero no impide relanzar.
|
|
342
|
+
const png = path.join(mode.INSTALLED_PACKAGE_DIR, 'images', 'icon-512.png');
|
|
343
|
+
if (fs.existsSync(png)) {
|
|
344
|
+
spawnSync(
|
|
345
|
+
'sips',
|
|
346
|
+
['-s', 'format', 'icns', png, '--out', path.join(contents, 'Resources', 'glassnote.icns')],
|
|
347
|
+
{ stdio: 'ignore' }
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// LSUIElement, igual que la app: el envoltorio tampoco tiene que dejar icono en el Dock.
|
|
352
|
+
const version = installedVersion() || '0.0.0';
|
|
353
|
+
fs.writeFileSync(
|
|
354
|
+
path.join(contents, 'Info.plist'),
|
|
355
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
356
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
357
|
+
<plist version="1.0">
|
|
358
|
+
<dict>
|
|
359
|
+
<key>CFBundleName</key><string>Glassnote</string>
|
|
360
|
+
<key>CFBundleDisplayName</key><string>Glassnote</string>
|
|
361
|
+
<key>CFBundleIdentifier</key><string>com.glassnote.launcher</string>
|
|
362
|
+
<key>CFBundleExecutable</key><string>Glassnote</string>
|
|
363
|
+
<key>CFBundleIconFile</key><string>glassnote</string>
|
|
364
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
365
|
+
<key>CFBundleShortVersionString</key><string>${version}</string>
|
|
366
|
+
<key>CFBundleVersion</key><string>${version}</string>
|
|
367
|
+
<key>LSUIElement</key><true/>
|
|
368
|
+
</dict>
|
|
369
|
+
</plist>
|
|
370
|
+
`
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// El Finder cachea el bundle por fecha: sin esto puede seguir mostrando el icono viejo.
|
|
374
|
+
spawnSync('touch', [appDir], { stdio: 'ignore' });
|
|
375
|
+
return appDir;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
// Que no se pueda crear la app no rompe la instalacion: el comando ya funciona.
|
|
378
|
+
log(` (no pude crear ${MAC_APP_NAME}: ${error.message})`);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function borrarAppMac() {
|
|
384
|
+
if (process.platform !== 'darwin') return;
|
|
385
|
+
for (const dir of macAppDirs()) {
|
|
386
|
+
if (!fs.existsSync(dir)) continue;
|
|
387
|
+
try {
|
|
388
|
+
step(`borrando ${dir}`);
|
|
389
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
390
|
+
} catch (error) {
|
|
391
|
+
log(` (no pude borrar ${dir}: ${error.message})`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
245
396
|
function cmdInstall(args) {
|
|
246
397
|
const version = args.find((a) => !a.startsWith('-')) || 'latest';
|
|
247
398
|
const noPath = args.includes('--no-path');
|
|
@@ -281,6 +432,9 @@ function cmdInstall(args) {
|
|
|
281
432
|
}
|
|
282
433
|
}
|
|
283
434
|
|
|
435
|
+
const appMac = instalarAppMac();
|
|
436
|
+
if (appMac) step(`app creada en ${appMac} (para relanzarlo desde el Finder)`);
|
|
437
|
+
|
|
284
438
|
let pathInfo = { touched: [], line: '' };
|
|
285
439
|
if (!noPath) {
|
|
286
440
|
pathInfo = IS_WIN ? addToPathWindows() : addToPathUnix();
|
|
@@ -346,6 +500,7 @@ function cmdUpdate(args) {
|
|
|
346
500
|
env: npmEnv(),
|
|
347
501
|
});
|
|
348
502
|
if (res.status !== 0) {
|
|
503
|
+
recordarFalloDeUpdate(version, `npm salió con ${res.status}`);
|
|
349
504
|
if (relaunch) {
|
|
350
505
|
// Falló la actualización: al menos el cliente vuelve. Quedarse sin app por
|
|
351
506
|
// un npm que no pudo con la red es peor que seguir en la versión vieja.
|
|
@@ -355,6 +510,11 @@ function cmdUpdate(args) {
|
|
|
355
510
|
fail('npm no pudo actualizar el paquete.');
|
|
356
511
|
}
|
|
357
512
|
|
|
513
|
+
olvidarFalloDeUpdate();
|
|
514
|
+
// Se rehace en cada update: quien ya tenia el cliente instalado de antes no paso
|
|
515
|
+
// por el install, y el .app tiene que aparecerle igual (y con la version al dia).
|
|
516
|
+
const appMac = instalarAppMac();
|
|
517
|
+
if (appMac) step(`app actualizada en ${appMac}`);
|
|
358
518
|
log(`Actualizado a v${installedVersion()}.`);
|
|
359
519
|
if (relaunch) {
|
|
360
520
|
launch({ detach: true });
|
|
@@ -366,6 +526,10 @@ function cmdUpdate(args) {
|
|
|
366
526
|
|
|
367
527
|
function cmdUninstall() {
|
|
368
528
|
log('glassnote — desinstalando');
|
|
529
|
+
// Se comprueba ANTES de borrar: después los archivos ya no están, pero el proceso que
|
|
530
|
+
// los tenía abiertos sigue vivo y con su ícono en la bandeja. Decir "Desinstalado" con
|
|
531
|
+
// la app todavía en pantalla es lo que confunde.
|
|
532
|
+
const seguiaAbierto = clienteEnMarcha();
|
|
369
533
|
step('quitando el autoarranque');
|
|
370
534
|
removeAutoStart();
|
|
371
535
|
|
|
@@ -378,11 +542,19 @@ function cmdUninstall() {
|
|
|
378
542
|
step('el PATH de usuario ya no tenía la entrada');
|
|
379
543
|
}
|
|
380
544
|
|
|
545
|
+
borrarAppMac();
|
|
546
|
+
|
|
381
547
|
if (fs.existsSync(mode.HOME_DIR)) {
|
|
382
548
|
step(`borrando ${mode.HOME_DIR}`);
|
|
383
549
|
fs.rmSync(mode.HOME_DIR, { recursive: true, force: true });
|
|
384
550
|
}
|
|
385
551
|
log('');
|
|
552
|
+
if (seguiaAbierto) {
|
|
553
|
+
log('OJO: el cliente sigue abierto. Sus archivos ya no están, pero el proceso vive');
|
|
554
|
+
log('hasta que lo cierres desde el ícono de la bandeja (Salir). Después no vuelve a');
|
|
555
|
+
log('abrirse: el autoarranque ya no está.');
|
|
556
|
+
log('');
|
|
557
|
+
}
|
|
386
558
|
log('Desinstalado. Tus datos y notas siguen donde estaban:');
|
|
387
559
|
log(` ${userDataDir()}`);
|
|
388
560
|
log('Borralos a mano si también querés perderlos.');
|
package/images/demo01.png
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/images/icon-16.png
CHANGED
|
File without changes
|
package/images/icon-24.png
CHANGED
|
File without changes
|
package/images/icon-512.png
CHANGED
|
File without changes
|
package/images/icon.ico
CHANGED
|
File without changes
|
package/images/icon.png
CHANGED
|
File without changes
|
package/images/splash.svg
CHANGED
|
File without changes
|
package/localserver.js
CHANGED
|
File without changes
|
package/logUtilities.js
CHANGED
|
File without changes
|
package/main.js
CHANGED
|
@@ -4,6 +4,7 @@ const {
|
|
|
4
4
|
Notification,
|
|
5
5
|
Tray,
|
|
6
6
|
Menu,
|
|
7
|
+
nativeImage,
|
|
7
8
|
screen,
|
|
8
9
|
} = require('electron');
|
|
9
10
|
const packageJson = require('./package.json');
|
|
@@ -17,6 +18,7 @@ const AutoUpdate = require('./autoupdate');
|
|
|
17
18
|
const NpmUpdate = require('./npmUpdate');
|
|
18
19
|
const npmMode = require('./npmMode');
|
|
19
20
|
const AutoUpdateRenderer = require('./autoupdate_renderer');
|
|
21
|
+
const { baselineRendererDir, rendererVersionFrom: getRendererVersionFromPath } = require('./rendererPaths');
|
|
20
22
|
const { createTailUtility } = require('./logUtilities');
|
|
21
23
|
|
|
22
24
|
const logRenderer = packageJson.logRenderer !== undefined ? packageJson.logRenderer : true;
|
|
@@ -120,6 +122,23 @@ cleanupOldLogs();
|
|
|
120
122
|
createTailUtility(logsDir);
|
|
121
123
|
|
|
122
124
|
app.whenReady().then(() => {
|
|
125
|
+
// macOS: esto es una app de bandeja, se maneja desde la barra de menu. Instalada por
|
|
126
|
+
// npm no hay .app propio, asi que el Dock mostraba el icono por defecto de Electron y
|
|
127
|
+
// una entrada que no sirve para nada. Se saca del Dock, pero antes se le pone el icono
|
|
128
|
+
// de glassnote: si algo lo vuelve a mostrar (un dialogo modal lo hace), que al menos
|
|
129
|
+
// no aparezca como "Electron".
|
|
130
|
+
if (process.platform === 'darwin' && app.dock) {
|
|
131
|
+
try {
|
|
132
|
+
const dockIcon = nativeImage.createFromPath(
|
|
133
|
+
path.join(__dirname, 'images', 'icon-512.png')
|
|
134
|
+
);
|
|
135
|
+
if (!dockIcon.isEmpty()) app.dock.setIcon(dockIcon);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn('[dock] no se pudo poner el icono:', error.message);
|
|
138
|
+
}
|
|
139
|
+
app.dock.hide();
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
// Configure auto-start - this will handle shortcut creation
|
|
124
143
|
//
|
|
125
144
|
// Corriendo por `npx` no se registra nada: la app vive en la caché de npx, que se
|
|
@@ -556,20 +575,7 @@ process.on('unhandledRejection', (reason, promise) => {
|
|
|
556
575
|
*
|
|
557
576
|
* Gana la version mas alta, sin importar de donde venga; empate, gana userData.
|
|
558
577
|
*/
|
|
559
|
-
//
|
|
560
|
-
// que npm resuelve sola. Pero si hay un clon del renderer al lado —el flujo de quien lo
|
|
561
|
-
// desarrolla, con `npm run watch-renderer`— ese gana: si no, editarlo no se veria nunca,
|
|
562
|
-
// tapado por la version publicada.
|
|
563
|
-
function baselineRendererDir() {
|
|
564
|
-
const clonLocal = path.join(__dirname, 'glassnote-renderer', 'dist');
|
|
565
|
-
if (fs.existsSync(clonLocal)) return clonLocal;
|
|
566
|
-
try {
|
|
567
|
-
return path.join(path.dirname(require.resolve('@glassnote/renderer/package.json')), 'dist');
|
|
568
|
-
} catch (error) {
|
|
569
|
-
// Sin dependencia ni clon solo queda userData; si tampoco esta, mas abajo se avisa.
|
|
570
|
-
return clonLocal;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
578
|
+
// baselineRendererDir vive en rendererPaths.js: lo comparte con el updater del renderer.
|
|
573
579
|
|
|
574
580
|
function loadRendererHtml(window, uuid) {
|
|
575
581
|
// Definir las dos fuentes a considerar con archivos HTML alternativos
|
|
@@ -695,38 +701,7 @@ function compareVersions(version1, version2) {
|
|
|
695
701
|
return 0; // Las versiones son iguales
|
|
696
702
|
}
|
|
697
703
|
|
|
698
|
-
|
|
699
|
-
* Obtiene la versión del renderer desde el package.json en el directorio especificado
|
|
700
|
-
* Si el directorio es un dist/ sin package.json, se mira el del paquete que lo contiene.
|
|
701
|
-
*/
|
|
702
|
-
function getRendererVersionFromPath(rendererPath) {
|
|
703
|
-
try {
|
|
704
|
-
// Primero intentar en el directorio especificado
|
|
705
|
-
const packageJsonPath = path.join(rendererPath, 'package.json');
|
|
706
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
707
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
708
|
-
return packageJson.version || '0.0.0';
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// Un dist/ sin package.json propio: la version la tiene el paquete de arriba.
|
|
712
|
-
if (path.basename(rendererPath) === 'dist') {
|
|
713
|
-
const parentDir = path.join(rendererPath, '..'); // Subir un nivel
|
|
714
|
-
const parentPackageJsonPath = path.join(parentDir, 'package.json');
|
|
715
|
-
if (fs.existsSync(parentPackageJsonPath)) {
|
|
716
|
-
const packageJson = JSON.parse(fs.readFileSync(parentPackageJsonPath, 'utf8'));
|
|
717
|
-
return packageJson.version || '0.0.0';
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
} catch (error) {
|
|
721
|
-
console.error(
|
|
722
|
-
'Error reading renderer version from',
|
|
723
|
-
rendererPath,
|
|
724
|
-
':',
|
|
725
|
-
error
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
return '0.0.0'; // Versión por defecto si no existe o hay error
|
|
729
|
-
}
|
|
704
|
+
// getRendererVersionFromPath = rendererVersionFrom de rendererPaths.js (ver el require de arriba).
|
|
730
705
|
|
|
731
706
|
/**
|
|
732
707
|
* Recarga el main window después de una actualización del renderer
|
package/npmMode.js
CHANGED
|
File without changes
|
package/npmUpdate.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// desprendido, la app se cierra, el ayudante actualiza y la vuelve a abrir. Igual en las
|
|
9
9
|
// tres plataformas, así no hay una rama por sistema operativo que solo se pruebe en uno.
|
|
10
10
|
const https = require('https');
|
|
11
|
+
const fs = require('fs');
|
|
11
12
|
const path = require('path');
|
|
12
13
|
const { spawn } = require('child_process');
|
|
13
14
|
|
|
@@ -90,18 +91,38 @@ class NpmUpdate {
|
|
|
90
91
|
const meta = await this.getJson(`${REGISTRY}/${npmMode.PKG_NAME}/${target}`);
|
|
91
92
|
if (!meta || !meta.version) return;
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
const objetivo = meta.version;
|
|
95
|
+
const hayQueActualizar = isLatest
|
|
96
|
+
? compareVersions(this.currentVersion, objetivo) < 0
|
|
97
|
+
: this.currentVersion !== objetivo;
|
|
98
|
+
if (!hayQueActualizar) return;
|
|
99
|
+
if (this.fallidoHacePoco(objetivo)) return;
|
|
100
|
+
this.applyUpdate(objetivo);
|
|
100
101
|
} catch (error) {
|
|
101
102
|
console.error('[update] error comprobando actualizaciones:', error.message);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
// Una actualización que falló NO se reintenta enseguida. cmdUpdate relanza la app a
|
|
107
|
+
// propósito cuando npm falla —quedarse sin cliente es peor que quedarse en la versión
|
|
108
|
+
// vieja—, pero el chequeo vuelve a los 5 segundos de arrancar. Con un fallo que
|
|
109
|
+
// persiste (sin red, el registry caído, una dependencia que no resuelve) eso es la app
|
|
110
|
+
// cerrándose y abriéndose cada veinte segundos, para siempre y sin decir por qué. Se
|
|
111
|
+
// recuerda la versión que falló y se espera al chequeo normal.
|
|
112
|
+
fallidoHacePoco(version) {
|
|
113
|
+
try {
|
|
114
|
+
const fallo = userData.get('failedUpdate');
|
|
115
|
+
if (!fallo || fallo.version !== version) return false;
|
|
116
|
+
const desde = Date.now() - (fallo.at || 0);
|
|
117
|
+
if (desde >= CHECK_INTERVAL_MS) return false;
|
|
118
|
+
const minutos = Math.max(1, Math.round((CHECK_INTERVAL_MS - desde) / 60000));
|
|
119
|
+
console.log(`[update] ${version} ya falló; no se reintenta hasta dentro de ~${minutos} min`);
|
|
120
|
+
return true;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
105
126
|
applyUpdate(version) {
|
|
106
127
|
if (this.updating) return;
|
|
107
128
|
this.updating = true;
|
|
@@ -117,11 +138,22 @@ class NpmUpdate {
|
|
|
117
138
|
PATH: `${npmMode.BIN_DIR}${path.delimiter}${process.env.PATH || ''}`,
|
|
118
139
|
});
|
|
119
140
|
|
|
141
|
+
// El ayudante corre desprendido y su salida no la veía nadie: cuando npm fallaba,
|
|
142
|
+
// el único síntoma era la app reiniciándose sola. Va a un archivo al lado de los
|
|
143
|
+
// datos de usuario, que es donde alguien lo va a buscar.
|
|
144
|
+
let salida = 'ignore';
|
|
145
|
+
try {
|
|
146
|
+
fs.mkdirSync(userData.getUserDataDir(), { recursive: true });
|
|
147
|
+
salida = fs.openSync(path.join(userData.getUserDataDir(), 'update.log'), 'a');
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[update] no se pudo abrir update.log:', error.message);
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
try {
|
|
121
153
|
const child = spawn(
|
|
122
154
|
process.execPath,
|
|
123
155
|
[cli, 'update', version, '--relaunch', '--wait', '4000'],
|
|
124
|
-
{ detached: true, stdio: 'ignore', env }
|
|
156
|
+
{ detached: true, stdio: ['ignore', salida, salida], env }
|
|
125
157
|
);
|
|
126
158
|
child.unref();
|
|
127
159
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassnote/client",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"description": "GlassNote — cliente de escritorio de notas superpuestas. Se instala con npx, sin instaladores nativos ni firmas.",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"autoStart.js",
|
|
30
30
|
"autoupdate.js",
|
|
31
31
|
"autoupdate_renderer.js",
|
|
32
|
+
"rendererPaths.js",
|
|
32
33
|
"npmMode.js",
|
|
33
34
|
"npmUpdate.js",
|
|
34
35
|
"localserver.js",
|
package/preload.js
CHANGED
|
File without changes
|
package/rendererPaths.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// De dónde sale el renderer y qué versión tiene. En un solo sitio a propósito.
|
|
2
|
+
//
|
|
3
|
+
// Esto vivía duplicado en main.js y en autoupdate_renderer.js, y las dos copias se
|
|
4
|
+
// fueron separando: la del updater seguía mirando `<paquete>/renderer/`, una carpeta que
|
|
5
|
+
// dejó de existir cuando el renderer pasó a ser la dependencia @glassnote/renderer. Como
|
|
6
|
+
// no la encontraba devolvía 0.0.0, cualquier versión del canal le ganaba, y cada
|
|
7
|
+
// instalación nueva bajaba el zip de S3 —incluso más viejo que el baseline— para después
|
|
8
|
+
// no usarlo. Un bug silencioso: se ve como 1.1 MB de tráfico que nadie pidió.
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// El baseline es la dependencia @glassnote/renderer, que npm resuelve sola. Pero si hay
|
|
15
|
+
// un clon del renderer al lado —el flujo de quien lo desarrolla, con `npm run
|
|
16
|
+
// watch-renderer`— ese gana: si no, editarlo no se vería nunca, tapado por la versión
|
|
17
|
+
// publicada.
|
|
18
|
+
function baselineRendererDir() {
|
|
19
|
+
const clonLocal = path.join(__dirname, 'glassnote-renderer', 'dist');
|
|
20
|
+
if (fs.existsSync(clonLocal)) return clonLocal;
|
|
21
|
+
try {
|
|
22
|
+
return path.join(path.dirname(require.resolve('@glassnote/renderer/package.json')), 'dist');
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// Sin dependencia ni clon solo queda userData; quien llama avisa si tampoco está.
|
|
25
|
+
return clonLocal;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// La versión de un directorio de renderer. El dist/ publicado trae su propio
|
|
30
|
+
// package.json; si algún día no lo trajera, la versión la tiene el paquete de arriba.
|
|
31
|
+
function rendererVersionFrom(rendererPath) {
|
|
32
|
+
try {
|
|
33
|
+
const propio = path.join(rendererPath, 'package.json');
|
|
34
|
+
if (fs.existsSync(propio)) {
|
|
35
|
+
return JSON.parse(fs.readFileSync(propio, 'utf8')).version || '0.0.0';
|
|
36
|
+
}
|
|
37
|
+
if (path.basename(rendererPath) === 'dist') {
|
|
38
|
+
const delPadre = path.join(rendererPath, '..', 'package.json');
|
|
39
|
+
if (fs.existsSync(delPadre)) {
|
|
40
|
+
return JSON.parse(fs.readFileSync(delPadre, 'utf8')).version || '0.0.0';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Error reading renderer version from', rendererPath, ':', error);
|
|
45
|
+
}
|
|
46
|
+
return '0.0.0'; // No hay renderer ahí, o no se pudo leer.
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Compara dos versiones semánticas: 1 si a es mayor, -1 si b lo es, 0 si empatan.
|
|
50
|
+
function compareRendererVersions(a, b) {
|
|
51
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
|
|
52
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
|
|
53
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
54
|
+
const va = i < pa.length ? pa[i] : 0;
|
|
55
|
+
const vb = i < pb.length ? pb[i] : 0;
|
|
56
|
+
if (va > vb) return 1;
|
|
57
|
+
if (va < vb) return -1;
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { baselineRendererDir, rendererVersionFrom, compareRendererVersions };
|
package/userData.js
CHANGED
|
File without changes
|