latex-stickies 1.3.6 → 1.3.7

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 CHANGED
@@ -146,6 +146,7 @@ npm test # unit suites
146
146
  npm run vendor # rebuild the bundled libraries
147
147
  npm run icon # rebuild the app icon from assets/icon-master.png
148
148
  npm run demo # re-record assets/demo.gif from the real app (needs ffmpeg)
149
+ npm run verify # install the packed tarball and check what a user gets
149
150
  npm run dist # build a macOS .app and .dmg
150
151
  ```
151
152
 
@@ -158,6 +159,7 @@ node scripts/smoke.js --lifecycle # closing every note behaves per platform
158
159
  node scripts/render-check.js # the first-run note renders correctly
159
160
  node scripts/ghost-check.js # autocomplete suggests, and Tab accepts
160
161
  node scripts/snapshot-check.js # a long note is captured whole
162
+ node scripts/verify-install.js # the install path, end to end (macOS)
161
163
  ```
162
164
 
163
165
  The renderer loads over `file://` under a strict content-security policy, so
@@ -10,8 +10,29 @@
10
10
  const { spawn } = require('child_process');
11
11
  const path = require('path');
12
12
 
13
+ // Name the Electron shell before resolving it, not only at install time.
14
+ //
15
+ // postinstall scripts are increasingly blocked -- npm warns about them by
16
+ // default now, and plenty of people and companies set ignore-scripts -- and a
17
+ // user who installs with them off gets "Electron" in their Dock. Doing it here
18
+ // as well means the name is right by the time macOS reads the bundle, whatever
19
+ // happened during the install. It returns immediately once done.
20
+ //
21
+ // Strictly before require('electron'): branding renames the executable and
22
+ // rewrites the path.txt that require reads, so a path captured first points at
23
+ // a file that no longer exists and the launch fails with ENOENT. That cost a
24
+ // release -- every first launch after upgrading failed, and only the second
25
+ // worked.
26
+ try {
27
+ require('../scripts/brand-electron.js');
28
+ } catch (_) { /* cosmetic: never block a launch over the name */ }
29
+
13
30
  let electron;
14
31
  try {
32
+ // Branding resolved the electron module itself, so it is already in Node's
33
+ // module cache holding the path from before the rename. Drop it and read it
34
+ // again, or the launch spawns a file that no longer exists.
35
+ delete require.cache[require.resolve('electron')];
15
36
  electron = require('electron');
16
37
  } catch (_) {
17
38
  console.error(
@@ -21,16 +42,6 @@ try {
21
42
  process.exit(1);
22
43
  }
23
44
 
24
- // Name the Electron shell before launching it, not only at install time.
25
- // postinstall scripts are increasingly blocked -- npm warns about them by
26
- // default now, and plenty of people and companies set ignore-scripts -- and a
27
- // user who installs with them off gets "Electron" in their Dock. Doing it here
28
- // as well means the name is right by the time macOS reads the bundle, whatever
29
- // happened during the install. It returns immediately once done.
30
- try {
31
- require('../scripts/brand-electron.js');
32
- } catch (_) { /* cosmetic: never block a launch over the name */ }
33
-
34
45
  const appDir = path.join(__dirname, '..');
35
46
  const child = spawn(electron, [appDir, ...process.argv.slice(2)], {
36
47
  detached: true,
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "latex-stickies",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "Sticky notes for your desktop that render LaTeX and Markdown.",
5
5
  "main": "src/main.js",
6
6
  "scripts": {
7
7
  "start": "electron .",
8
8
  "test": "node scripts/test.js",
9
+ "verify": "node scripts/verify-install.js",
9
10
  "vendor": "node scripts/vendor.js",
10
11
  "icon": "node scripts/make-icon.js",
11
12
  "demo": "node scripts/make-demo.js",
@@ -49,6 +49,35 @@ function refreshLaunchServices(appPath) {
49
49
  } catch (_) { /* the name is still right; only the cache is stale */ }
50
50
  }
51
51
 
52
+ /**
53
+ * Puts our icon in the bundle, and says whether it had to.
54
+ *
55
+ * The bundle icon is what macOS shows before the app is running -- in Finder,
56
+ * in Cmd-Tab, and in the Dock at launch -- so app.dock.setIcon() alone is not
57
+ * enough. Compared byte for byte rather than copied blindly, because a copy
58
+ * invalidates the signature and costs a re-sign every launch.
59
+ */
60
+ function copyIcon(appPath) {
61
+ const icon = path.join(__dirname, '..', 'build', 'icon.icns');
62
+ const target = path.join(appPath, 'Contents', 'Resources', 'electron.icns');
63
+ if (!fs.existsSync(icon) || !fs.existsSync(target)) return false;
64
+ if (fs.readFileSync(icon).equals(fs.readFileSync(target))) return false;
65
+ fs.copyFileSync(icon, target);
66
+ return true;
67
+ }
68
+
69
+ /**
70
+ * Re-signs the bundle. Required, not optional: any edit under Contents/
71
+ * invalidates the ad-hoc signature, and macOS refuses to launch a bundle whose
72
+ * signature does not match its contents.
73
+ */
74
+ function resign(appPath) {
75
+ execFileSync('codesign', ['--force', '--deep', '--sign', '-', appPath], {
76
+ stdio: 'ignore',
77
+ });
78
+ execFileSync('codesign', ['--verify', appPath], { stdio: 'ignore' });
79
+ }
80
+
52
81
  function main() {
53
82
  if (process.platform !== 'darwin') return; // only macOS names apps this way
54
83
 
@@ -76,7 +105,16 @@ function main() {
76
105
  if (read('CFBundleName') === NAME
77
106
  && read('CFBundleIdentifier') === APP_ID
78
107
  && fs.existsSync(binary)) {
79
- // Already named, so a wrong name in the Dock can only be a stale cache.
108
+ // Named already, but the icon may still be an old one: someone who
109
+ // installed an earlier version has a bundle branded with that version's
110
+ // artwork, and every upgrade since has taken this path and left it there.
111
+ if (copyIcon(appPath)) {
112
+ resign(appPath);
113
+ refreshLaunchServices(appPath);
114
+ console.log('updated the icon on the Electron shell');
115
+ return;
116
+ }
117
+ // Otherwise a wrong name in the Dock can only be a stale cache.
80
118
  refreshLaunchServices(appPath);
81
119
  // Say so rather than exiting in silence. A user reporting "the Dock still
82
120
  // says Electron" needs to know whether the rename failed or whether they
@@ -118,18 +156,8 @@ function main() {
118
156
  fs.writeFileSync(pathTxt, `Electron.app/Contents/MacOS/${NAME}`);
119
157
  }
120
158
 
121
- // The bundle icon is what shows before the app can set its own.
122
- const icon = path.join(__dirname, '..', 'build', 'icon.icns');
123
- const target = path.join(appPath, 'Contents', 'Resources', 'electron.icns');
124
- if (fs.existsSync(icon) && fs.existsSync(target)) fs.copyFileSync(icon, target);
125
-
126
- // Required: the edits above invalidate the ad-hoc signature, and macOS
127
- // refuses to launch a bundle whose signature does not match its contents.
128
- execFileSync('codesign', ['--force', '--deep', '--sign', '-', appPath], {
129
- stdio: 'ignore',
130
- });
131
- execFileSync('codesign', ['--verify', appPath], { stdio: 'ignore' });
132
-
159
+ copyIcon(appPath);
160
+ resign(appPath);
133
161
  refreshLaunchServices(appPath);
134
162
 
135
163
  console.log(`named the Electron shell "${NAME}"`);
package/src/main.js CHANGED
@@ -473,11 +473,23 @@ let quitting = false;
473
473
  */
474
474
  function prepareToExit() {
475
475
  quitting = true;
476
- if (stopWatchingNotes) {
477
- stopWatchingNotes();
478
- stopWatchingNotes = null;
476
+ // Nothing here may throw. The caller's next statement is process.exit(), and
477
+ // an exception thrown on the way out skips it -- leaving exactly the
478
+ // invisible, unreachable process this whole path exists to prevent. Saving
479
+ // is worth attempting; it is not worth staying alive for.
480
+ try {
481
+ if (stopWatchingNotes) {
482
+ stopWatchingNotes();
483
+ stopWatchingNotes = null;
484
+ }
485
+ } catch (err) {
486
+ console.error('could not stop watching the notes folder', err);
487
+ }
488
+ try {
489
+ store.saveNow();
490
+ } catch (err) {
491
+ console.error('could not save on the way out', err);
479
492
  }
480
- store.saveNow();
481
493
  }
482
494
 
483
495
  function watchNotesFolder() {
@@ -490,12 +502,16 @@ function watchNotesFolder() {
490
502
  }
491
503
 
492
504
  app.whenReady().then(async () => {
493
- // Run from npm there is no .app bundle to carry the icon, so macOS would show
494
- // the generic Electron atom in the Dock. Set it explicitly.
495
- // Only when running from source or from npm. A packaged app already carries
496
- // its icon in the bundle, and build/ is not shipped inside it -- trying
497
- // anyway logged a missing-file error on every launch of the built app.
498
- if (!app.isPackaged && process.platform === 'darwin' && app.dock) {
505
+ // Run from npm there is no .app bundle of our own to carry the icon, so
506
+ // macOS would show the generic Electron atom in the Dock. Set it explicitly.
507
+ //
508
+ // Keyed on the file existing, not on app.isPackaged. Naming the Dock entry
509
+ // means renaming the executable inside the vendored Electron.app, and
510
+ // isPackaged is derived from that executable's name -- so an npm install
511
+ // started reporting itself as packaged, this branch stopped running, and
512
+ // fixing the name cost the icon. A packaged build has no build/ directory
513
+ // inside it, which is the same condition, tested directly.
514
+ if (process.platform === 'darwin' && app.dock && fs.existsSync(ICON)) {
499
515
  try {
500
516
  const image = nativeImage.createFromBuffer(fs.readFileSync(ICON));
501
517
  if (!image.isEmpty()) app.dock.setIcon(image);
package/src/store.js CHANGED
@@ -20,7 +20,16 @@ const { app } = require('electron');
20
20
  const fs = require('fs');
21
21
  const path = require('path');
22
22
 
23
- const DIR = path.join(app.getPath('documents'), 'LaTeX Stickies');
23
+ /**
24
+ * Where the notes live.
25
+ *
26
+ * The override exists for harnesses. On macOS Electron resolves the documents
27
+ * folder through the OS rather than $HOME, so a test that redirects HOME still
28
+ * reads and writes the real notes -- which is how a verification run ended up
29
+ * touching them.
30
+ */
31
+ const DIR = process.env.LATEX_STICKIES_NOTES_DIR
32
+ || path.join(app.getPath('documents'), 'LaTeX Stickies');
24
33
  const INDEX = path.join(DIR, '.stickies.json');
25
34
  /** The old single-file store, migrated from once and then left alone. */
26
35
  const LEGACY = path.join(app.getPath('userData'), 'notes.json');