@opys/runtime 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 +138 -0
- package/dist/index.cjs +129 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +89 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +94 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @opys/installer
|
|
2
|
+
|
|
3
|
+
Programmatic install and launch for Opys manifests. Downloads artifacts in parallel, verifies integrity, extracts natives, and spawns the JVM.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @opys/installer @opys/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Manifest sources
|
|
12
|
+
|
|
13
|
+
Both `install` and `launch` accept a **manifest source** as their first argument:
|
|
14
|
+
|
|
15
|
+
| Value | Example |
|
|
16
|
+
| ----------------- | ------------------------------------------ |
|
|
17
|
+
| File path string | `'opys.json'` |
|
|
18
|
+
| HTTPS URL object | `new URL('https://example.com/pack.json')` |
|
|
19
|
+
| Parsed `Manifest` | object returned by `resolveManifest` |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## `install(source, options?)`
|
|
24
|
+
|
|
25
|
+
Streams missing artifacts to `<finalPath>.partial` then renames them into place; extracts zips for artifacts with `extract` rules. Already-cached artifacts (path exists on disk) are skipped. A failed integrity check throws `IntegrityError` immediately.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { install } from '@opys/installer';
|
|
29
|
+
|
|
30
|
+
await install('opys.json', {
|
|
31
|
+
vars: {
|
|
32
|
+
root: '/opt/minecraft/1.20.1',
|
|
33
|
+
username: 'Player',
|
|
34
|
+
uuid: '...',
|
|
35
|
+
token: '...',
|
|
36
|
+
},
|
|
37
|
+
concurrency: 16,
|
|
38
|
+
onProgress(p) {
|
|
39
|
+
if (p.phase === 'download') {
|
|
40
|
+
process.stderr.write(` ${p.fetched}/${p.total}\r`);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Options**
|
|
47
|
+
|
|
48
|
+
| Option | Type | Default | Description |
|
|
49
|
+
| ----------------- | ------------------------------ | ------- | ---------------------------------- |
|
|
50
|
+
| `platform` | `OsOptions` | auto | Override OS/arch detection |
|
|
51
|
+
| `vars` | `Record<string, string>` | `{}` | Extra vars; override manifest vars |
|
|
52
|
+
| `concurrency` | `number` | `8` | Max parallel downloads |
|
|
53
|
+
| `onProgress` | `(p: InstallProgress) => void` | — | Progress callback |
|
|
54
|
+
| `verifyIntegrity` | `boolean` | `true` | Skip hash checks if `false` |
|
|
55
|
+
|
|
56
|
+
**`InstallProgress`**
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
type InstallProgress =
|
|
60
|
+
| { phase: 'resolve' }
|
|
61
|
+
| { phase: 'download'; fetched: number; total: number; skipped: number }
|
|
62
|
+
| { phase: 'verify' }
|
|
63
|
+
| { phase: 'extract'; count: number };
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## `launch(source, options?)`
|
|
69
|
+
|
|
70
|
+
Runs `install` then spawns the process described by the manifest's launch config. Returns a `ChildProcess` — the caller decides how to wait on it.
|
|
71
|
+
|
|
72
|
+
Pass `install: false` to skip installation.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { launch } from '@opys/installer';
|
|
76
|
+
|
|
77
|
+
const child = await launch('opys.json', {
|
|
78
|
+
vars: {
|
|
79
|
+
root: '/opt/minecraft/1.20.1',
|
|
80
|
+
username: 'Player',
|
|
81
|
+
uuid: '...',
|
|
82
|
+
token: '...',
|
|
83
|
+
},
|
|
84
|
+
install: { onProgress: (p) => console.log(p) },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await new Promise<void>((resolve, reject) => {
|
|
88
|
+
child.on('exit', (code) =>
|
|
89
|
+
code === 0 || code === null ? resolve() : reject(new Error(`exit ${code}`)),
|
|
90
|
+
);
|
|
91
|
+
child.on('error', reject);
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Options**
|
|
96
|
+
|
|
97
|
+
| Option | Type | Default | Description |
|
|
98
|
+
| ---------- | ------------------------- | ------- | -------------------------------------- |
|
|
99
|
+
| `platform` | `OsOptions` | auto | Override OS/arch detection |
|
|
100
|
+
| `vars` | `Record<string, string>` | `{}` | Extra vars; typically auth credentials |
|
|
101
|
+
| `install` | `InstallOptions \| false` | `{}` | Install options, or `false` to skip |
|
|
102
|
+
| `log` | `(level, msg) => void` | — | Debug/warn logger for spawn details |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## `resolveManifest(source)`
|
|
107
|
+
|
|
108
|
+
Resolves any manifest source to a `Manifest` object.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { resolveManifest } from '@opys/installer';
|
|
112
|
+
|
|
113
|
+
const manifest = await resolveManifest('opys.json');
|
|
114
|
+
console.log(manifest.artifacts.length);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## `currentPlatform()`
|
|
120
|
+
|
|
121
|
+
Returns the `OsOptions` for the current host.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { currentPlatform } from '@opys/installer';
|
|
125
|
+
|
|
126
|
+
const platform = currentPlatform();
|
|
127
|
+
// { name: 'linux', arch: 'x64', version: '...' }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Error types
|
|
133
|
+
|
|
134
|
+
| Class | When |
|
|
135
|
+
| ----------------- | ---------------------------------- |
|
|
136
|
+
| `NetworkError` | HTTP download failure |
|
|
137
|
+
| `IntegrityError` | Hash mismatch on a downloaded file |
|
|
138
|
+
| `ExtractionError` | ZIP extraction failure |
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let node_child_process = require("node:child_process");
|
|
30
|
+
let _opys_runtime_binding = require("@opys/runtime-binding");
|
|
31
|
+
_opys_runtime_binding = __toESM(_opys_runtime_binding);
|
|
32
|
+
|
|
33
|
+
//#region lib/index.ts
|
|
34
|
+
/**
|
|
35
|
+
* `@opys/runtime` — install + launch executor. Behaviors are backed by the
|
|
36
|
+
* Rust `opys-runtime` crate (via napi-rs); the TS surface wraps the
|
|
37
|
+
* binding with a Node `child_process.spawn` for `launch`, and translates
|
|
38
|
+
* the napi-thrown messages back into the typed `NetworkError` /
|
|
39
|
+
* `IntegrityError` / `ExtractionError` classes consumers still
|
|
40
|
+
* `instanceof`-check.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Compat error classes. The Rust bridge currently throws
|
|
44
|
+
* `napi::Error::from_reason(msg)` with the discriminant baked into the
|
|
45
|
+
* message ("HTTP …", "Integrity check failed: …", "Failed to extract …");
|
|
46
|
+
* `translateError` re-wraps those into these classes so consumers using
|
|
47
|
+
* `instanceof` keep working. Q10's `code`-discriminant model is the
|
|
48
|
+
* follow-up — when the Rust side emits structured errors, these classes
|
|
49
|
+
* either gain a `code` field or get replaced wholesale.
|
|
50
|
+
*/
|
|
51
|
+
var NetworkError = class extends Error {
|
|
52
|
+
kind = "network";
|
|
53
|
+
constructor(url, status, message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.url = url;
|
|
56
|
+
this.status = status;
|
|
57
|
+
this.name = "NetworkError";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var IntegrityError = class extends Error {
|
|
61
|
+
kind = "integrity";
|
|
62
|
+
constructor(paths) {
|
|
63
|
+
super(`Integrity check failed: ${paths.join(", ")}`);
|
|
64
|
+
this.paths = paths;
|
|
65
|
+
this.name = "IntegrityError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var ExtractionError = class extends Error {
|
|
69
|
+
kind = "extraction";
|
|
70
|
+
constructor(artifactPath, options) {
|
|
71
|
+
super(`Failed to extract ${artifactPath}`, options);
|
|
72
|
+
this.artifactPath = artifactPath;
|
|
73
|
+
this.name = "ExtractionError";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
function translateError(err) {
|
|
77
|
+
if (!(err instanceof Error)) return err;
|
|
78
|
+
const msg = err.message;
|
|
79
|
+
let m = /^HTTP (\d+) downloading (\S+)/.exec(msg);
|
|
80
|
+
if (m) return new NetworkError(m[2], Number(m[1]), msg);
|
|
81
|
+
m = /^Integrity check failed:\s*(.+)$/.exec(msg);
|
|
82
|
+
if (m) return new IntegrityError(m[1].split(",").map((s) => s.trim()).filter(Boolean));
|
|
83
|
+
m = /^Failed to extract (\S+):/.exec(msg);
|
|
84
|
+
if (m) return new ExtractionError(m[1], { cause: err });
|
|
85
|
+
return err;
|
|
86
|
+
}
|
|
87
|
+
async function install(manifest, options = {}) {
|
|
88
|
+
const { onProgress, ...rest } = options;
|
|
89
|
+
const bridge = onProgress ? (event) => onProgress(event) : void 0;
|
|
90
|
+
try {
|
|
91
|
+
await _opys_runtime_binding.install(manifest, rest, bridge);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw translateError(err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function buildLaunch(manifest, options = {}) {
|
|
97
|
+
try {
|
|
98
|
+
return await _opys_runtime_binding.buildLaunch(manifest, options);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
throw translateError(err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function launch(manifest, options = {}) {
|
|
104
|
+
const { install: installOpts = {}, cwd, ...launchRest } = options;
|
|
105
|
+
if (installOpts !== false) await install(manifest, installOpts);
|
|
106
|
+
const spec = await buildLaunch(manifest, {
|
|
107
|
+
...launchRest,
|
|
108
|
+
cwd
|
|
109
|
+
});
|
|
110
|
+
return (0, node_child_process.spawn)(spec.command, spec.args, {
|
|
111
|
+
cwd: spec.workdir,
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
...spec.envs
|
|
115
|
+
},
|
|
116
|
+
stdio: "inherit"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const currentPlatform = _opys_runtime_binding.currentPlatform;
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
exports.ExtractionError = ExtractionError;
|
|
123
|
+
exports.IntegrityError = IntegrityError;
|
|
124
|
+
exports.NetworkError = NetworkError;
|
|
125
|
+
exports.buildLaunch = buildLaunch;
|
|
126
|
+
exports.currentPlatform = currentPlatform;
|
|
127
|
+
exports.install = install;
|
|
128
|
+
exports.launch = launch;
|
|
129
|
+
exports.translateError = translateError;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ChildProcess } from "node:child_process";
|
|
2
|
+
import * as napi from "@opys/runtime-binding";
|
|
3
|
+
|
|
4
|
+
//#region lib/index.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Discriminated by `phase`. The Rust bridge populates only the fields
|
|
7
|
+
* relevant to each phase; the union encodes that contract so consumers can
|
|
8
|
+
* narrow on `phase` and access the populated fields without optional-chain
|
|
9
|
+
* dances.
|
|
10
|
+
*/
|
|
11
|
+
type InstallProgress = {
|
|
12
|
+
phase: 'resolve';
|
|
13
|
+
resolved: number;
|
|
14
|
+
} | {
|
|
15
|
+
phase: 'download';
|
|
16
|
+
fetched: number;
|
|
17
|
+
total: number;
|
|
18
|
+
skipped: number;
|
|
19
|
+
} | {
|
|
20
|
+
phase: 'download:start';
|
|
21
|
+
path: string;
|
|
22
|
+
total: number;
|
|
23
|
+
} | {
|
|
24
|
+
phase: 'download:bytes';
|
|
25
|
+
path: string;
|
|
26
|
+
bytes: number;
|
|
27
|
+
} | {
|
|
28
|
+
phase: 'download:done';
|
|
29
|
+
path: string;
|
|
30
|
+
} | {
|
|
31
|
+
phase: 'verify';
|
|
32
|
+
} | {
|
|
33
|
+
phase: 'extract';
|
|
34
|
+
count: number;
|
|
35
|
+
} | {
|
|
36
|
+
phase: 'sweep';
|
|
37
|
+
removed: number;
|
|
38
|
+
};
|
|
39
|
+
interface InstallOptions {
|
|
40
|
+
platform?: napi.OsOptions;
|
|
41
|
+
vars?: Record<string, string>;
|
|
42
|
+
concurrency?: number;
|
|
43
|
+
verifyIntegrity?: boolean;
|
|
44
|
+
features?: string[];
|
|
45
|
+
onProgress?: (p: InstallProgress) => void;
|
|
46
|
+
}
|
|
47
|
+
interface LaunchOptions {
|
|
48
|
+
platform?: napi.OsOptions;
|
|
49
|
+
features?: string[];
|
|
50
|
+
vars?: Record<string, string>;
|
|
51
|
+
cwd?: string;
|
|
52
|
+
install?: InstallOptions | false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Compat error classes. The Rust bridge currently throws
|
|
56
|
+
* `napi::Error::from_reason(msg)` with the discriminant baked into the
|
|
57
|
+
* message ("HTTP …", "Integrity check failed: …", "Failed to extract …");
|
|
58
|
+
* `translateError` re-wraps those into these classes so consumers using
|
|
59
|
+
* `instanceof` keep working. Q10's `code`-discriminant model is the
|
|
60
|
+
* follow-up — when the Rust side emits structured errors, these classes
|
|
61
|
+
* either gain a `code` field or get replaced wholesale.
|
|
62
|
+
*/
|
|
63
|
+
declare class NetworkError extends Error {
|
|
64
|
+
readonly url: string;
|
|
65
|
+
readonly status: number;
|
|
66
|
+
readonly kind: "network";
|
|
67
|
+
constructor(url: string, status: number, message: string);
|
|
68
|
+
}
|
|
69
|
+
declare class IntegrityError extends Error {
|
|
70
|
+
readonly paths: string[];
|
|
71
|
+
readonly kind: "integrity";
|
|
72
|
+
constructor(paths: string[]);
|
|
73
|
+
}
|
|
74
|
+
declare class ExtractionError extends Error {
|
|
75
|
+
readonly artifactPath: string;
|
|
76
|
+
readonly kind: "extraction";
|
|
77
|
+
constructor(artifactPath: string, options?: ErrorOptions);
|
|
78
|
+
}
|
|
79
|
+
type InstallError = NetworkError | IntegrityError | ExtractionError;
|
|
80
|
+
declare function translateError(err: unknown): unknown;
|
|
81
|
+
declare function install(manifest: unknown, options?: InstallOptions): Promise<void>;
|
|
82
|
+
declare function buildLaunch(manifest: unknown, options?: Omit<LaunchOptions, 'install'>): Promise<napi.LaunchSpec>;
|
|
83
|
+
declare function launch(manifest: unknown, options?: LaunchOptions): Promise<ChildProcess>;
|
|
84
|
+
declare const currentPlatform: any;
|
|
85
|
+
type OsOptions = napi.OsOptions;
|
|
86
|
+
type LaunchSpec = napi.LaunchSpec;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ExtractionError, InstallError, InstallOptions, InstallProgress, IntegrityError, LaunchOptions, LaunchSpec, NetworkError, OsOptions, buildLaunch, currentPlatform, install, launch, translateError };
|
|
89
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../lib/index.ts"],"mappings":";;;;;;;;;;KAkBY,eAAA;EACN,KAAA;EAAkB,QAAA;AAAA;EAClB,KAAA;EAAmB,OAAA;EAAiB,KAAA;EAAe,OAAA;AAAA;EACnD,KAAA;EAAyB,IAAA;EAAc,KAAA;AAAA;EACvC,KAAA;EAAyB,IAAA;EAAc,KAAA;AAAA;EACvC,KAAA;EAAwB,IAAA;AAAA;EACxB,KAAA;AAAA;EACA,KAAA;EAAkB,KAAA;AAAA;EAClB,KAAA;EAAgB,OAAA;AAAA;AAAA,UAEL,cAAA;EACf,QAAA,GAAW,IAAA,CAAK,SAAA;EAChB,IAAA,GAAO,MAAA;EACP,WAAA;EACA,eAAA;EACA,QAAA;EACA,UAAA,IAAc,CAAA,EAAG,eAAA;AAAA;AAAA,UAGF,aAAA;EACf,QAAA,GAAW,IAAA,CAAK,SAAA;EAChB,QAAA;EACA,IAAA,GAAO,MAAA;EACP,GAAA;EACA,OAAA,GAAU,cAAA;AAAA;;;;;;;;;;cAYC,YAAA,SAAqB,KAAA;EAAA,SAGrB,GAAA;EAAA,SACA,MAAA;EAAA,SAHF,IAAA;cAEE,GAAA,UACA,MAAA,UACT,OAAA;AAAA;AAAA,cAMS,cAAA,SAAuB,KAAA;EAAA,SAEb,KAAA;EAAA,SADZ,IAAA;cACY,KAAA;AAAA;AAAA,cAKV,eAAA,SAAwB,KAAA;EAAA,SAGxB,YAAA;EAAA,SAFF,IAAA;cAEE,YAAA,UACT,OAAA,GAAU,YAAA;AAAA;AAAA,KAMF,YAAA,GAAe,YAAA,GAAe,cAAA,GAAiB,eAAA;AAAA,iBAE3C,cAAA,CAAe,GAAA;AAAA,iBAkBT,OAAA,CACpB,QAAA,WACA,OAAA,GAAS,cAAA,GACR,OAAA;AAAA,iBAYmB,WAAA,CACpB,QAAA,WACA,OAAA,GAAS,IAAA,CAAK,aAAA,eACb,OAAA,CAAQ,IAAA,CAAK,UAAA;AAAA,iBAQM,MAAA,CACpB,QAAA,WACA,OAAA,GAAS,aAAA,GACR,OAAA,CAAQ,YAAA;AAAA,cAaE,eAAA;AAAA,KACD,SAAA,GAAY,IAAA,CAAK,SAAA;AAAA,KACjB,UAAA,GAAa,IAAA,CAAK,UAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ChildProcess } from "node:child_process";
|
|
2
|
+
import * as napi from "@opys/runtime-binding";
|
|
3
|
+
|
|
4
|
+
//#region lib/index.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Discriminated by `phase`. The Rust bridge populates only the fields
|
|
7
|
+
* relevant to each phase; the union encodes that contract so consumers can
|
|
8
|
+
* narrow on `phase` and access the populated fields without optional-chain
|
|
9
|
+
* dances.
|
|
10
|
+
*/
|
|
11
|
+
type InstallProgress = {
|
|
12
|
+
phase: 'resolve';
|
|
13
|
+
resolved: number;
|
|
14
|
+
} | {
|
|
15
|
+
phase: 'download';
|
|
16
|
+
fetched: number;
|
|
17
|
+
total: number;
|
|
18
|
+
skipped: number;
|
|
19
|
+
} | {
|
|
20
|
+
phase: 'download:start';
|
|
21
|
+
path: string;
|
|
22
|
+
total: number;
|
|
23
|
+
} | {
|
|
24
|
+
phase: 'download:bytes';
|
|
25
|
+
path: string;
|
|
26
|
+
bytes: number;
|
|
27
|
+
} | {
|
|
28
|
+
phase: 'download:done';
|
|
29
|
+
path: string;
|
|
30
|
+
} | {
|
|
31
|
+
phase: 'verify';
|
|
32
|
+
} | {
|
|
33
|
+
phase: 'extract';
|
|
34
|
+
count: number;
|
|
35
|
+
} | {
|
|
36
|
+
phase: 'sweep';
|
|
37
|
+
removed: number;
|
|
38
|
+
};
|
|
39
|
+
interface InstallOptions {
|
|
40
|
+
platform?: napi.OsOptions;
|
|
41
|
+
vars?: Record<string, string>;
|
|
42
|
+
concurrency?: number;
|
|
43
|
+
verifyIntegrity?: boolean;
|
|
44
|
+
features?: string[];
|
|
45
|
+
onProgress?: (p: InstallProgress) => void;
|
|
46
|
+
}
|
|
47
|
+
interface LaunchOptions {
|
|
48
|
+
platform?: napi.OsOptions;
|
|
49
|
+
features?: string[];
|
|
50
|
+
vars?: Record<string, string>;
|
|
51
|
+
cwd?: string;
|
|
52
|
+
install?: InstallOptions | false;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Compat error classes. The Rust bridge currently throws
|
|
56
|
+
* `napi::Error::from_reason(msg)` with the discriminant baked into the
|
|
57
|
+
* message ("HTTP …", "Integrity check failed: …", "Failed to extract …");
|
|
58
|
+
* `translateError` re-wraps those into these classes so consumers using
|
|
59
|
+
* `instanceof` keep working. Q10's `code`-discriminant model is the
|
|
60
|
+
* follow-up — when the Rust side emits structured errors, these classes
|
|
61
|
+
* either gain a `code` field or get replaced wholesale.
|
|
62
|
+
*/
|
|
63
|
+
declare class NetworkError extends Error {
|
|
64
|
+
readonly url: string;
|
|
65
|
+
readonly status: number;
|
|
66
|
+
readonly kind: "network";
|
|
67
|
+
constructor(url: string, status: number, message: string);
|
|
68
|
+
}
|
|
69
|
+
declare class IntegrityError extends Error {
|
|
70
|
+
readonly paths: string[];
|
|
71
|
+
readonly kind: "integrity";
|
|
72
|
+
constructor(paths: string[]);
|
|
73
|
+
}
|
|
74
|
+
declare class ExtractionError extends Error {
|
|
75
|
+
readonly artifactPath: string;
|
|
76
|
+
readonly kind: "extraction";
|
|
77
|
+
constructor(artifactPath: string, options?: ErrorOptions);
|
|
78
|
+
}
|
|
79
|
+
type InstallError = NetworkError | IntegrityError | ExtractionError;
|
|
80
|
+
declare function translateError(err: unknown): unknown;
|
|
81
|
+
declare function install(manifest: unknown, options?: InstallOptions): Promise<void>;
|
|
82
|
+
declare function buildLaunch(manifest: unknown, options?: Omit<LaunchOptions, 'install'>): Promise<napi.LaunchSpec>;
|
|
83
|
+
declare function launch(manifest: unknown, options?: LaunchOptions): Promise<ChildProcess>;
|
|
84
|
+
declare const currentPlatform: any;
|
|
85
|
+
type OsOptions = napi.OsOptions;
|
|
86
|
+
type LaunchSpec = napi.LaunchSpec;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ExtractionError, InstallError, InstallOptions, InstallProgress, IntegrityError, LaunchOptions, LaunchSpec, NetworkError, OsOptions, buildLaunch, currentPlatform, install, launch, translateError };
|
|
89
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../lib/index.ts"],"mappings":";;;;;;;;;;KAkBY,eAAA;EACN,KAAA;EAAkB,QAAA;AAAA;EAClB,KAAA;EAAmB,OAAA;EAAiB,KAAA;EAAe,OAAA;AAAA;EACnD,KAAA;EAAyB,IAAA;EAAc,KAAA;AAAA;EACvC,KAAA;EAAyB,IAAA;EAAc,KAAA;AAAA;EACvC,KAAA;EAAwB,IAAA;AAAA;EACxB,KAAA;AAAA;EACA,KAAA;EAAkB,KAAA;AAAA;EAClB,KAAA;EAAgB,OAAA;AAAA;AAAA,UAEL,cAAA;EACf,QAAA,GAAW,IAAA,CAAK,SAAA;EAChB,IAAA,GAAO,MAAA;EACP,WAAA;EACA,eAAA;EACA,QAAA;EACA,UAAA,IAAc,CAAA,EAAG,eAAA;AAAA;AAAA,UAGF,aAAA;EACf,QAAA,GAAW,IAAA,CAAK,SAAA;EAChB,QAAA;EACA,IAAA,GAAO,MAAA;EACP,GAAA;EACA,OAAA,GAAU,cAAA;AAAA;;;;;;;;;;cAYC,YAAA,SAAqB,KAAA;EAAA,SAGrB,GAAA;EAAA,SACA,MAAA;EAAA,SAHF,IAAA;cAEE,GAAA,UACA,MAAA,UACT,OAAA;AAAA;AAAA,cAMS,cAAA,SAAuB,KAAA;EAAA,SAEb,KAAA;EAAA,SADZ,IAAA;cACY,KAAA;AAAA;AAAA,cAKV,eAAA,SAAwB,KAAA;EAAA,SAGxB,YAAA;EAAA,SAFF,IAAA;cAEE,YAAA,UACT,OAAA,GAAU,YAAA;AAAA;AAAA,KAMF,YAAA,GAAe,YAAA,GAAe,cAAA,GAAiB,eAAA;AAAA,iBAE3C,cAAA,CAAe,GAAA;AAAA,iBAkBT,OAAA,CACpB,QAAA,WACA,OAAA,GAAS,cAAA,GACR,OAAA;AAAA,iBAYmB,WAAA,CACpB,QAAA,WACA,OAAA,GAAS,IAAA,CAAK,aAAA,eACb,OAAA,CAAQ,IAAA,CAAK,UAAA;AAAA,iBAQM,MAAA,CACpB,QAAA,WACA,OAAA,GAAS,aAAA,GACR,OAAA,CAAQ,YAAA;AAAA,cAaE,eAAA;AAAA,KACD,SAAA,GAAY,IAAA,CAAK,SAAA;AAAA,KACjB,UAAA,GAAa,IAAA,CAAK,UAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import * as napi from "@opys/runtime-binding";
|
|
3
|
+
|
|
4
|
+
//#region lib/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* `@opys/runtime` — install + launch executor. Behaviors are backed by the
|
|
7
|
+
* Rust `opys-runtime` crate (via napi-rs); the TS surface wraps the
|
|
8
|
+
* binding with a Node `child_process.spawn` for `launch`, and translates
|
|
9
|
+
* the napi-thrown messages back into the typed `NetworkError` /
|
|
10
|
+
* `IntegrityError` / `ExtractionError` classes consumers still
|
|
11
|
+
* `instanceof`-check.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Compat error classes. The Rust bridge currently throws
|
|
15
|
+
* `napi::Error::from_reason(msg)` with the discriminant baked into the
|
|
16
|
+
* message ("HTTP …", "Integrity check failed: …", "Failed to extract …");
|
|
17
|
+
* `translateError` re-wraps those into these classes so consumers using
|
|
18
|
+
* `instanceof` keep working. Q10's `code`-discriminant model is the
|
|
19
|
+
* follow-up — when the Rust side emits structured errors, these classes
|
|
20
|
+
* either gain a `code` field or get replaced wholesale.
|
|
21
|
+
*/
|
|
22
|
+
var NetworkError = class extends Error {
|
|
23
|
+
kind = "network";
|
|
24
|
+
constructor(url, status, message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.url = url;
|
|
27
|
+
this.status = status;
|
|
28
|
+
this.name = "NetworkError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var IntegrityError = class extends Error {
|
|
32
|
+
kind = "integrity";
|
|
33
|
+
constructor(paths) {
|
|
34
|
+
super(`Integrity check failed: ${paths.join(", ")}`);
|
|
35
|
+
this.paths = paths;
|
|
36
|
+
this.name = "IntegrityError";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var ExtractionError = class extends Error {
|
|
40
|
+
kind = "extraction";
|
|
41
|
+
constructor(artifactPath, options) {
|
|
42
|
+
super(`Failed to extract ${artifactPath}`, options);
|
|
43
|
+
this.artifactPath = artifactPath;
|
|
44
|
+
this.name = "ExtractionError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function translateError(err) {
|
|
48
|
+
if (!(err instanceof Error)) return err;
|
|
49
|
+
const msg = err.message;
|
|
50
|
+
let m = /^HTTP (\d+) downloading (\S+)/.exec(msg);
|
|
51
|
+
if (m) return new NetworkError(m[2], Number(m[1]), msg);
|
|
52
|
+
m = /^Integrity check failed:\s*(.+)$/.exec(msg);
|
|
53
|
+
if (m) return new IntegrityError(m[1].split(",").map((s) => s.trim()).filter(Boolean));
|
|
54
|
+
m = /^Failed to extract (\S+):/.exec(msg);
|
|
55
|
+
if (m) return new ExtractionError(m[1], { cause: err });
|
|
56
|
+
return err;
|
|
57
|
+
}
|
|
58
|
+
async function install(manifest, options = {}) {
|
|
59
|
+
const { onProgress, ...rest } = options;
|
|
60
|
+
const bridge = onProgress ? (event) => onProgress(event) : void 0;
|
|
61
|
+
try {
|
|
62
|
+
await napi.install(manifest, rest, bridge);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
throw translateError(err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function buildLaunch(manifest, options = {}) {
|
|
68
|
+
try {
|
|
69
|
+
return await napi.buildLaunch(manifest, options);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
throw translateError(err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function launch(manifest, options = {}) {
|
|
75
|
+
const { install: installOpts = {}, cwd, ...launchRest } = options;
|
|
76
|
+
if (installOpts !== false) await install(manifest, installOpts);
|
|
77
|
+
const spec = await buildLaunch(manifest, {
|
|
78
|
+
...launchRest,
|
|
79
|
+
cwd
|
|
80
|
+
});
|
|
81
|
+
return spawn(spec.command, spec.args, {
|
|
82
|
+
cwd: spec.workdir,
|
|
83
|
+
env: {
|
|
84
|
+
...process.env,
|
|
85
|
+
...spec.envs
|
|
86
|
+
},
|
|
87
|
+
stdio: "inherit"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const currentPlatform = napi.currentPlatform;
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
export { ExtractionError, IntegrityError, NetworkError, buildLaunch, currentPlatform, install, launch, translateError };
|
|
94
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../lib/index.ts"],"sourcesContent":["/**\n * `@opys/runtime` — install + launch executor. Behaviors are backed by the\n * Rust `opys-runtime` crate (via napi-rs); the TS surface wraps the\n * binding with a Node `child_process.spawn` for `launch`, and translates\n * the napi-thrown messages back into the typed `NetworkError` /\n * `IntegrityError` / `ExtractionError` classes consumers still\n * `instanceof`-check.\n */\n\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport * as napi from '@opys/runtime-binding';\n\n/**\n * Discriminated by `phase`. The Rust bridge populates only the fields\n * relevant to each phase; the union encodes that contract so consumers can\n * narrow on `phase` and access the populated fields without optional-chain\n * dances.\n */\nexport type InstallProgress =\n | { phase: 'resolve'; resolved: number }\n | { phase: 'download'; fetched: number; total: number; skipped: number }\n | { phase: 'download:start'; path: string; total: number }\n | { phase: 'download:bytes'; path: string; bytes: number }\n | { phase: 'download:done'; path: string }\n | { phase: 'verify' }\n | { phase: 'extract'; count: number }\n | { phase: 'sweep'; removed: number };\n\nexport interface InstallOptions {\n platform?: napi.OsOptions;\n vars?: Record<string, string>;\n concurrency?: number;\n verifyIntegrity?: boolean;\n features?: string[];\n onProgress?: (p: InstallProgress) => void;\n}\n\nexport interface LaunchOptions {\n platform?: napi.OsOptions;\n features?: string[];\n vars?: Record<string, string>;\n cwd?: string;\n install?: InstallOptions | false;\n}\n\n/**\n * Compat error classes. The Rust bridge currently throws\n * `napi::Error::from_reason(msg)` with the discriminant baked into the\n * message (\"HTTP …\", \"Integrity check failed: …\", \"Failed to extract …\");\n * `translateError` re-wraps those into these classes so consumers using\n * `instanceof` keep working. Q10's `code`-discriminant model is the\n * follow-up — when the Rust side emits structured errors, these classes\n * either gain a `code` field or get replaced wholesale.\n */\nexport class NetworkError extends Error {\n readonly kind = 'network' as const;\n constructor(\n readonly url: string,\n readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = 'NetworkError';\n }\n}\nexport class IntegrityError extends Error {\n readonly kind = 'integrity' as const;\n constructor(readonly paths: string[]) {\n super(`Integrity check failed: ${paths.join(', ')}`);\n this.name = 'IntegrityError';\n }\n}\nexport class ExtractionError extends Error {\n readonly kind = 'extraction' as const;\n constructor(\n readonly artifactPath: string,\n options?: ErrorOptions,\n ) {\n super(`Failed to extract ${artifactPath}`, options);\n this.name = 'ExtractionError';\n }\n}\nexport type InstallError = NetworkError | IntegrityError | ExtractionError;\n\nexport function translateError(err: unknown): unknown {\n if (!(err instanceof Error)) return err;\n const msg = err.message;\n let m = /^HTTP (\\d+) downloading (\\S+)/.exec(msg);\n if (m) return new NetworkError(m[2]!, Number(m[1]!), msg);\n m = /^Integrity check failed:\\s*(.+)$/.exec(msg);\n if (m) {\n const paths = m[1]!\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n return new IntegrityError(paths);\n }\n m = /^Failed to extract (\\S+):/.exec(msg);\n if (m) return new ExtractionError(m[1]!, { cause: err });\n return err;\n}\n\nexport async function install(\n manifest: unknown,\n options: InstallOptions = {},\n): Promise<void> {\n const { onProgress, ...rest } = options;\n const bridge = onProgress\n ? (event: unknown) => onProgress(event as InstallProgress)\n : undefined;\n try {\n await napi.install(manifest, rest, bridge);\n } catch (err) {\n throw translateError(err);\n }\n}\n\nexport async function buildLaunch(\n manifest: unknown,\n options: Omit<LaunchOptions, 'install'> = {},\n): Promise<napi.LaunchSpec> {\n try {\n return await napi.buildLaunch(manifest, options);\n } catch (err) {\n throw translateError(err);\n }\n}\n\nexport async function launch(\n manifest: unknown,\n options: LaunchOptions = {},\n): Promise<ChildProcess> {\n const { install: installOpts = {}, cwd, ...launchRest } = options;\n if (installOpts !== false) {\n await install(manifest, installOpts);\n }\n const spec = await buildLaunch(manifest, { ...launchRest, cwd });\n return spawn(spec.command, spec.args, {\n cwd: spec.workdir,\n env: { ...process.env, ...spec.envs },\n stdio: 'inherit',\n });\n}\n\nexport const currentPlatform = napi.currentPlatform;\nexport type OsOptions = napi.OsOptions;\nexport type LaunchSpec = napi.LaunchSpec;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,eAAb,cAAkC,MAAM;CACtC,AAAS,OAAO;CAChB,YACE,AAAS,KACT,AAAS,QACT,SACA;AACA,QAAM,QAAQ;EAJL;EACA;AAIT,OAAK,OAAO;;;AAGhB,IAAa,iBAAb,cAAoC,MAAM;CACxC,AAAS,OAAO;CAChB,YAAY,AAAS,OAAiB;AACpC,QAAM,2BAA2B,MAAM,KAAK,KAAK,GAAG;EADjC;AAEnB,OAAK,OAAO;;;AAGhB,IAAa,kBAAb,cAAqC,MAAM;CACzC,AAAS,OAAO;CAChB,YACE,AAAS,cACT,SACA;AACA,QAAM,qBAAqB,gBAAgB,QAAQ;EAH1C;AAIT,OAAK,OAAO;;;AAKhB,SAAgB,eAAe,KAAuB;AACpD,KAAI,EAAE,eAAe,OAAQ,QAAO;CACpC,MAAM,MAAM,IAAI;CAChB,IAAI,IAAI,gCAAgC,KAAK,IAAI;AACjD,KAAI,EAAG,QAAO,IAAI,aAAa,EAAE,IAAK,OAAO,EAAE,GAAI,EAAE,IAAI;AACzD,KAAI,mCAAmC,KAAK,IAAI;AAChD,KAAI,EAKF,QAAO,IAAI,eAJG,EAAE,GACb,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ,CACc;AAElC,KAAI,4BAA4B,KAAK,IAAI;AACzC,KAAI,EAAG,QAAO,IAAI,gBAAgB,EAAE,IAAK,EAAE,OAAO,KAAK,CAAC;AACxD,QAAO;;AAGT,eAAsB,QACpB,UACA,UAA0B,EAAE,EACb;CACf,MAAM,EAAE,YAAY,GAAG,SAAS;CAChC,MAAM,SAAS,cACV,UAAmB,WAAW,MAAyB,GACxD;AACJ,KAAI;AACF,QAAM,KAAK,QAAQ,UAAU,MAAM,OAAO;UACnC,KAAK;AACZ,QAAM,eAAe,IAAI;;;AAI7B,eAAsB,YACpB,UACA,UAA0C,EAAE,EAClB;AAC1B,KAAI;AACF,SAAO,MAAM,KAAK,YAAY,UAAU,QAAQ;UACzC,KAAK;AACZ,QAAM,eAAe,IAAI;;;AAI7B,eAAsB,OACpB,UACA,UAAyB,EAAE,EACJ;CACvB,MAAM,EAAE,SAAS,cAAc,EAAE,EAAE,KAAK,GAAG,eAAe;AAC1D,KAAI,gBAAgB,MAClB,OAAM,QAAQ,UAAU,YAAY;CAEtC,MAAM,OAAO,MAAM,YAAY,UAAU;EAAE,GAAG;EAAY;EAAK,CAAC;AAChE,QAAO,MAAM,KAAK,SAAS,KAAK,MAAM;EACpC,KAAK,KAAK;EACV,KAAK;GAAE,GAAG,QAAQ;GAAK,GAAG,KAAK;GAAM;EACrC,OAAO;EACR,CAAC;;AAGJ,MAAa,kBAAkB,KAAK"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opys/runtime",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.mjs",
|
|
9
|
+
"require": "./dist/index.cjs"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsdown lib/index.ts --format esm,cjs --dts --clean",
|
|
17
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
18
|
+
"test": "vitest run tests/unit"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@opys/core": "^0.1.2",
|
|
23
|
+
"@opys/runtime-binding": "^0.1.2"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/harmoniya-net/opys.git",
|
|
31
|
+
"directory": "packages/runtime"
|
|
32
|
+
}
|
|
33
|
+
}
|