latex-stickies 1.4.0 → 1.4.1
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/README.md +5 -0
- package/bin/latex-stickies.js +112 -4
- package/package.json +1 -1
- package/scripts/brand-electron.js +12 -2
package/README.md
CHANGED
|
@@ -23,6 +23,11 @@ npm install -g latex-stickies
|
|
|
23
23
|
latex-stickies
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
If a launch fails, the launcher says so and shows the runtime's own output;
|
|
27
|
+
the full log is kept at `~/Library/Logs/latex-stickies/launch.log` on macOS,
|
|
28
|
+
`%LOCALAPPDATA%\latex-stickies\` on Windows, and `$XDG_STATE_HOME` (or
|
|
29
|
+
`~/.local/state/latex-stickies/`) on Linux.
|
|
30
|
+
|
|
26
31
|
Requires Node 22.12 or newer. The first run downloads the Electron runtime
|
|
27
32
|
(~230 MB), so give it a minute; later launches are instant. On a Mac it also
|
|
28
33
|
keeps a copy of the runtime carrying this app's name and icon in
|
package/bin/latex-stickies.js
CHANGED
|
@@ -6,10 +6,26 @@
|
|
|
6
6
|
* We spawn it against this package's own directory, detached, so the notes
|
|
7
7
|
* keep running after the terminal that started them is closed -- a sticky
|
|
8
8
|
* note that dies with your shell is not a sticky note.
|
|
9
|
+
*
|
|
10
|
+
* Detached, but not unwatched. This used to print "LaTeX Stickies is running"
|
|
11
|
+
* the instant it had spawned something, with output going to /dev/null, so a
|
|
12
|
+
* runtime that started and died a moment later -- a missing shared library, a
|
|
13
|
+
* half-downloaded Electron, root without --no-sandbox -- reported success and
|
|
14
|
+
* left nothing behind to read. The launcher now keeps the child for a couple
|
|
15
|
+
* of seconds and says what happened if it does not survive.
|
|
9
16
|
*/
|
|
10
17
|
const { spawn } = require('child_process');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const os = require('os');
|
|
11
20
|
const path = require('path');
|
|
12
21
|
|
|
22
|
+
/** How long a launch has to survive before we call it a launch. */
|
|
23
|
+
const STARTUP_MS = 2000;
|
|
24
|
+
/** How much of the log to show when it does not. */
|
|
25
|
+
const LOG_TAIL_LINES = 20;
|
|
26
|
+
/** Kept from growing without bound: each launch starts a fresh log. */
|
|
27
|
+
const LOG_NAME = 'launch.log';
|
|
28
|
+
|
|
13
29
|
// Brand before resolving anything: this returns the path to a copy of the
|
|
14
30
|
// Electron shell carrying our name and icon, kept outside node_modules so it
|
|
15
31
|
// survives npx cache churn and npm ci. Falls back to the plain shell, which
|
|
@@ -37,16 +53,108 @@ if (!electron) {
|
|
|
37
53
|
}
|
|
38
54
|
}
|
|
39
55
|
|
|
56
|
+
/** Where this platform expects a log to live. */
|
|
57
|
+
function logDirectory() {
|
|
58
|
+
if (process.platform === 'darwin') {
|
|
59
|
+
return path.join(os.homedir(), 'Library', 'Logs', 'latex-stickies');
|
|
60
|
+
}
|
|
61
|
+
if (process.platform === 'win32') {
|
|
62
|
+
const base = process.env.LOCALAPPDATA
|
|
63
|
+
|| path.join(os.homedir(), 'AppData', 'Local');
|
|
64
|
+
return path.join(base, 'latex-stickies');
|
|
65
|
+
}
|
|
66
|
+
const base = process.env.XDG_STATE_HOME
|
|
67
|
+
|| path.join(os.homedir(), '.local', 'state');
|
|
68
|
+
return path.join(base, 'latex-stickies');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Opens the log the app's output goes to.
|
|
73
|
+
*
|
|
74
|
+
* Truncated on every launch rather than appended to: it exists to explain the
|
|
75
|
+
* launch that just failed, and that keeps it bounded without any rotation.
|
|
76
|
+
* If it cannot be opened -- a read-only home, an odd container -- the launch
|
|
77
|
+
* still goes ahead with the output discarded, because a missing log is a far
|
|
78
|
+
* smaller problem than refusing to start.
|
|
79
|
+
*/
|
|
80
|
+
function openLog() {
|
|
81
|
+
try {
|
|
82
|
+
const dir = logDirectory();
|
|
83
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
84
|
+
const file = path.join(dir, LOG_NAME);
|
|
85
|
+
return { file, fd: fs.openSync(file, 'w') };
|
|
86
|
+
} catch (_) {
|
|
87
|
+
return { file: null, fd: 'ignore' };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The tail of the log, for a failure message. */
|
|
92
|
+
function logTail(file) {
|
|
93
|
+
if (!file) return '';
|
|
94
|
+
try {
|
|
95
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
96
|
+
return lines.slice(-LOG_TAIL_LINES).join('\n');
|
|
97
|
+
} catch (_) {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { file: logFile, fd: logFd } = openLog();
|
|
103
|
+
|
|
40
104
|
const appDir = path.join(__dirname, '..');
|
|
41
105
|
const child = spawn(electron, [appDir, ...process.argv.slice(2)], {
|
|
42
106
|
detached: true,
|
|
43
|
-
stdio: 'ignore',
|
|
107
|
+
stdio: ['ignore', logFd, logFd],
|
|
44
108
|
});
|
|
45
109
|
|
|
110
|
+
// The child has its own handle on the log now; this one would otherwise keep
|
|
111
|
+
// the descriptor open for the life of the parent.
|
|
112
|
+
if (typeof logFd === 'number') {
|
|
113
|
+
try { fs.closeSync(logFd); } catch (_) { /* already gone */ }
|
|
114
|
+
}
|
|
115
|
+
|
|
46
116
|
child.on('error', (err) => {
|
|
47
|
-
console.error(
|
|
117
|
+
console.error(`Failed to launch LaTeX Stickies: ${err.message}`);
|
|
48
118
|
process.exit(1);
|
|
49
119
|
});
|
|
50
120
|
|
|
51
|
-
|
|
52
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Electron will not run as root without --no-sandbox, and says so only in the
|
|
123
|
+
* output nobody was reading. Naming it beats adding the flag quietly: turning
|
|
124
|
+
* off the sandbox is a decision for whoever is running as root to make.
|
|
125
|
+
*/
|
|
126
|
+
function rootHint() {
|
|
127
|
+
if (process.getuid && process.getuid() === 0) {
|
|
128
|
+
return '\nRunning as root: Electron refuses to start without a sandbox.'
|
|
129
|
+
+ '\nRun it as an ordinary user, or pass --no-sandbox if you mean it:'
|
|
130
|
+
+ '\n latex-stickies --no-sandbox';
|
|
131
|
+
}
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const onEarlyExit = (code, signal) => {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
const tail = logTail(logFile);
|
|
138
|
+
console.error(
|
|
139
|
+
`LaTeX Stickies exited immediately (code=${code}, signal=${signal}).`
|
|
140
|
+
+ rootHint()
|
|
141
|
+
+ (tail ? `\n\nLast ${LOG_TAIL_LINES} lines of the log:\n${tail}` : '')
|
|
142
|
+
+ (logFile ? `\n\nFull log: ${logFile}` : '\n\nNo log could be written.')
|
|
143
|
+
);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
child.on('exit', onEarlyExit);
|
|
148
|
+
|
|
149
|
+
// Survived the window: let go of it and let this process end, so the terminal
|
|
150
|
+
// is not held open by a note that is running perfectly well.
|
|
151
|
+
const timer = setTimeout(() => {
|
|
152
|
+
child.removeListener('exit', onEarlyExit);
|
|
153
|
+
child.removeAllListeners('error');
|
|
154
|
+
child.unref();
|
|
155
|
+
console.log('LaTeX Stickies is running. Close the notes to quit.');
|
|
156
|
+
}, STARTUP_MS);
|
|
157
|
+
|
|
158
|
+
// Watching must not become waiting: an unreferenced timer still fires, but it
|
|
159
|
+
// no longer holds the event loop open on its own.
|
|
160
|
+
timer.unref();
|
package/package.json
CHANGED
|
@@ -63,6 +63,12 @@ function source() {
|
|
|
63
63
|
const binary = require(path.join(dir, 'index.js'));
|
|
64
64
|
// .../Electron.app/Contents/MacOS/Electron -> .../Electron.app
|
|
65
65
|
const app = path.resolve(path.dirname(binary), '..', '..');
|
|
66
|
+
// Make sure that really is a bundle before anything is built from it. A
|
|
67
|
+
// stub or an odd install resolves to something that is not an .app, and the
|
|
68
|
+
// build then fails partway and takes the existing branded copy with it.
|
|
69
|
+
if (!app.endsWith('.app') || !fs.existsSync(path.join(app, 'Contents', 'MacOS'))) {
|
|
70
|
+
throw new Error(`the Electron runtime at ${app} is not an app bundle`);
|
|
71
|
+
}
|
|
66
72
|
const { version } = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
67
73
|
return { app, version };
|
|
68
74
|
}
|
|
@@ -173,8 +179,12 @@ function ensure() {
|
|
|
173
179
|
return binary;
|
|
174
180
|
} catch (err) {
|
|
175
181
|
// Leave nothing half-built: a bundle with a broken signature will not
|
|
176
|
-
// launch at all, which is far worse than the wrong name.
|
|
177
|
-
|
|
182
|
+
// launch at all, which is far worse than the wrong name. Only what this
|
|
183
|
+
// run was building, though -- a failure part way through is no reason to
|
|
184
|
+
// remove a copy that was working before it.
|
|
185
|
+
if (!isCurrent(version)) {
|
|
186
|
+
fs.rmSync(TARGET, { recursive: true, force: true });
|
|
187
|
+
}
|
|
178
188
|
report(`could not brand the app (${first(err)})`);
|
|
179
189
|
return null;
|
|
180
190
|
}
|