uncontainerizable 0.0.3 → 0.1.0
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/dist/adapters/index.d.mts +2 -1
- package/dist/adapters/index.mjs +2 -1
- package/dist/adapters-BYLFpYWC.mjs +157 -0
- package/dist/adapters-BYLFpYWC.mjs.map +1 -0
- package/dist/index-B1y4yb1N.d.mts +132 -0
- package/dist/index-B1y4yb1N.d.mts.map +1 -0
- package/dist/index.d.mts +25 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +65 -2
- package/dist/index.mjs.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { a as appkit, i as chromium, n as firefox, r as crashReporter, t as defaultAdapters } from "../index-B1y4yb1N.mjs";
|
|
2
|
+
export { appkit, chromium, crashReporter, defaultAdapters, firefox };
|
package/dist/adapters/index.mjs
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { a as appkit, i as chromium, n as firefox, r as crashReporter, t as defaultAdapters } from "../adapters-BYLFpYWC.mjs";
|
|
2
|
+
export { appkit, chromium, crashReporter, defaultAdapters, firefox };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { readdir, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
//#region src/adapters/appkit.ts
|
|
5
|
+
/**
|
|
6
|
+
* Deletes the AppKit "Saved Application State" directory for the managed
|
|
7
|
+
* app after a terminal-stage destroy.
|
|
8
|
+
*
|
|
9
|
+
* macOS's AppKit persists a per-app snapshot of open windows and the
|
|
10
|
+
* "should reopen windows on next launch" hint at
|
|
11
|
+
* `~/Library/Saved Application State/<bundleId>.savedState/`. When an
|
|
12
|
+
* app is force-killed (our SIGTERM/SIGKILL ladder), AppKit interprets
|
|
13
|
+
* the missing clean-shutdown marker as a crash and shows the
|
|
14
|
+
* "Reopen windows?" dialog next launch. Wiping the directory suppresses
|
|
15
|
+
* the dialog.
|
|
16
|
+
*
|
|
17
|
+
* Only matches on darwin probes that carry a resolved `bundleId`.
|
|
18
|
+
* Programs spawned without bundle-ID resolution (anything outside
|
|
19
|
+
* `lsappinfo`'s knowledge of launched apps, for example `sleep` or a
|
|
20
|
+
* manually-compiled binary) silently skip.
|
|
21
|
+
*/
|
|
22
|
+
const appkit = {
|
|
23
|
+
name: "appkit",
|
|
24
|
+
matches(probe) {
|
|
25
|
+
return probe.platform === "darwin" && typeof probe.bundleId === "string";
|
|
26
|
+
},
|
|
27
|
+
async clearCrashState(probe) {
|
|
28
|
+
if (!probe.bundleId) return;
|
|
29
|
+
await rm(join(homedir(), "Library", "Saved Application State", `${probe.bundleId}.savedState`), {
|
|
30
|
+
recursive: true,
|
|
31
|
+
force: true
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/adapters/chromium.ts
|
|
37
|
+
const CHROMIUM_BASENAMES = [
|
|
38
|
+
"chrome",
|
|
39
|
+
"chromium",
|
|
40
|
+
"Chromium",
|
|
41
|
+
"Google Chrome",
|
|
42
|
+
"brave",
|
|
43
|
+
"Brave Browser",
|
|
44
|
+
"msedge",
|
|
45
|
+
"Microsoft Edge"
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* Stub adapter for Chromium-family browsers.
|
|
49
|
+
*
|
|
50
|
+
* Matches by executable basename (case-sensitive, since bundle exe names
|
|
51
|
+
* are preserved verbatim). The real `clearCrashState` implementation
|
|
52
|
+
* edits the "Last Exit Type" key in the browser's `Preferences` JSON so
|
|
53
|
+
* the next launch does not prompt the user to restore a crashed
|
|
54
|
+
* session. That implementation needs per-OS profile-dir detection and
|
|
55
|
+
* careful JSON editing; it ships in a follow-up.
|
|
56
|
+
*
|
|
57
|
+
* Until then `clearCrashState` emits a warning and returns. The adapter
|
|
58
|
+
* still matches so callers wiring it in can see the "didn't run" signal
|
|
59
|
+
* and swap in a real cleanup when the full version ships.
|
|
60
|
+
*/
|
|
61
|
+
const chromium = {
|
|
62
|
+
name: "chromium",
|
|
63
|
+
matches(probe) {
|
|
64
|
+
const exe = probe.executablePath ? basename(probe.executablePath) : void 0;
|
|
65
|
+
return exe ? CHROMIUM_BASENAMES.includes(exe) : false;
|
|
66
|
+
},
|
|
67
|
+
clearCrashState(_probe) {
|
|
68
|
+
console.warn("uncontainerizable: chromium.clearCrashState is a stub; the next launch may show a 'didn't shut down correctly' dialog. Track the real implementation in a follow-up release.");
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/adapters/crash-reporter.ts
|
|
73
|
+
/**
|
|
74
|
+
* Deletes per-app crash reports from macOS's user-level CrashReporter
|
|
75
|
+
* archive after a terminal-stage destroy.
|
|
76
|
+
*
|
|
77
|
+
* When our SIGTERM/SIGKILL ladder kills an app, macOS's `ReportCrash`
|
|
78
|
+
* daemon writes an `.ips` entry under
|
|
79
|
+
* `~/Library/Logs/DiagnosticReports/` named like
|
|
80
|
+
* `<AppName>_<timestamp>_<host>.ips`. The next time the user opens the
|
|
81
|
+
* Console app (or the crash-reporter dialog appears) those entries
|
|
82
|
+
* show up as "<App> quit unexpectedly" even though the quit was
|
|
83
|
+
* deliberate.
|
|
84
|
+
*
|
|
85
|
+
* This adapter matches by the basename of `Probe.executablePath` and
|
|
86
|
+
* deletes every report whose filename starts with that basename,
|
|
87
|
+
* followed by an underscore. We only touch files under the user's
|
|
88
|
+
* DiagnosticReports directory; the system-wide `/Library/Logs/...`
|
|
89
|
+
* archive is root-owned and out of scope.
|
|
90
|
+
*
|
|
91
|
+
* Note that this does NOT suppress the modal "quit unexpectedly"
|
|
92
|
+
* dialog that fires immediately when a process crashes: that is
|
|
93
|
+
* displayed before any cleanup hook runs. For that case, let the
|
|
94
|
+
* `apple_event_quit` stage resolve the quit cleanly instead of
|
|
95
|
+
* escalating to SIGTERM/SIGKILL.
|
|
96
|
+
*/
|
|
97
|
+
const crashReporter = {
|
|
98
|
+
name: "crashReporter",
|
|
99
|
+
matches(probe) {
|
|
100
|
+
return probe.platform === "darwin" && typeof probe.executablePath === "string";
|
|
101
|
+
},
|
|
102
|
+
async clearCrashState(probe) {
|
|
103
|
+
if (!probe.executablePath) return;
|
|
104
|
+
const appName = basename(probe.executablePath);
|
|
105
|
+
if (!appName) return;
|
|
106
|
+
const dir = join(homedir(), "Library", "Logs", "DiagnosticReports");
|
|
107
|
+
if (!(await stat(dir).catch(() => void 0))?.isDirectory()) return;
|
|
108
|
+
const entries = await readdir(dir);
|
|
109
|
+
const prefix = `${appName}_`;
|
|
110
|
+
const removals = entries.filter((name) => name.startsWith(prefix) && (name.endsWith(".ips") || name.endsWith(".crash"))).map((name) => rm(join(dir, name), { force: true }));
|
|
111
|
+
await Promise.all(removals);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/adapters/firefox.ts
|
|
116
|
+
const FIREFOX_BASENAMES = [
|
|
117
|
+
"firefox",
|
|
118
|
+
"Firefox",
|
|
119
|
+
"firefox-bin"
|
|
120
|
+
];
|
|
121
|
+
/**
|
|
122
|
+
* Stub adapter for Firefox.
|
|
123
|
+
*
|
|
124
|
+
* Matches by executable basename. The real `clearCrashState` deletes
|
|
125
|
+
* the profile's `sessionstore-backups/recovery.jsonlz4` so the next
|
|
126
|
+
* launch does not offer session restore. Per-OS profile-dir detection
|
|
127
|
+
* and lz4 handling ship in a follow-up.
|
|
128
|
+
*
|
|
129
|
+
* Until then `clearCrashState` emits a warning and returns.
|
|
130
|
+
*/
|
|
131
|
+
const firefox = {
|
|
132
|
+
name: "firefox",
|
|
133
|
+
matches(probe) {
|
|
134
|
+
const exe = probe.executablePath ? basename(probe.executablePath) : void 0;
|
|
135
|
+
return exe ? FIREFOX_BASENAMES.includes(exe) : false;
|
|
136
|
+
},
|
|
137
|
+
clearCrashState(_probe) {
|
|
138
|
+
console.warn("uncontainerizable: firefox.clearCrashState is a stub; the next launch may prompt to restore the previous session. Track the real implementation in a follow-up release.");
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/adapters/index.ts
|
|
143
|
+
/**
|
|
144
|
+
* The bundle a typical browser-supervising caller wants: every built-in
|
|
145
|
+
* adapter, in an order that doesn't matter (hook invocation is
|
|
146
|
+
* idempotent and non-overlapping across adapters).
|
|
147
|
+
*/
|
|
148
|
+
const defaultAdapters = [
|
|
149
|
+
chromium,
|
|
150
|
+
firefox,
|
|
151
|
+
appkit,
|
|
152
|
+
crashReporter
|
|
153
|
+
];
|
|
154
|
+
//#endregion
|
|
155
|
+
export { appkit as a, chromium as i, firefox as n, crashReporter as r, defaultAdapters as t };
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=adapters-BYLFpYWC.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters-BYLFpYWC.mjs","names":[],"sources":["../src/adapters/appkit.ts","../src/adapters/chromium.ts","../src/adapters/crash-reporter.ts","../src/adapters/firefox.ts","../src/adapters/index.ts"],"sourcesContent":["import { rm } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport type { Adapter, Probe } from \"#/types.js\";\n\n/**\n * Deletes the AppKit \"Saved Application State\" directory for the managed\n * app after a terminal-stage destroy.\n *\n * macOS's AppKit persists a per-app snapshot of open windows and the\n * \"should reopen windows on next launch\" hint at\n * `~/Library/Saved Application State/<bundleId>.savedState/`. When an\n * app is force-killed (our SIGTERM/SIGKILL ladder), AppKit interprets\n * the missing clean-shutdown marker as a crash and shows the\n * \"Reopen windows?\" dialog next launch. Wiping the directory suppresses\n * the dialog.\n *\n * Only matches on darwin probes that carry a resolved `bundleId`.\n * Programs spawned without bundle-ID resolution (anything outside\n * `lsappinfo`'s knowledge of launched apps, for example `sleep` or a\n * manually-compiled binary) silently skip.\n */\nexport const appkit: Adapter = {\n name: \"appkit\",\n\n matches(probe: Probe): boolean {\n return probe.platform === \"darwin\" && typeof probe.bundleId === \"string\";\n },\n\n async clearCrashState(probe: Probe): Promise<void> {\n if (!probe.bundleId) {\n return;\n }\n const dir = join(\n homedir(),\n \"Library\",\n \"Saved Application State\",\n `${probe.bundleId}.savedState`\n );\n await rm(dir, { recursive: true, force: true });\n },\n};\n","import { basename } from \"node:path\";\n\nimport type { Adapter, Probe } from \"#/types.js\";\n\nconst CHROMIUM_BASENAMES = [\n \"chrome\",\n \"chromium\",\n \"Chromium\",\n \"Google Chrome\",\n \"brave\",\n \"Brave Browser\",\n \"msedge\",\n \"Microsoft Edge\",\n];\n\n/**\n * Stub adapter for Chromium-family browsers.\n *\n * Matches by executable basename (case-sensitive, since bundle exe names\n * are preserved verbatim). The real `clearCrashState` implementation\n * edits the \"Last Exit Type\" key in the browser's `Preferences` JSON so\n * the next launch does not prompt the user to restore a crashed\n * session. That implementation needs per-OS profile-dir detection and\n * careful JSON editing; it ships in a follow-up.\n *\n * Until then `clearCrashState` emits a warning and returns. The adapter\n * still matches so callers wiring it in can see the \"didn't run\" signal\n * and swap in a real cleanup when the full version ships.\n */\nexport const chromium: Adapter = {\n name: \"chromium\",\n\n matches(probe: Probe): boolean {\n const exe = probe.executablePath\n ? basename(probe.executablePath)\n : undefined;\n return exe ? CHROMIUM_BASENAMES.includes(exe) : false;\n },\n\n clearCrashState(_probe: Probe): void {\n console.warn(\n \"uncontainerizable: chromium.clearCrashState is a stub; the next launch may show a 'didn't shut down correctly' dialog. Track the real implementation in a follow-up release.\"\n );\n },\n};\n","import { readdir, rm, stat } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, join } from \"node:path\";\n\nimport type { Adapter, Probe } from \"#/types.js\";\n\n/**\n * Deletes per-app crash reports from macOS's user-level CrashReporter\n * archive after a terminal-stage destroy.\n *\n * When our SIGTERM/SIGKILL ladder kills an app, macOS's `ReportCrash`\n * daemon writes an `.ips` entry under\n * `~/Library/Logs/DiagnosticReports/` named like\n * `<AppName>_<timestamp>_<host>.ips`. The next time the user opens the\n * Console app (or the crash-reporter dialog appears) those entries\n * show up as \"<App> quit unexpectedly\" even though the quit was\n * deliberate.\n *\n * This adapter matches by the basename of `Probe.executablePath` and\n * deletes every report whose filename starts with that basename,\n * followed by an underscore. We only touch files under the user's\n * DiagnosticReports directory; the system-wide `/Library/Logs/...`\n * archive is root-owned and out of scope.\n *\n * Note that this does NOT suppress the modal \"quit unexpectedly\"\n * dialog that fires immediately when a process crashes: that is\n * displayed before any cleanup hook runs. For that case, let the\n * `apple_event_quit` stage resolve the quit cleanly instead of\n * escalating to SIGTERM/SIGKILL.\n */\nexport const crashReporter: Adapter = {\n name: \"crashReporter\",\n\n matches(probe: Probe): boolean {\n return (\n probe.platform === \"darwin\" && typeof probe.executablePath === \"string\"\n );\n },\n\n async clearCrashState(probe: Probe): Promise<void> {\n if (!probe.executablePath) {\n return;\n }\n const appName = basename(probe.executablePath);\n if (!appName) {\n return;\n }\n const dir = join(homedir(), \"Library\", \"Logs\", \"DiagnosticReports\");\n // `fs.stat` rejects if the path doesn't exist. A fresh user account\n // may not have a DiagnosticReports directory, so probe first and\n // short-circuit if it's missing or not a directory. Swallowing the\n // reject into `undefined` keeps the happy path straight-line.\n const stats = await stat(dir).catch(() => undefined);\n if (!stats?.isDirectory()) {\n return;\n }\n const entries = await readdir(dir);\n const prefix = `${appName}_`;\n const removals = entries\n .filter(\n (name) =>\n name.startsWith(prefix) &&\n (name.endsWith(\".ips\") || name.endsWith(\".crash\"))\n )\n .map((name) => rm(join(dir, name), { force: true }));\n await Promise.all(removals);\n },\n};\n","import { basename } from \"node:path\";\n\nimport type { Adapter, Probe } from \"#/types.js\";\n\nconst FIREFOX_BASENAMES = [\"firefox\", \"Firefox\", \"firefox-bin\"];\n\n/**\n * Stub adapter for Firefox.\n *\n * Matches by executable basename. The real `clearCrashState` deletes\n * the profile's `sessionstore-backups/recovery.jsonlz4` so the next\n * launch does not offer session restore. Per-OS profile-dir detection\n * and lz4 handling ship in a follow-up.\n *\n * Until then `clearCrashState` emits a warning and returns.\n */\nexport const firefox: Adapter = {\n name: \"firefox\",\n\n matches(probe: Probe): boolean {\n const exe = probe.executablePath\n ? basename(probe.executablePath)\n : undefined;\n return exe ? FIREFOX_BASENAMES.includes(exe) : false;\n },\n\n clearCrashState(_probe: Probe): void {\n console.warn(\n \"uncontainerizable: firefox.clearCrashState is a stub; the next launch may prompt to restore the previous session. Track the real implementation in a follow-up release.\"\n );\n },\n};\n","import { appkit } from \"#/adapters/appkit.js\";\nimport { chromium } from \"#/adapters/chromium.js\";\nimport { crashReporter } from \"#/adapters/crash-reporter.js\";\nimport { firefox } from \"#/adapters/firefox.js\";\nimport type { Adapter } from \"#/types.js\";\n\nexport { appkit } from \"#/adapters/appkit.js\";\nexport { chromium } from \"#/adapters/chromium.js\";\nexport { crashReporter } from \"#/adapters/crash-reporter.js\";\nexport { firefox } from \"#/adapters/firefox.js\";\n\n/**\n * The bundle a typical browser-supervising caller wants: every built-in\n * adapter, in an order that doesn't matter (hook invocation is\n * idempotent and non-overlapping across adapters).\n */\nexport const defaultAdapters: readonly Adapter[] = [\n chromium,\n firefox,\n appkit,\n crashReporter,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,SAAkB;CAC7B,MAAM;CAEN,QAAQ,OAAuB;AAC7B,SAAO,MAAM,aAAa,YAAY,OAAO,MAAM,aAAa;;CAGlE,MAAM,gBAAgB,OAA6B;AACjD,MAAI,CAAC,MAAM,SACT;AAQF,QAAM,GANM,KACV,SAAS,EACT,WACA,2BACA,GAAG,MAAM,SAAS,aACnB,EACa;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;;CAElD;;;ACtCD,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;AAgBD,MAAa,WAAoB;CAC/B,MAAM;CAEN,QAAQ,OAAuB;EAC7B,MAAM,MAAM,MAAM,iBACd,SAAS,MAAM,eAAe,GAC9B,KAAA;AACJ,SAAO,MAAM,mBAAmB,SAAS,IAAI,GAAG;;CAGlD,gBAAgB,QAAqB;AACnC,UAAQ,KACN,+KACD;;CAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;ACdD,MAAa,gBAAyB;CACpC,MAAM;CAEN,QAAQ,OAAuB;AAC7B,SACE,MAAM,aAAa,YAAY,OAAO,MAAM,mBAAmB;;CAInE,MAAM,gBAAgB,OAA6B;AACjD,MAAI,CAAC,MAAM,eACT;EAEF,MAAM,UAAU,SAAS,MAAM,eAAe;AAC9C,MAAI,CAAC,QACH;EAEF,MAAM,MAAM,KAAK,SAAS,EAAE,WAAW,QAAQ,oBAAoB;AAMnE,MAAI,EADU,MAAM,KAAK,IAAI,CAAC,YAAY,KAAA,EAAU,GACxC,aAAa,CACvB;EAEF,MAAM,UAAU,MAAM,QAAQ,IAAI;EAClC,MAAM,SAAS,GAAG,QAAQ;EAC1B,MAAM,WAAW,QACd,QACE,SACC,KAAK,WAAW,OAAO,KACtB,KAAK,SAAS,OAAO,IAAI,KAAK,SAAS,SAAS,EACpD,CACA,KAAK,SAAS,GAAG,KAAK,KAAK,KAAK,EAAE,EAAE,OAAO,MAAM,CAAC,CAAC;AACtD,QAAM,QAAQ,IAAI,SAAS;;CAE9B;;;AC/DD,MAAM,oBAAoB;CAAC;CAAW;CAAW;CAAc;;;;;;;;;;;AAY/D,MAAa,UAAmB;CAC9B,MAAM;CAEN,QAAQ,OAAuB;EAC7B,MAAM,MAAM,MAAM,iBACd,SAAS,MAAM,eAAe,GAC9B,KAAA;AACJ,SAAO,MAAM,kBAAkB,SAAS,IAAI,GAAG;;CAGjD,gBAAgB,QAAqB;AACnC,UAAQ,KACN,0KACD;;CAEJ;;;;;;;;ACfD,MAAa,kBAAsC;CACjD;CACA;CACA;CACA;CACD"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { JsContainOptions, JsDestroyOptions, JsDestroyResult, JsProbe, JsQuitOptions, JsQuitResult, JsStageResult, NodeContainer } from "@uncontainerizable/native";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type SupportedPlatform = "linux" | "darwin" | "win32";
|
|
5
|
+
type QuitOptions = JsQuitOptions;
|
|
6
|
+
type DestroyOptions = JsDestroyOptions;
|
|
7
|
+
type QuitResult = JsQuitResult;
|
|
8
|
+
type DestroyResult = JsDestroyResult;
|
|
9
|
+
type StageResult = JsStageResult;
|
|
10
|
+
type Probe = JsProbe;
|
|
11
|
+
/**
|
|
12
|
+
* Platform-agnostic handle for a spawned contained process. Returned by
|
|
13
|
+
* `App.contain`; backed by the napi-generated `NodeContainer` class.
|
|
14
|
+
*/
|
|
15
|
+
type Container = NodeContainer;
|
|
16
|
+
/**
|
|
17
|
+
* Per-app lifecycle hook. Each method except `name` and `matches` is
|
|
18
|
+
* optional; unimplemented hooks are skipped by the Rust orchestrator.
|
|
19
|
+
*
|
|
20
|
+
* Every hook may return a value or a Promise; the TypeScript wrapper
|
|
21
|
+
* normalizes both forms to `Promise<_>` before handing the adapter to
|
|
22
|
+
* the napi bridge. Hooks must not throw synchronously: a thrown value
|
|
23
|
+
* is trapped by the bridge and recorded in
|
|
24
|
+
* `QuitResult.adapterErrors`, but ergonomic code should prefer
|
|
25
|
+
* returning a rejected Promise.
|
|
26
|
+
*/
|
|
27
|
+
type Adapter = {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
matches(probe: Probe): boolean | Promise<boolean>;
|
|
30
|
+
beforeQuit?(probe: Probe): void | Promise<void>;
|
|
31
|
+
beforeStage?(probe: Probe, stageName: string): void | Promise<void>;
|
|
32
|
+
afterStage?(probe: Probe, result: StageResult): void | Promise<void>;
|
|
33
|
+
afterQuit?(probe: Probe, result: QuitResult): void | Promise<void>;
|
|
34
|
+
clearCrashState?(probe: Probe): void | Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Extends the napi-generated options with a TypeScript-only `adapters`
|
|
38
|
+
* field. The wrapper normalizes each adapter's hook methods to the
|
|
39
|
+
* async signatures the napi bridge expects before crossing the
|
|
40
|
+
* boundary.
|
|
41
|
+
*/
|
|
42
|
+
type ContainOptions = Omit<JsContainOptions, "adapters"> & {
|
|
43
|
+
adapters?: Adapter[];
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/adapters/appkit.d.ts
|
|
47
|
+
/**
|
|
48
|
+
* Deletes the AppKit "Saved Application State" directory for the managed
|
|
49
|
+
* app after a terminal-stage destroy.
|
|
50
|
+
*
|
|
51
|
+
* macOS's AppKit persists a per-app snapshot of open windows and the
|
|
52
|
+
* "should reopen windows on next launch" hint at
|
|
53
|
+
* `~/Library/Saved Application State/<bundleId>.savedState/`. When an
|
|
54
|
+
* app is force-killed (our SIGTERM/SIGKILL ladder), AppKit interprets
|
|
55
|
+
* the missing clean-shutdown marker as a crash and shows the
|
|
56
|
+
* "Reopen windows?" dialog next launch. Wiping the directory suppresses
|
|
57
|
+
* the dialog.
|
|
58
|
+
*
|
|
59
|
+
* Only matches on darwin probes that carry a resolved `bundleId`.
|
|
60
|
+
* Programs spawned without bundle-ID resolution (anything outside
|
|
61
|
+
* `lsappinfo`'s knowledge of launched apps, for example `sleep` or a
|
|
62
|
+
* manually-compiled binary) silently skip.
|
|
63
|
+
*/
|
|
64
|
+
declare const appkit: Adapter;
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/adapters/chromium.d.ts
|
|
67
|
+
/**
|
|
68
|
+
* Stub adapter for Chromium-family browsers.
|
|
69
|
+
*
|
|
70
|
+
* Matches by executable basename (case-sensitive, since bundle exe names
|
|
71
|
+
* are preserved verbatim). The real `clearCrashState` implementation
|
|
72
|
+
* edits the "Last Exit Type" key in the browser's `Preferences` JSON so
|
|
73
|
+
* the next launch does not prompt the user to restore a crashed
|
|
74
|
+
* session. That implementation needs per-OS profile-dir detection and
|
|
75
|
+
* careful JSON editing; it ships in a follow-up.
|
|
76
|
+
*
|
|
77
|
+
* Until then `clearCrashState` emits a warning and returns. The adapter
|
|
78
|
+
* still matches so callers wiring it in can see the "didn't run" signal
|
|
79
|
+
* and swap in a real cleanup when the full version ships.
|
|
80
|
+
*/
|
|
81
|
+
declare const chromium: Adapter;
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/adapters/crash-reporter.d.ts
|
|
84
|
+
/**
|
|
85
|
+
* Deletes per-app crash reports from macOS's user-level CrashReporter
|
|
86
|
+
* archive after a terminal-stage destroy.
|
|
87
|
+
*
|
|
88
|
+
* When our SIGTERM/SIGKILL ladder kills an app, macOS's `ReportCrash`
|
|
89
|
+
* daemon writes an `.ips` entry under
|
|
90
|
+
* `~/Library/Logs/DiagnosticReports/` named like
|
|
91
|
+
* `<AppName>_<timestamp>_<host>.ips`. The next time the user opens the
|
|
92
|
+
* Console app (or the crash-reporter dialog appears) those entries
|
|
93
|
+
* show up as "<App> quit unexpectedly" even though the quit was
|
|
94
|
+
* deliberate.
|
|
95
|
+
*
|
|
96
|
+
* This adapter matches by the basename of `Probe.executablePath` and
|
|
97
|
+
* deletes every report whose filename starts with that basename,
|
|
98
|
+
* followed by an underscore. We only touch files under the user's
|
|
99
|
+
* DiagnosticReports directory; the system-wide `/Library/Logs/...`
|
|
100
|
+
* archive is root-owned and out of scope.
|
|
101
|
+
*
|
|
102
|
+
* Note that this does NOT suppress the modal "quit unexpectedly"
|
|
103
|
+
* dialog that fires immediately when a process crashes: that is
|
|
104
|
+
* displayed before any cleanup hook runs. For that case, let the
|
|
105
|
+
* `apple_event_quit` stage resolve the quit cleanly instead of
|
|
106
|
+
* escalating to SIGTERM/SIGKILL.
|
|
107
|
+
*/
|
|
108
|
+
declare const crashReporter: Adapter;
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/adapters/firefox.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* Stub adapter for Firefox.
|
|
113
|
+
*
|
|
114
|
+
* Matches by executable basename. The real `clearCrashState` deletes
|
|
115
|
+
* the profile's `sessionstore-backups/recovery.jsonlz4` so the next
|
|
116
|
+
* launch does not offer session restore. Per-OS profile-dir detection
|
|
117
|
+
* and lz4 handling ship in a follow-up.
|
|
118
|
+
*
|
|
119
|
+
* Until then `clearCrashState` emits a warning and returns.
|
|
120
|
+
*/
|
|
121
|
+
declare const firefox: Adapter;
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/adapters/index.d.ts
|
|
124
|
+
/**
|
|
125
|
+
* The bundle a typical browser-supervising caller wants: every built-in
|
|
126
|
+
* adapter, in an order that doesn't matter (hook invocation is
|
|
127
|
+
* idempotent and non-overlapping across adapters).
|
|
128
|
+
*/
|
|
129
|
+
declare const defaultAdapters: readonly Adapter[];
|
|
130
|
+
//#endregion
|
|
131
|
+
export { appkit as a, Container as c, Probe as d, QuitOptions as f, SupportedPlatform as h, chromium as i, DestroyOptions as l, StageResult as m, firefox as n, Adapter as o, QuitResult as p, crashReporter as r, ContainOptions as s, defaultAdapters as t, DestroyResult as u };
|
|
132
|
+
//# sourceMappingURL=index-B1y4yb1N.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-B1y4yb1N.d.mts","names":[],"sources":["../src/types.ts","../src/adapters/appkit.ts","../src/adapters/chromium.ts","../src/adapters/crash-reporter.ts","../src/adapters/firefox.ts","../src/adapters/index.ts"],"mappings":";;;KAWY,iBAAA;AAAA,KAEA,WAAA,GAAc,aAAA;AAAA,KACd,cAAA,GAAiB,gBAAA;AAAA,KACjB,UAAA,GAAa,YAAA;AAAA,KACb,aAAA,GAAgB,eAAA;AAAA,KAChB,WAAA,GAAc,aAAA;AAAA,KACd,KAAA,GAAQ,OAAA;AALpB;;;;AAAA,KAWY,SAAA,GAAY,aAAA;AAVxB;;;;;AACA;;;;;AACA;AAFA,KAuBY,OAAA;EAAA,SACD,IAAA;EACT,OAAA,CAAQ,KAAA,EAAO,KAAA,aAAkB,OAAA;EACjC,UAAA,EAAY,KAAA,EAAO,KAAA,UAAe,OAAA;EAClC,WAAA,EAAa,KAAA,EAAO,KAAA,EAAO,SAAA,kBAA2B,OAAA;EACtD,UAAA,EAAY,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,WAAA,UAAqB,OAAA;EACvD,SAAA,EAAW,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,UAAA,UAAoB,OAAA;EACrD,eAAA,EAAiB,KAAA,EAAO,KAAA,UAAe,OAAA;AAAA;AA1BzC;;;;;AAMA;AANA,KAmCY,cAAA,GAAiB,IAAA,CAAK,gBAAA;EAChC,QAAA,GAAW,OAAA;AAAA;;;;;AA3Cb;;;;;AAEA;;;;;AACA;;;;;cCSa,MAAA,EAAQ,OAAA;;;;;ADZrB;;;;;AAEA;;;;;AACA;;cEea,QAAA,EAAU,OAAA;;;;;AFlBvB;;;;;AAEA;;;;;AACA;;;;;AACA;;;;;AACA;;cGca,aAAA,EAAe,OAAA;;;;;AHnB5B;;;;;AAEA;;;cIGa,OAAA,EAAS,OAAA;;;;;;AJHtB;;cKGa,eAAA,WAA0B,OAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
import { a as appkit, c as Container, d as Probe, f as QuitOptions, h as SupportedPlatform, i as chromium, l as DestroyOptions, m as StageResult, n as firefox, o as Adapter, p as QuitResult, r as crashReporter, s as ContainOptions, t as defaultAdapters, u as DestroyResult } from "./index-B1y4yb1N.mjs";
|
|
1
2
|
import { coreVersion } from "@uncontainerizable/native";
|
|
2
3
|
|
|
3
|
-
//#region src/
|
|
4
|
-
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Namespaced handle for spawning contained processes.
|
|
7
|
+
*
|
|
8
|
+
* Construct once per application, typically with a reverse-DNS string like
|
|
9
|
+
* `"com.example.my-supervisor"`. The prefix namespaces identity strings so
|
|
10
|
+
* two unrelated libraries using `uncontainerizable` cannot collide.
|
|
11
|
+
*
|
|
12
|
+
* Throws `INVALID_IDENTITY` if the prefix contains characters outside
|
|
13
|
+
* `[A-Za-z0-9._:-]`.
|
|
14
|
+
*/
|
|
15
|
+
declare class App {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(prefix: string);
|
|
18
|
+
get prefix(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Spawn a contained process. If `options.identity` is set, any previous
|
|
21
|
+
* instance with the same (prefix, identity) pair is killed before this
|
|
22
|
+
* one launches. If `options.adapters` is non-empty the Rust
|
|
23
|
+
* orchestrator drives their lifecycle hooks around the quit ladder.
|
|
24
|
+
*/
|
|
25
|
+
contain(command: string, options?: ContainOptions): Promise<Container>;
|
|
26
|
+
}
|
|
5
27
|
//#endregion
|
|
6
|
-
export { type SupportedPlatform, coreVersion };
|
|
28
|
+
export { type Adapter, App, type ContainOptions, type Container, type DestroyOptions, type DestroyResult, type Probe, type QuitOptions, type QuitResult, type StageResult, type SupportedPlatform, appkit, chromium, coreVersion, crashReporter, defaultAdapters, firefox };
|
|
7
29
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AAwBA;;;;;;;;;cAAa,GAAA;EAAA;cAGC,MAAA;EAAA,IAIR,MAAA,CAAA;EAUI;;;;;;EAAR,OAAA,CAAQ,OAAA,UAAiB,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,SAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as appkit, i as chromium, n as firefox, r as crashReporter, t as defaultAdapters } from "./adapters-BYLFpYWC.mjs";
|
|
2
|
+
import { NodeApp, coreVersion } from "@uncontainerizable/native";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Namespaced handle for spawning contained processes.
|
|
6
|
+
*
|
|
7
|
+
* Construct once per application, typically with a reverse-DNS string like
|
|
8
|
+
* `"com.example.my-supervisor"`. The prefix namespaces identity strings so
|
|
9
|
+
* two unrelated libraries using `uncontainerizable` cannot collide.
|
|
10
|
+
*
|
|
11
|
+
* Throws `INVALID_IDENTITY` if the prefix contains characters outside
|
|
12
|
+
* `[A-Za-z0-9._:-]`.
|
|
13
|
+
*/
|
|
14
|
+
var App = class {
|
|
15
|
+
#inner;
|
|
16
|
+
constructor(prefix) {
|
|
17
|
+
this.#inner = new NodeApp(prefix);
|
|
18
|
+
}
|
|
19
|
+
get prefix() {
|
|
20
|
+
return this.#inner.prefix;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Spawn a contained process. If `options.identity` is set, any previous
|
|
24
|
+
* instance with the same (prefix, identity) pair is killed before this
|
|
25
|
+
* one launches. If `options.adapters` is non-empty the Rust
|
|
26
|
+
* orchestrator drives their lifecycle hooks around the quit ladder.
|
|
27
|
+
*/
|
|
28
|
+
contain(command, options = {}) {
|
|
29
|
+
const { adapters, ...nativeOpts } = options;
|
|
30
|
+
return this.#inner.contain(command, {
|
|
31
|
+
...nativeOpts,
|
|
32
|
+
adapters: adapters?.map(normalizeAdapter)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Normalize a user-provided `Adapter` (which may declare sync methods)
|
|
38
|
+
* into the always-async shape the napi bridge expects. Every optional
|
|
39
|
+
* hook is only forwarded if defined, so the Rust side keeps its
|
|
40
|
+
* "undefined means skip" semantics.
|
|
41
|
+
*/
|
|
42
|
+
function normalizeAdapter(adapter) {
|
|
43
|
+
return {
|
|
44
|
+
name: adapter.name,
|
|
45
|
+
matches: async (probe) => Boolean(await adapter.matches(probe)),
|
|
46
|
+
beforeQuit: adapter.beforeQuit ? async (probe) => {
|
|
47
|
+
await adapter.beforeQuit?.(probe);
|
|
48
|
+
} : void 0,
|
|
49
|
+
beforeStage: adapter.beforeStage ? async (probe, stageName) => {
|
|
50
|
+
await adapter.beforeStage?.(probe, stageName);
|
|
51
|
+
} : void 0,
|
|
52
|
+
afterStage: adapter.afterStage ? async (probe, result) => {
|
|
53
|
+
await adapter.afterStage?.(probe, result);
|
|
54
|
+
} : void 0,
|
|
55
|
+
afterQuit: adapter.afterQuit ? async (probe, result) => {
|
|
56
|
+
await adapter.afterQuit?.(probe, result);
|
|
57
|
+
} : void 0,
|
|
58
|
+
clearCrashState: adapter.clearCrashState ? async (probe) => {
|
|
59
|
+
await adapter.clearCrashState?.(probe);
|
|
60
|
+
} : void 0
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { App, appkit, chromium, coreVersion, crashReporter, defaultAdapters, firefox };
|
|
2
65
|
|
|
3
|
-
|
|
66
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#inner"],"sources":["../src/index.ts"],"sourcesContent":["import { type JsAdapter, NodeApp } from \"@uncontainerizable/native\";\n\nimport type { Adapter, ContainOptions, Container } from \"#/types.js\";\n\nexport { coreVersion } from \"@uncontainerizable/native\";\n\nexport {\n appkit,\n chromium,\n crashReporter,\n defaultAdapters,\n firefox,\n} from \"#/adapters/index.js\";\n\n/**\n * Namespaced handle for spawning contained processes.\n *\n * Construct once per application, typically with a reverse-DNS string like\n * `\"com.example.my-supervisor\"`. The prefix namespaces identity strings so\n * two unrelated libraries using `uncontainerizable` cannot collide.\n *\n * Throws `INVALID_IDENTITY` if the prefix contains characters outside\n * `[A-Za-z0-9._:-]`.\n */\nexport class App {\n readonly #inner: NodeApp;\n\n constructor(prefix: string) {\n this.#inner = new NodeApp(prefix);\n }\n\n get prefix(): string {\n return this.#inner.prefix;\n }\n\n /**\n * Spawn a contained process. If `options.identity` is set, any previous\n * instance with the same (prefix, identity) pair is killed before this\n * one launches. If `options.adapters` is non-empty the Rust\n * orchestrator drives their lifecycle hooks around the quit ladder.\n */\n contain(command: string, options: ContainOptions = {}): Promise<Container> {\n const { adapters, ...nativeOpts } = options;\n return this.#inner.contain(command, {\n ...nativeOpts,\n adapters: adapters?.map(normalizeAdapter),\n });\n }\n}\n\n/**\n * Normalize a user-provided `Adapter` (which may declare sync methods)\n * into the always-async shape the napi bridge expects. Every optional\n * hook is only forwarded if defined, so the Rust side keeps its\n * \"undefined means skip\" semantics.\n */\nfunction normalizeAdapter(adapter: Adapter): JsAdapter {\n return {\n name: adapter.name,\n matches: async (probe) => Boolean(await adapter.matches(probe)),\n beforeQuit: adapter.beforeQuit\n ? async (probe) => {\n await adapter.beforeQuit?.(probe);\n }\n : undefined,\n beforeStage: adapter.beforeStage\n ? async (probe, stageName) => {\n await adapter.beforeStage?.(probe, stageName);\n }\n : undefined,\n afterStage: adapter.afterStage\n ? async (probe, result) => {\n await adapter.afterStage?.(probe, result);\n }\n : undefined,\n afterQuit: adapter.afterQuit\n ? async (probe, result) => {\n await adapter.afterQuit?.(probe, result);\n }\n : undefined,\n clearCrashState: adapter.clearCrashState\n ? async (probe) => {\n await adapter.clearCrashState?.(probe);\n }\n : undefined,\n };\n}\n\nexport type {\n Adapter,\n ContainOptions,\n Container,\n DestroyOptions,\n DestroyResult,\n Probe,\n QuitOptions,\n QuitResult,\n StageResult,\n SupportedPlatform,\n} from \"#/types.js\";\n"],"mappings":";;;;;;;;;;;;;AAwBA,IAAa,MAAb,MAAiB;CACf;CAEA,YAAY,QAAgB;AAC1B,QAAA,QAAc,IAAI,QAAQ,OAAO;;CAGnC,IAAI,SAAiB;AACnB,SAAO,MAAA,MAAY;;;;;;;;CASrB,QAAQ,SAAiB,UAA0B,EAAE,EAAsB;EACzE,MAAM,EAAE,UAAU,GAAG,eAAe;AACpC,SAAO,MAAA,MAAY,QAAQ,SAAS;GAClC,GAAG;GACH,UAAU,UAAU,IAAI,iBAAiB;GAC1C,CAAC;;;;;;;;;AAUN,SAAS,iBAAiB,SAA6B;AACrD,QAAO;EACL,MAAM,QAAQ;EACd,SAAS,OAAO,UAAU,QAAQ,MAAM,QAAQ,QAAQ,MAAM,CAAC;EAC/D,YAAY,QAAQ,aAChB,OAAO,UAAU;AACf,SAAM,QAAQ,aAAa,MAAM;MAEnC,KAAA;EACJ,aAAa,QAAQ,cACjB,OAAO,OAAO,cAAc;AAC1B,SAAM,QAAQ,cAAc,OAAO,UAAU;MAE/C,KAAA;EACJ,YAAY,QAAQ,aAChB,OAAO,OAAO,WAAW;AACvB,SAAM,QAAQ,aAAa,OAAO,OAAO;MAE3C,KAAA;EACJ,WAAW,QAAQ,YACf,OAAO,OAAO,WAAW;AACvB,SAAM,QAAQ,YAAY,OAAO,OAAO;MAE1C,KAAA;EACJ,iBAAiB,QAAQ,kBACrB,OAAO,UAAU;AACf,SAAM,QAAQ,kBAAkB,MAAM;MAExC,KAAA;EACL"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uncontainerizable",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Graceful process lifecycle for programs that can't be containerized",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lifecycle",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"provenance": true
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@uncontainerizable/native": "0.0
|
|
44
|
+
"@uncontainerizable/native": "0.1.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^24",
|
|
48
48
|
"publint": "^0.3",
|
|
49
|
-
"tsdown": "^0.
|
|
49
|
+
"tsdown": "^0.21.7",
|
|
50
50
|
"typescript": "^6",
|
|
51
51
|
"vitest": "^4",
|
|
52
52
|
"@uncontainerizable/tsconfig": "0.0.0"
|