uncontainerizable 0.1.1 → 0.1.2
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 +25 -8
- package/dist/index.d.mts +9 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +9 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,9 +18,10 @@ If the program can run in a real sandbox — namespaces, seccomp, landlock
|
|
|
18
18
|
stages.
|
|
19
19
|
- **Tree-aware teardown.** Helper processes get reaped alongside the root;
|
|
20
20
|
the container is "empty" only when no member remains.
|
|
21
|
-
- **Identity-based
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
- **Identity-based preemption.** Spawning with an `identity` preempts
|
|
22
|
+
earlier matching instances. Linux and Windows are identity-scoped;
|
|
23
|
+
macOS `.app` launches use bundle-scoped preemption on the Launch
|
|
24
|
+
Services path.
|
|
24
25
|
- **Adapter hooks.** Per-app lifecycle callbacks suppress "didn't shut
|
|
25
26
|
down correctly" dialogs after force-kill.
|
|
26
27
|
- **Infallible destroy.** `destroy()` aggregates errors into the result
|
|
@@ -32,13 +33,24 @@ If the program can run in a real sandbox — namespaces, seccomp, landlock
|
|
|
32
33
|
| Platform | Preemption primitive | Quit ladder |
|
|
33
34
|
| ----------------- | -------------------- | ------------------------------------------ |
|
|
34
35
|
| Linux (x64/arm64) | cgroup v2 | `SIGTERM` → `SIGKILL` (race-free via freeze) |
|
|
35
|
-
| macOS (x64/arm64) | `argv[0]` tag scan
|
|
36
|
+
| macOS (x64/arm64) | `argv[0]` tag scan / bundle-exec `ps` scan | `aevt/quit` → `SIGTERM` → `SIGKILL` |
|
|
36
37
|
| Windows (x64/arm64) | named Job Object | `WM_CLOSE` → `TerminateJobObject` |
|
|
37
38
|
|
|
38
39
|
Linux musl is shipped via `cargo-zigbuild`. Identity strings are
|
|
39
40
|
namespaced by an app-level prefix (conventionally reverse-DNS) so
|
|
40
41
|
libraries using `uncontainerizable` cannot collide.
|
|
41
42
|
|
|
43
|
+
On macOS, direct-exec launches use `argv[0]` tag scanning. Launch
|
|
44
|
+
Services `.app` launches instead match by bundle executable path via
|
|
45
|
+
`ps comm=`, so supplying `identity` there kills any running instance of
|
|
46
|
+
that bundle before relaunch, regardless of which identity started it.
|
|
47
|
+
Launch Services therefore does not support keeping two instances of the
|
|
48
|
+
same `.app` alive concurrently through this route. If you need that for
|
|
49
|
+
an app bundle, first make sure the app itself supports concurrent
|
|
50
|
+
instances, then pass the inner executable path
|
|
51
|
+
(`Foo.app/Contents/MacOS/Foo`) so the launch goes through direct-exec
|
|
52
|
+
instead of Launch Services.
|
|
53
|
+
|
|
42
54
|
## Installation
|
|
43
55
|
|
|
44
56
|
```sh
|
|
@@ -80,8 +92,13 @@ console.log(`exited at ${result.quit.exitedAtStage}`);
|
|
|
80
92
|
```
|
|
81
93
|
|
|
82
94
|
A second call to `app.contain(..., { identity: "browser-main" })` will
|
|
83
|
-
kill the running instance before launching the new one.
|
|
84
|
-
|
|
95
|
+
kill the running instance before launching the new one. On macOS `.app`
|
|
96
|
+
launches, the same option acts as a bundle-scoped clean-slate switch and
|
|
97
|
+
clears any running instance of that bundle. Omit `identity` to skip
|
|
98
|
+
preemption entirely. If you need concurrent instances of a bundled app on
|
|
99
|
+
macOS, pass the executable inside the bundle rather than the `.app`
|
|
100
|
+
directory, and only do that if the app itself supports multiple
|
|
101
|
+
instances.
|
|
85
102
|
|
|
86
103
|
## API
|
|
87
104
|
|
|
@@ -103,9 +120,9 @@ Spawns a contained process. Options:
|
|
|
103
120
|
| `args` | `string[]` | Command-line arguments. |
|
|
104
121
|
| `env` | `Record<…>` | Environment overrides. |
|
|
105
122
|
| `cwd` | `string` | Working directory. |
|
|
106
|
-
| `identity` | `string` | Enables
|
|
123
|
+
| `identity` | `string` | Enables preemption; macOS `.app` launches match by bundle, other routes by identity. Use the inner executable path, not the `.app` path, if the app supports concurrent instances and you need more than one at once. |
|
|
107
124
|
| `adapters` | `Adapter[]` | Per-app lifecycle hooks. |
|
|
108
|
-
| `darwinTagArgv0` | `boolean` | macOS only; set `false` if the managed program misreads argv[0]. |
|
|
125
|
+
| `darwinTagArgv0` | `boolean` | macOS direct-exec only; set `false` if the managed program misreads argv[0] (ignored for `.app` bundle launches). |
|
|
109
126
|
|
|
110
127
|
### `container.quit(options?) → Promise<QuitResult>`
|
|
111
128
|
|
package/dist/index.d.mts
CHANGED
|
@@ -17,10 +17,15 @@ declare class App {
|
|
|
17
17
|
constructor(prefix: string);
|
|
18
18
|
get prefix(): string;
|
|
19
19
|
/**
|
|
20
|
-
* Spawn a contained process. If `options.identity` is set,
|
|
21
|
-
* instance
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* Spawn a contained process. If `options.identity` is set, a prior
|
|
21
|
+
* matching instance is killed before launch. On macOS Launch Services
|
|
22
|
+
* `.app` launches, matching is bundle-scoped rather than
|
|
23
|
+
* `(prefix, identity)`-scoped, so this route cannot keep two instances
|
|
24
|
+
* of the same app alive concurrently. If the app itself supports
|
|
25
|
+
* multiple concurrent instances, pass the inner executable path
|
|
26
|
+
* (`Foo.app/Contents/MacOS/Foo`) to use the direct-exec route instead.
|
|
27
|
+
* If `options.adapters` is non-empty the Rust orchestrator drives their
|
|
28
|
+
* lifecycle hooks around the quit ladder.
|
|
24
29
|
*/
|
|
25
30
|
contain(command: string, options?: ContainOptions): Promise<Container>;
|
|
26
31
|
}
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AAwBA;;;;;;;;;cAAa,GAAA;EAAA;cAGC,MAAA;EAAA,IAIR,MAAA,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AAwBA;;;;;;;;;cAAa,GAAA;EAAA;cAGC,MAAA;EAAA,IAIR,MAAA,CAAA;EAeI;;;;;;;;;;;EAAR,OAAA,CAAQ,OAAA,UAAiB,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,SAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -20,10 +20,15 @@ var App = class {
|
|
|
20
20
|
return this.#inner.prefix;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Spawn a contained process. If `options.identity` is set,
|
|
24
|
-
* instance
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* Spawn a contained process. If `options.identity` is set, a prior
|
|
24
|
+
* matching instance is killed before launch. On macOS Launch Services
|
|
25
|
+
* `.app` launches, matching is bundle-scoped rather than
|
|
26
|
+
* `(prefix, identity)`-scoped, so this route cannot keep two instances
|
|
27
|
+
* of the same app alive concurrently. If the app itself supports
|
|
28
|
+
* multiple concurrent instances, pass the inner executable path
|
|
29
|
+
* (`Foo.app/Contents/MacOS/Foo`) to use the direct-exec route instead.
|
|
30
|
+
* If `options.adapters` is non-empty the Rust orchestrator drives their
|
|
31
|
+
* lifecycle hooks around the quit ladder.
|
|
27
32
|
*/
|
|
28
33
|
contain(command, options = {}) {
|
|
29
34
|
const { adapters, ...nativeOpts } = options;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +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,
|
|
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, a prior\n * matching instance is killed before launch. On macOS Launch Services\n * `.app` launches, matching is bundle-scoped rather than\n * `(prefix, identity)`-scoped, so this route cannot keep two instances\n * of the same app alive concurrently. If the app itself supports\n * multiple concurrent instances, pass the inner executable path\n * (`Foo.app/Contents/MacOS/Foo`) to use the direct-exec route instead.\n * If `options.adapters` is non-empty the Rust orchestrator drives their\n * 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;;;;;;;;;;;;;CAcrB,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.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Graceful process lifecycle for programs that can't be containerized",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lifecycle",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"provenance": true
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@uncontainerizable/native": "0.1.
|
|
44
|
+
"@uncontainerizable/native": "0.1.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^24",
|