beatrina 0.8.6 → 0.8.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/bin/beatrina.mjs +7 -5
- package/bin/shortcut.mjs +34 -6
- package/build-info.json +1 -1
- package/carmar_V0.8.7.html +1522 -0
- package/failsafe/serve.R +130 -5
- package/failsafe/session-documents.R +104 -0
- package/host/engine-js.mjs +4 -0
- package/host/engine-pool.mjs +8 -2
- package/host/engine-r.mjs +20 -13
- package/host/engine-stdio.mjs +10 -2
- package/host/main.mjs +28 -4
- package/host/planes/latex.mjs +27 -1
- package/host/planes/sessions.mjs +9 -5
- package/host/server.mjs +49 -4
- package/host/session-keep.mjs +70 -0
- package/host/worker-plane.mjs +47 -2
- package/kernel/kernel-version +1 -1
- package/kernel/kernel.R +8 -5
- package/kernel/latex.R +305 -4
- package/kernel/worker.R +85 -9
- package/kernel/workspace-keep.R +188 -0
- package/package.json +2 -2
- package/carmar_V0.8.6.html +0 -1310
package/bin/beatrina.mjs
CHANGED
|
@@ -147,10 +147,12 @@ if (args.fromApp) {
|
|
|
147
147
|
}
|
|
148
148
|
// A page already shows the session: the document went there. No page (the
|
|
149
149
|
// browser was closed): open the session's notebook, which collects it.
|
|
150
|
-
if (delivered !== "page" && args.open && live.
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
if (delivered !== "page" && args.open && live.url) {
|
|
151
|
+
// The page the session SERVES (http://127.0.0.1:<port>/), never the file: macOS `open` drops a
|
|
152
|
+
// file URL's #kernel=…&pair=… fragment and the page then cannot find its kernel. The served
|
|
153
|
+
// origin needs no pairing. BEATRINA_OPENER substitutes the program (test/beatrina-sessions.test.mjs).
|
|
154
|
+
const opener = process.env.BEATRINA_OPENER ? [process.env.BEATRINA_OPENER, [live.url]]
|
|
155
|
+
: process.platform === "darwin" ? ["open", [live.url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", live.url]] : ["xdg-open", [live.url]];
|
|
154
156
|
spawnSync(opener[0], opener[1]);
|
|
155
157
|
}
|
|
156
158
|
if (delivered === "refused") alert(`The running Beatrina session could not open ${file}.`);
|
|
@@ -201,7 +203,7 @@ const forced = process.env.BEATRINA_FORCE_FAILSAFE === "1";
|
|
|
201
203
|
const deadlineS = Number(process.env.BEATRINA_HOST_DEADLINE || 60);
|
|
202
204
|
let started = forced
|
|
203
205
|
? { announced: false, refused: false, reason: "BEATRINA_FORCE_FAILSAFE=1 asked for it", code: null }
|
|
204
|
-
: await runUntilAnnounced({ cmd: process.execPath, args: [path.join(root, "host", "main.mjs"), ...(open ? ["--open"] : [])], env: process.env,
|
|
206
|
+
: await runUntilAnnounced({ cmd: process.execPath, args: [path.join(root, "host", "main.mjs"), ...(open ? ["--open=url"] : [])], env: process.env,
|
|
205
207
|
deadlineMs: (Number.isFinite(deadlineS) && deadlineS > 0 ? deadlineS : 60) * 1000 });
|
|
206
208
|
if (started.announced) followChild(started.child);
|
|
207
209
|
else {
|
package/bin/shortcut.mjs
CHANGED
|
@@ -22,9 +22,20 @@ import os from "node:os";
|
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
|
|
24
24
|
export const BUNDLE_ID = "me.saqr.beatrina";
|
|
25
|
+
// Each type is EXPORTED by Beatrina.app as its own UTI (UTExportedTypeDeclarations) and named in its
|
|
26
|
+
// document types (LSItemContentTypes): Launch Services sets a default handler per UTI, and an app that
|
|
27
|
+
// only lists an extension has no UTI of its own to be the default FOR — the first --default answered
|
|
28
|
+
// -50 (paramErr) for exactly that reason, and .qmd stayed with the app that had declared it (CarmaR).
|
|
29
|
+
// `legacy` is the type CarmaR.app declared for the same extension. A CarmaR bundle anywhere on the
|
|
30
|
+
// disk re-registers itself the moment Finder or Launch Services meets it again, and its declaration
|
|
31
|
+
// then wins the extension back, so a .qmd is typed as CarmaR's type and Beatrina — claiming only its
|
|
32
|
+
// own — is not even a handler of it (2026-09-16: a double-click opened CarmaR out of a dev build
|
|
33
|
+
// folder). Claiming the legacy type too, and being made the default for it, keeps Beatrina the app
|
|
34
|
+
// that opens the file whichever type the system pins on it.
|
|
25
35
|
export const DOC_TYPES = Object.freeze([
|
|
26
|
-
{ ext: "qmd", name: "Quarto document", mime: "text/x-quarto-markdown" },
|
|
27
|
-
{ ext: "Rmd", name: "R Markdown document", mime: "text/x-r-markdown" },
|
|
36
|
+
{ ext: "qmd", name: "Quarto document", mime: "text/x-quarto-markdown", uti: "me.saqr.beatrina.qmd", legacy: "me.saqr.carmar.qmd" },
|
|
37
|
+
{ ext: "Rmd", name: "R Markdown document", mime: "text/x-r-markdown", uti: "me.saqr.beatrina.rmd", legacy: "me.saqr.carmar.rmd" },
|
|
38
|
+
{ ext: "carmd", name: "Beatrina notebook document", mime: "text/markdown", uti: "me.saqr.beatrina.carmd", legacy: "me.saqr.carmar.carmd" },
|
|
28
39
|
]);
|
|
29
40
|
|
|
30
41
|
const q = (s) => `'${String(s).replace(/'/g, "'\\''")}'`; // POSIX single-quote
|
|
@@ -48,9 +59,19 @@ end open
|
|
|
48
59
|
export function documentTypesXml() {
|
|
49
60
|
return `<array>${DOC_TYPES.map((t) => `<dict><key>CFBundleTypeName</key><string>${t.name}</string>`
|
|
50
61
|
+ `<key>CFBundleTypeRole</key><string>Editor</string><key>LSHandlerRank</key><string>Alternate</string>`
|
|
62
|
+
+ `<key>LSItemContentTypes</key><array><string>${t.uti}</string><string>${t.legacy}</string></array>`
|
|
51
63
|
+ `<key>CFBundleTypeExtensions</key><array><string>${t.ext}</string><string>${t.ext.toLowerCase()}</string></array></dict>`).join("")}</array>`;
|
|
52
64
|
}
|
|
53
65
|
|
|
66
|
+
/** The UTIs Beatrina.app exports for those types (UTExportedTypeDeclarations), as plutil-insertable XML. */
|
|
67
|
+
export function exportedTypesXml() {
|
|
68
|
+
return `<array>${DOC_TYPES.map((t) => `<dict><key>UTTypeIdentifier</key><string>${t.uti}</string>`
|
|
69
|
+
+ `<key>UTTypeDescription</key><string>${t.name}</string>`
|
|
70
|
+
+ `<key>UTTypeConformsTo</key><array><string>public.plain-text</string></array>`
|
|
71
|
+
+ `<key>UTTypeTagSpecification</key><dict><key>public.filename-extension</key><array><string>${t.ext}</string><string>${t.ext.toLowerCase()}</string></array>`
|
|
72
|
+
+ `<key>public.mime-type</key><array><string>${t.mime}</string></array></dict></dict>`).join("")}</array>`;
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
/** How to start Beatrina from an icon: this Node, this command, and any baked options. */
|
|
55
76
|
export function launchCommand({ node = process.execPath, cli, port = "", noOpen = false }) {
|
|
56
77
|
return `${q(node)} ${q(cli)}${port ? ` --port ${port}` : ""}${noOpen ? " --no-open" : ""}`;
|
|
@@ -75,16 +96,23 @@ function macShortcut({ cli, dir, port, makeDefault, noOpen }) {
|
|
|
75
96
|
run("plutil", ["-replace", "CFBundleIdentifier", "-string", BUNDLE_ID, plist]);
|
|
76
97
|
run("plutil", ["-replace", "CFBundleName", "-string", "Beatrina", plist]);
|
|
77
98
|
run("plutil", ["-replace", "CFBundleDocumentTypes", "-xml", documentTypesXml(), plist]);
|
|
99
|
+
run("plutil", ["-replace", "UTExportedTypeDeclarations", "-xml", exportedTypesXml(), plist]);
|
|
78
100
|
run("codesign", ["--force", "--sign", "-", app]); // the plist changed after osacompile signed it
|
|
79
101
|
if (fs.existsSync(LSREGISTER)) run(LSREGISTER, ["-f", app]);
|
|
80
102
|
const said = [`Beatrina.app is in ${path.dirname(app)}. Double-click it, or right-click a .qmd and choose Open With ▸ Beatrina.`];
|
|
81
103
|
if (makeDefault) {
|
|
104
|
+
// Beatrina's own UTI for each type, and whatever UTI Launch Services currently resolves the
|
|
105
|
+
// extension to (another app's declaration, or a dynamic one) — both are pointed at Beatrina, so
|
|
106
|
+
// a .qmd opens here whichever identity the system had attached to it. 0 is success.
|
|
82
107
|
const js = `ObjC.import("CoreServices");
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
const types = ${JSON.stringify(DOC_TYPES.map((t) => ({ ext: t.ext, uti: t.uti, legacy: t.legacy })))};
|
|
109
|
+
types.map((t) => { const tagged = $.UTTypeCreatePreferredIdentifierForTag($.kUTTagClassFilenameExtension, $(t.ext), $()).js;
|
|
110
|
+
const utis = [...new Set([t.uti, t.legacy, tagged])];
|
|
111
|
+
return t.ext + "=" + utis.map((u) => $.LSSetDefaultRoleHandlerForContentType($(u), $.kLSRolesAll, $(${JSON.stringify(BUNDLE_ID)}))).join("/"); }).join(" ");`;
|
|
86
112
|
const out = run("osascript", ["-l", "JavaScript", "-e", js]).trim();
|
|
87
|
-
|
|
113
|
+
const failed = /=(?:[^0 /]|-\d)/.test(out);
|
|
114
|
+
said.push(failed ? `Beatrina could not be made the default for every type (${out}; 0 means done).`
|
|
115
|
+
: `Beatrina is now the default app for ${DOC_TYPES.map((t) => `.${t.ext}`).join(", ")}.`);
|
|
88
116
|
}
|
|
89
117
|
return { app, said };
|
|
90
118
|
}
|
package/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.8.
|
|
1
|
+
{"version":"0.8.7","commit":"b7aa1255558293d1e2b47a4799a344fde0846b2e","dirty":false}
|