@damurka/jovian 0.1.0 → 0.1.1
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 +6 -10
- package/docs/api/types.md +2 -2
- package/docs/getting-started.md +2 -2
- package/docs/releasing.md +18 -2
- package/docs/troubleshooting.md +5 -1
- package/lib/session/r-setup.d.ts +46 -0
- package/lib/session/r-setup.js +207 -0
- package/lib/session/runtimes.d.ts +29 -0
- package/lib/session/runtimes.js +80 -0
- package/lib/session/session-manager.d.ts +4 -3
- package/lib/session/session-manager.js +14 -3
- package/lib/types/engine.d.ts +37 -36
- package/package.json +4 -4
- package/packages/hera/DESCRIPTION +1 -1
package/README.md
CHANGED
|
@@ -9,11 +9,11 @@ import { SessionManager } from '@damurka/jovian';
|
|
|
9
9
|
|
|
10
10
|
const manager = new SessionManager();
|
|
11
11
|
|
|
12
|
-
const r = await manager.createSession({ kernelType: 'r',
|
|
12
|
+
const r = await manager.createSession({ kernelType: 'r', workingDirectory: '/projects/analysis' });
|
|
13
13
|
const result = await r.execute('x <- 1:10; mean(x)');
|
|
14
14
|
console.log(result.success, result.output);
|
|
15
15
|
|
|
16
|
-
const py = await manager.createSession({ kernelType: 'python'
|
|
16
|
+
const py = await manager.createSession({ kernelType: 'python' });
|
|
17
17
|
console.log((await py.execute('sum(range(1, 11))')).success);
|
|
18
18
|
|
|
19
19
|
await manager.stopAll();
|
|
@@ -40,13 +40,9 @@ The package ships **prebuilt** `themisto`, `elara` and `carpo` binaries — no c
|
|
|
40
40
|
|
|
41
41
|
What you must already have on the machine:
|
|
42
42
|
|
|
43
|
-
- **R** (4.2 or newer; a build with a shared library, which the CRAN/Posit binaries and distribution packages are) for R sessions.
|
|
43
|
+
- **R** (4.2 or newer; a build with a shared library, which the CRAN/Posit binaries and distribution packages are) for R sessions. If `rHome` is not passed, it is found from `$R_HOME`, then `R RHOME` (R on `PATH`), then the Windows registry; pass `rHome` to choose a specific installation. You do not need to install any R packages yourself. The `hera` R package every R session needs ships inside the npm package, and before the **first** R session the library installs it, together with its CRAN dependencies (`cli`, `evaluate`, `glue`, `IRdisplay`, `jsonlite`, `R6`, `repr`, `rlang` and what they need), into your R library. Nothing else (not even `remotes`) has to be installed first. This needs an internet connection, takes about 20 seconds where CRAN has binaries (Windows, macOS) and a few minutes on Linux, where packages are compiled from source and need a compiler (Ubuntu: `sudo apt install build-essential`). It happens once; later sessions start straight away. A package that is installed but cannot be loaded (Debian/Ubuntu `r-cran-*` packages built for an older R fail with `undefined symbol: SETLENGTH`) is reinstalled from CRAN into your own library. If it cannot finish, `createSession()` rejects with R's own reason. Set `JOVIAN_SKIP_R_SETUP=1` to skip this step and manage the packages yourself.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
install.packages(c("remotes", "cli", "evaluate", "glue", "IRdisplay", "jsonlite", "R6", "repr", "rlang"))
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
- **Python 3** with its shared library (optional, for Python sessions); pass `pythonHome` (`python3 -c "import sys; print(sys.prefix)"`).
|
|
45
|
+
- **Python 3** with its shared library (optional, for Python sessions); if `pythonHome` is not passed, it is found from `$PYTHONHOME`, then the first `python3` / `python` on `PATH` (its `sys.base_prefix`); pass `pythonHome` to choose one.
|
|
50
46
|
- **Linux:** `libuuid` (`libuuid1`, present on nearly every system) and a glibc at least as new as the one the binaries were built against (Ubuntu 24.04's, 2.39). On an older distribution, [build from source](#requirements).
|
|
51
47
|
- **macOS:** 14 or newer.
|
|
52
48
|
- **Windows:** the [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist) (x64, 2015–2022) — the binaries use the dynamic C++ runtime; most machines already have it.
|
|
@@ -117,9 +113,9 @@ Jovian builds C++ (Adrastea, Elara, Carpo, Themisto) and TypeScript. R and Pytho
|
|
|
117
113
|
| Variable | Used by | Meaning |
|
|
118
114
|
|---|---|---|
|
|
119
115
|
| `VCPKG_ROOT` | build | vcpkg checkout; used by `npm run build` and the CMake presets. |
|
|
120
|
-
| `R_HOME` | runtime, tests, examples | R installation to use when `rHome` is not passed
|
|
116
|
+
| `R_HOME` | runtime, tests, examples | R installation to use when `rHome` is not passed; otherwise the library asks `R RHOME`. |
|
|
121
117
|
| `R_PATH`, `R_LIBS` | examples, playground | Passed as `rPath` / `rLibs`. |
|
|
122
|
-
| `PYTHONHOME` | runtime, tests | Python installation prefix when `pythonHome` is not passed. |
|
|
118
|
+
| `PYTHONHOME` | runtime, tests | Python installation prefix when `pythonHome` is not passed; otherwise the library asks `python3` / `python` for its `sys.base_prefix`. |
|
|
123
119
|
| `JOVIAN_NATIVE_DIR` | `lib/` | Directory holding `themisto`, `elara` and `carpo`. Default: the installed `@damurka/jovian-<os>-<cpu>` package, else `dist/native/Release` in a source checkout. Use it to run against a *copy* of the binaries (Windows will not let you overwrite a running `.exe`). |
|
|
124
120
|
| `ELARA_HERA_SRC` | Elara | Set for you from the `heraSrcPath` option: where Elara installs `hera` from if it is missing or older than the source. |
|
|
125
121
|
|
package/docs/api/types.md
CHANGED
|
@@ -9,12 +9,12 @@ Passed to `SessionManager.createSession()`; also `Partial<EngineOptions>` to `Se
|
|
|
9
9
|
| Field | Type | Default | Meaning |
|
|
10
10
|
|---|---|---|---|
|
|
11
11
|
| `kernelType` | `'r'` \| `'python'` | `'r'` | Which kernel the session runs. Selects which of the fields below apply and which executable (`elara` / `carpo`) is spawned. |
|
|
12
|
-
| `rHome` | string |
|
|
12
|
+
| `rHome` | string | discovered | R installation (`R RHOME`). When omitted it is found from `$R_HOME`, then `R RHOME` (R on PATH), then the Windows registry; pass it to pick a specific installation. |
|
|
13
13
|
| `rPath` | string | `<rHome>/bin/x64` (Windows) | Directory containing `R.dll`; put on the kernel's `PATH`. |
|
|
14
14
|
| `rLibs` | string | — | Extra library path (`R_LIBS`, `R_LIBS_USER`, and `R_LIBS_SITE` on Windows); where `hera` is looked up and installed. |
|
|
15
15
|
| `pandocPath` | string | — | Directory with a `pandoc` binary, for bundled R installs that do not ship it on `PATH` (`RSTUDIO_PANDOC`). |
|
|
16
16
|
| `heraSrcPath` | string | — | Source directory of the `hera` package. If set, Elara installs it (`remotes::install_local`, needs `remotes`) when it is missing or older than the source. **There is no built-in default**: if `hera` is not installed and this is unset, R code cannot run (see [Troubleshooting](../troubleshooting.md#hera-is-not-installed)). |
|
|
17
|
-
| `pythonHome` | string |
|
|
17
|
+
| `pythonHome` | string | discovered | Python installation prefix (`PYTHONHOME`). Its shared library is loaded from here. When omitted it is found from `$PYTHONHOME`, then `python3` / `python` on PATH (`sys.base_prefix`). |
|
|
18
18
|
| `pythonPath` | string | — | Extra `PYTHONPATH`. |
|
|
19
19
|
| `venvPath` | string | — | A venv whose `site-packages` is added to `sys.path`. `pythonHome` must still point at the *base* install. |
|
|
20
20
|
| `workingDirectory` | string | supervisor's cwd | Directory the kernel process starts in (`getwd()` / `os.getcwd()`); relative paths resolve against it. Must exist, or `createSession()` rejects with `workingDirectory does not exist or is not a directory: <path>`. Kept across `restart()`. |
|
package/docs/getting-started.md
CHANGED
|
@@ -55,7 +55,7 @@ import { SessionManager } from './dist/lib/index.js';
|
|
|
55
55
|
const manager = new SessionManager();
|
|
56
56
|
const session = await manager.createSession({
|
|
57
57
|
kernelType: 'r',
|
|
58
|
-
rHome: process.env.R_HOME, //
|
|
58
|
+
rHome: process.env.R_HOME, // optional: found from $R_HOME / `R RHOME` / the Windows registry when omitted
|
|
59
59
|
rPath: process.env.R_PATH, // Windows only: e.g. "C:/Program Files/R/R-4.6.0/bin/x64"
|
|
60
60
|
workingDirectory: process.cwd(), // where getwd() will point
|
|
61
61
|
});
|
|
@@ -78,7 +78,7 @@ await manager.stopAll(); // always: it also end
|
|
|
78
78
|
```javascript
|
|
79
79
|
const py = await manager.createSession({
|
|
80
80
|
kernelType: 'python',
|
|
81
|
-
pythonHome: process.env.PYTHONHOME, //
|
|
81
|
+
pythonHome: process.env.PYTHONHOME, // optional: found from $PYTHONHOME / python3 / python when omitted (the prefix containing libpython)
|
|
82
82
|
workingDirectory: process.cwd(),
|
|
83
83
|
});
|
|
84
84
|
py.on('error', () => {});
|
package/docs/releasing.md
CHANGED
|
@@ -15,10 +15,25 @@ The repository's own `package.json` is `"private": true` — it is the developme
|
|
|
15
15
|
## One-time setup
|
|
16
16
|
|
|
17
17
|
1. **The scope.** `@damurka` must be a user or organization you can publish to on npmjs.com. If you use a different scope, change it in two places — `SCOPE` in `scripts/release.mjs` and `PACKAGE_SCOPE` in `lib/session/native-paths.ts` (a unit test fails if they differ) — plus the names in `README.md`, `docs/`, and the tarball globs in `.github/workflows/release.yml`.
|
|
18
|
-
2. **A token.** On npmjs.com create an *automation* access token (or a granular token with read/write on the `@damurka` packages) and add it to the GitHub repository as the secret **`NPM_TOKEN`** (Settings → Secrets and variables → Actions).
|
|
18
|
+
2. **A token.** On npmjs.com create an *automation* access token (or a granular token with read/write on the `@damurka` packages) and add it to the GitHub repository as the secret **`NPM_TOKEN`** (Settings → Secrets and variables → Actions). Staging needs no 2FA and works with any token type (npm also offers stage-only tokens, which cannot publish directly); the 2FA happens when you approve. A token that still asks for a one-time password on a direct `npm publish` fails in CI with `EOTP`.
|
|
19
19
|
3. Scoped packages are private by default on npm; the packages carry `publishConfig.access: public`, and the workflow passes `--access public`.
|
|
20
20
|
4. The workflow publishes with **provenance** (`--provenance`, needs the `id-token: write` permission it declares), which requires the GitHub repository to be public.
|
|
21
21
|
|
|
22
|
+
## The first release is published by hand
|
|
23
|
+
|
|
24
|
+
npm's staged publishing (below) only works for a package that **already exists** on the registry; a first-time publish of a new package cannot be staged. So the very first version (`0.1.0`) is published from your machine, where npm can ask for your 2FA code:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm login
|
|
28
|
+
# the tarballs CI built and smoke-tested (download them from the Release run's artifacts, or `gh run download <run-id>`):
|
|
29
|
+
npm publish ./tarballs-win32-x64/damurka-jovian-win32-x64-0.1.0.tgz --access public
|
|
30
|
+
npm publish ./tarballs-linux-x64/damurka-jovian-linux-x64-0.1.0.tgz --access public
|
|
31
|
+
npm publish ./tarballs-darwin-arm64/damurka-jovian-darwin-arm64-0.1.0.tgz --access public
|
|
32
|
+
npm publish ./tarballs-linux-x64/damurka-jovian-0.1.0.tgz --access public # last: it depends on the three above
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The main package goes last so nothing depends on a version that is not there yet. This first version carries no provenance badge. From the next version on, use the workflow.
|
|
36
|
+
|
|
22
37
|
## Cutting a release
|
|
23
38
|
|
|
24
39
|
1. Make sure `main` is green on CI.
|
|
@@ -31,7 +46,8 @@ The repository's own `package.json` is `"private": true` — it is the developme
|
|
|
31
46
|
```
|
|
32
47
|
|
|
33
48
|
4. `release.yml` runs. For each platform it builds the native binaries in Release, compiles the library, stages both packages, packs them, and **smoke-tests the packed tarballs**: `scripts/release-smoke.mjs` installs the platform tarball and the main tarball into an empty project (outside the repository, with an empty R library so the bundled `hera` has to install from the package) and starts a real R kernel and a real Python kernel from them.
|
|
34
|
-
5. Only if every platform passed does the `publish` job run
|
|
49
|
+
5. Only if every platform passed does the `publish` job run. It **stages** the packages with `npm stage publish` (npm requires this for CI; needs npm ≥ 11.15, which the job installs), platform packages first, then the main package. Nothing is public yet.
|
|
50
|
+
6. **Approve them**, with 2FA, in the same order: `npm stage list` shows the queue, `npm stage approve <stage-id>` publishes one (or approve on npmjs.com). Approve the main package last. `npm stage reject <stage-id>` discards one.
|
|
35
51
|
|
|
36
52
|
A version with a hyphen (`v0.2.0-rc.1`) is published under the `next` dist-tag, so it does not become what `npm install` picks by default.
|
|
37
53
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -35,7 +35,11 @@ Messages below are quoted from the code. **First habit:** a kernel that fails to
|
|
|
35
35
|
| Symptom | Cause / fix |
|
|
36
36
|
|---|---|
|
|
37
37
|
| `Could not load R.dll (…). Is R installed? Checked PATH and R_HOME=… Install R from https://cran.r-project.org, or make sure R_HOME/the R bin directory is configured correctly.` | Windows: `rHome` wrong, or `rPath` (the folder containing `R.dll`, normally `<R_HOME>\bin\x64`) is not right. |
|
|
38
|
-
| `R_HOME is not set -- elara needs a working R installation to run. …` |
|
|
38
|
+
| `R_HOME is not set -- elara needs a working R installation to run. …` | The library could not find R (it tries `$R_HOME`, `R RHOME`, and on Windows the registry): put R on `PATH` or pass `rHome` (`R RHOME` prints it). |
|
|
39
|
+
| `No libpython3.*.so* … was found under '/lib' … Is Python installed at ''?` | The library could not find Python (it tries `$PYTHONHOME`, then `python3`/`python` on `PATH`): install Python 3 (`sudo apt install python3`) or pass `pythonHome`. |
|
|
40
|
+
| `Could not set up the R packages the kernel needs: …` | The one-time install of `hera` and its dependencies (see the README's Install section) failed; the text after the colon is R's own reason (no internet access to CRAN, no compiler on Linux, a library that cannot be written to). Fix that and call `createSession()` again; it retries. `JOVIAN_SKIP_R_SETUP=1` skips the step. |
|
|
41
|
+
| `unable to load shared object '/usr/lib/R/site-library/<pkg>/libs/<pkg>.so': undefined symbol: SETLENGTH` (or another `undefined symbol`) | Only when the automatic setup is skipped or you install by hand. Debian/Ubuntu: a package installed with `apt` (`r-cran-*`, in `/usr/lib/R/site-library`) was built for an older R than yours (the automatic setup reinstalls these itself). Reinstall it, and everything it needs, from CRAN into your own library, which R searches first. Needs a compiler (`sudo apt install build-essential`):<br>`wanted <- c("remotes","cli","evaluate","glue","IRdisplay","jsonlite","R6","repr","rlang")`<br>`all <- unique(c(wanted, unlist(tools::package_dependencies(wanted, db = available.packages(), recursive = TRUE), use.names = FALSE)))`<br>`ip <- installed.packages(); install.packages(setdiff(all, rownames(ip)[!is.na(ip[, "Priority"])]), repos = "https://cloud.r-project.org")` |
|
|
42
|
+
| `WARNING: 'hera' package could not be loaded (status: install_failed: …)` | Elara could not install the bundled `hera` R package; the text after `install_failed:` is R's own reason. Usually one of `hera`'s CRAN dependencies is missing or cannot be built (install them first: `install.packages(c("remotes", "cli", "evaluate", "glue", "IRdisplay", "jsonlite", "R6", "repr", "rlang"))`), or the R library is not writable. To see the full output run `R CMD INSTALL node_modules/@damurka/jovian/packages/hera`. |
|
|
39
43
|
| `Could not load …/lib/libR.so (…). Is R installed at '…'? If this R was built from source, it needs to have been configured with --enable-R-shlib, or no libR.so exists at all` | Use a distribution/CRAN R, or rebuild R with `--enable-R-shlib`. |
|
|
40
44
|
| No `libR.dylib` on macOS | Point `rHome` at the framework's `Resources` directory (what `R RHOME` prints). |
|
|
41
45
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { EngineOptions } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs once, before the first R session, when the library was given a copy of
|
|
4
|
+
* the 'hera' R package (always the case for an npm install): makes sure hera
|
|
5
|
+
* and everything it needs are installed, so a person who has just installed R
|
|
6
|
+
* has nothing to do by hand.
|
|
7
|
+
*
|
|
8
|
+
* It is a separate step, not part of the kernel's start-up, because the
|
|
9
|
+
* supervisor gives a kernel 60 seconds to register and installing a dozen
|
|
10
|
+
* packages (compiling some, on Linux) takes longer. A kernel started after
|
|
11
|
+
* this finds hera already there.
|
|
12
|
+
*
|
|
13
|
+
* Only what is missing is installed, into the first writable library, and
|
|
14
|
+
* nothing is installed at all when hera is already current. A package that is
|
|
15
|
+
* installed but cannot be loaded (Debian/Ubuntu `r-cran-*` packages built for
|
|
16
|
+
* an older R fail with "undefined symbol: SETLENGTH") counts as missing and is
|
|
17
|
+
* reinstalled from CRAN into the user's own library, which R searches first.
|
|
18
|
+
*/
|
|
19
|
+
export declare const R_SETUP_SCRIPT: string;
|
|
20
|
+
export interface SetupLogger {
|
|
21
|
+
debug(message: string): void;
|
|
22
|
+
info(message: string): void;
|
|
23
|
+
}
|
|
24
|
+
export interface SetupProcessOutcome {
|
|
25
|
+
code: number | null;
|
|
26
|
+
timedOut: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface SetupDeps {
|
|
29
|
+
env: NodeJS.ProcessEnv;
|
|
30
|
+
platform: string;
|
|
31
|
+
exists: (path: string) => boolean;
|
|
32
|
+
/** Runs `rscript scriptFile ...args`, reporting each stdout/stderr line. */
|
|
33
|
+
run: (rscript: string, scriptFile: string, args: string[], env: NodeJS.ProcessEnv, onLine: (line: string) => void, timeoutMs: number) => Promise<SetupProcessOutcome>;
|
|
34
|
+
timeoutMs: number;
|
|
35
|
+
}
|
|
36
|
+
export declare function rscriptPath(rHome: string, deps?: Pick<SetupDeps, 'platform' | 'exists'>): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Makes sure hera and its dependencies are installed for this R (see the
|
|
39
|
+
* comment on R_SETUP_SCRIPT). Resolves immediately when there is nothing to
|
|
40
|
+
* do: a Python session, no bundled hera (a source checkout), no R found, or
|
|
41
|
+
* JOVIAN_SKIP_R_SETUP set. Concurrent calls for the same R share one run, and
|
|
42
|
+
* a run that succeeded is not repeated by this process. Rejects with R's own
|
|
43
|
+
* explanation when the packages could not be installed.
|
|
44
|
+
*/
|
|
45
|
+
export declare function ensureRPackages(options: EngineOptions, logger: SetupLogger, deps?: SetupDeps): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=r-setup.d.ts.map
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
/**
|
|
8
|
+
* Runs once, before the first R session, when the library was given a copy of
|
|
9
|
+
* the 'hera' R package (always the case for an npm install): makes sure hera
|
|
10
|
+
* and everything it needs are installed, so a person who has just installed R
|
|
11
|
+
* has nothing to do by hand.
|
|
12
|
+
*
|
|
13
|
+
* It is a separate step, not part of the kernel's start-up, because the
|
|
14
|
+
* supervisor gives a kernel 60 seconds to register and installing a dozen
|
|
15
|
+
* packages (compiling some, on Linux) takes longer. A kernel started after
|
|
16
|
+
* this finds hera already there.
|
|
17
|
+
*
|
|
18
|
+
* Only what is missing is installed, into the first writable library, and
|
|
19
|
+
* nothing is installed at all when hera is already current. A package that is
|
|
20
|
+
* installed but cannot be loaded (Debian/Ubuntu `r-cran-*` packages built for
|
|
21
|
+
* an older R fail with "undefined symbol: SETLENGTH") counts as missing and is
|
|
22
|
+
* reinstalled from CRAN into the user's own library, which R searches first.
|
|
23
|
+
*/
|
|
24
|
+
export const R_SETUP_SCRIPT = String.raw `
|
|
25
|
+
src <- commandArgs(trailingOnly = TRUE)[1]
|
|
26
|
+
say <- function(...) cat("JOVIAN_R_SETUP: ", ..., "\n", sep = "")
|
|
27
|
+
fail <- function(...) {
|
|
28
|
+
cat("JOVIAN_R_SETUP_ERROR: ", ..., "\n", sep = "")
|
|
29
|
+
quit(save = "no", status = 1)
|
|
30
|
+
}
|
|
31
|
+
field <- function(path, name) {
|
|
32
|
+
value <- tryCatch(read.dcf(path, fields = name)[1, 1], error = function(e) NA_character_)
|
|
33
|
+
if (is.na(value)) NA_character_ else value
|
|
34
|
+
}
|
|
35
|
+
stamp_wanted <- field(file.path(src, "DESCRIPTION"), "Config/jovian/release")
|
|
36
|
+
|
|
37
|
+
hera_current <- function() {
|
|
38
|
+
installed <- tryCatch(find.package("hera", quiet = TRUE), error = function(e) character(0))
|
|
39
|
+
if (!length(installed)) return(FALSE)
|
|
40
|
+
if (!is.na(stamp_wanted) && !identical(field(file.path(installed, "DESCRIPTION"), "Config/jovian/release"), stamp_wanted)) return(FALSE)
|
|
41
|
+
suppressWarnings(suppressMessages(requireNamespace("hera", quietly = TRUE)))
|
|
42
|
+
}
|
|
43
|
+
if (hera_current()) {
|
|
44
|
+
say("hera is already installed")
|
|
45
|
+
quit(save = "no", status = 0)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
repos <- getOption("repos")
|
|
49
|
+
if (is.null(repos) || is.na(repos["CRAN"]) || identical(unname(repos["CRAN"]), "@CRAN@")) {
|
|
50
|
+
options(repos = c(CRAN = "https://cloud.r-project.org"))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
is_writable <- function(path) dir.exists(path) && file.access(path, 2) == 0
|
|
54
|
+
lib <- Filter(is_writable, .libPaths())[1]
|
|
55
|
+
if (is.na(lib)) {
|
|
56
|
+
lib <- Sys.getenv("R_LIBS_USER")
|
|
57
|
+
if (!nzchar(lib)) lib <- file.path(Sys.getenv("HOME"), "R", "library")
|
|
58
|
+
dir.create(lib, recursive = TRUE, showWarnings = FALSE)
|
|
59
|
+
.libPaths(c(lib, .libPaths()))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ip <- installed.packages()
|
|
63
|
+
base_packages <- rownames(ip)[!is.na(ip[, "Priority"])]
|
|
64
|
+
fields <- tryCatch(read.dcf(file.path(src, "DESCRIPTION"), fields = c("Depends", "Imports", "LinkingTo")), error = function(e) NULL)
|
|
65
|
+
direct <- unique(trimws(sub("[(].*$", "", unlist(strsplit(paste(stats::na.omit(as.vector(fields)), collapse = ","), ",")))))
|
|
66
|
+
direct <- setdiff(direct[nzchar(direct)], c("R", base_packages))
|
|
67
|
+
|
|
68
|
+
available <- tryCatch(suppressWarnings(available.packages()), error = function(e) NULL)
|
|
69
|
+
wanted <- direct
|
|
70
|
+
if (!is.null(available) && nrow(available) > 0) {
|
|
71
|
+
everything <- tools::package_dependencies(direct, db = available, recursive = TRUE)
|
|
72
|
+
wanted <- setdiff(unique(c(direct, unlist(everything, use.names = FALSE))), base_packages)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
usable <- function(package) suppressWarnings(suppressMessages(requireNamespace(package, quietly = TRUE)))
|
|
76
|
+
missing <- wanted[!vapply(wanted, usable, logical(1))]
|
|
77
|
+
warnings_seen <- character()
|
|
78
|
+
collect <- function(expr) withCallingHandlers(expr, warning = function(w) {
|
|
79
|
+
warnings_seen <<- c(warnings_seen, conditionMessage(w))
|
|
80
|
+
invokeRestart("muffleWarning")
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
if (length(missing) > 0) {
|
|
84
|
+
if (is.null(available) || nrow(available) == 0) {
|
|
85
|
+
fail("these R packages are needed but not installed: ", paste(missing, collapse = ", "),
|
|
86
|
+
" -- and CRAN could not be reached to install them (check the internet connection)")
|
|
87
|
+
}
|
|
88
|
+
say("installing ", length(missing), " R package(s) from CRAN into ", lib, " (first run only): ", paste(missing, collapse = ", "))
|
|
89
|
+
tryCatch(collect(utils::install.packages(missing, lib = lib, repos = getOption("repos"), quiet = TRUE)),
|
|
90
|
+
error = function(e) fail("installing R packages failed: ", conditionMessage(e)))
|
|
91
|
+
still_missing <- missing[!vapply(missing, usable, logical(1))]
|
|
92
|
+
if (length(still_missing) > 0) {
|
|
93
|
+
fail("could not install these R packages: ", paste(still_missing, collapse = ", "),
|
|
94
|
+
" -- on Linux they are compiled from source, which needs a compiler (Ubuntu: sudo apt install build-essential)",
|
|
95
|
+
if (length(warnings_seen)) paste0(" | ", substr(paste(unique(warnings_seen), collapse = " | "), 1, 400)) else "")
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
say("installing hera into ", lib)
|
|
100
|
+
tryCatch(collect(utils::install.packages(src, repos = NULL, type = "source", lib = lib, quiet = TRUE)),
|
|
101
|
+
error = function(e) NULL)
|
|
102
|
+
if (!hera_current()) {
|
|
103
|
+
output <- tryCatch(
|
|
104
|
+
suppressWarnings(system2(file.path(R.home("bin"), "R"), c("CMD", "INSTALL", paste0("--library=", shQuote(lib)), shQuote(src)), stdout = TRUE, stderr = TRUE)),
|
|
105
|
+
error = function(e) conditionMessage(e))
|
|
106
|
+
lines <- grep("ERROR|error|not available|cannot|denied|failed", output, value = TRUE)
|
|
107
|
+
reason <- paste(utils::tail(if (length(lines)) lines else output, 6), collapse = " | ")
|
|
108
|
+
fail("hera could not be installed: ", substr(gsub("[[:space:]]+", " ", reason), 1, 800))
|
|
109
|
+
}
|
|
110
|
+
say("done")
|
|
111
|
+
`;
|
|
112
|
+
const runRscript = async (rscript, scriptFile, args, env, onLine, timeoutMs) => {
|
|
113
|
+
const child = spawn(rscript, [scriptFile, ...args], { env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
114
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
115
|
+
createInterface({ input: stream }).on('line', onLine);
|
|
116
|
+
}
|
|
117
|
+
let timedOut = false;
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
timedOut = true;
|
|
120
|
+
child.kill();
|
|
121
|
+
}, timeoutMs);
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
child.once('error', (error) => {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
reject(error);
|
|
126
|
+
});
|
|
127
|
+
child.once('close', (code) => {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
resolve({ code, timedOut });
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
const defaultDeps = () => ({
|
|
134
|
+
env: process.env,
|
|
135
|
+
platform: process.platform,
|
|
136
|
+
exists: existsSync,
|
|
137
|
+
run: runRscript,
|
|
138
|
+
timeoutMs: 30 * 60 * 1000
|
|
139
|
+
});
|
|
140
|
+
export function rscriptPath(rHome, deps = defaultDeps()) {
|
|
141
|
+
const name = deps.platform === 'win32' ? 'Rscript.exe' : 'Rscript';
|
|
142
|
+
const candidates = [join(rHome, 'bin', name)];
|
|
143
|
+
if (deps.platform === 'win32')
|
|
144
|
+
candidates.push(join(rHome, 'bin', 'x64', name));
|
|
145
|
+
return candidates.find((candidate) => deps.exists(candidate));
|
|
146
|
+
}
|
|
147
|
+
const inFlight = new Map();
|
|
148
|
+
/**
|
|
149
|
+
* Makes sure hera and its dependencies are installed for this R (see the
|
|
150
|
+
* comment on R_SETUP_SCRIPT). Resolves immediately when there is nothing to
|
|
151
|
+
* do: a Python session, no bundled hera (a source checkout), no R found, or
|
|
152
|
+
* JOVIAN_SKIP_R_SETUP set. Concurrent calls for the same R share one run, and
|
|
153
|
+
* a run that succeeded is not repeated by this process. Rejects with R's own
|
|
154
|
+
* explanation when the packages could not be installed.
|
|
155
|
+
*/
|
|
156
|
+
export function ensureRPackages(options, logger, deps = defaultDeps()) {
|
|
157
|
+
const { rHome, heraSrcPath } = options;
|
|
158
|
+
if (options.kernelType === 'python' || !rHome || !heraSrcPath)
|
|
159
|
+
return Promise.resolve();
|
|
160
|
+
if (deps.env.JOVIAN_SKIP_R_SETUP)
|
|
161
|
+
return Promise.resolve();
|
|
162
|
+
const rscript = rscriptPath(rHome, deps);
|
|
163
|
+
if (!rscript) {
|
|
164
|
+
logger.debug(`No Rscript under ${rHome}; leaving the R packages to the kernel`);
|
|
165
|
+
return Promise.resolve();
|
|
166
|
+
}
|
|
167
|
+
const key = [rHome, heraSrcPath, options.rLibs ?? ''].join('|');
|
|
168
|
+
let pending = inFlight.get(key);
|
|
169
|
+
if (!pending) {
|
|
170
|
+
pending = setUp(rscript, heraSrcPath, options.rLibs, logger, deps).catch((error) => {
|
|
171
|
+
inFlight.delete(key);
|
|
172
|
+
throw error;
|
|
173
|
+
});
|
|
174
|
+
inFlight.set(key, pending);
|
|
175
|
+
}
|
|
176
|
+
return pending;
|
|
177
|
+
}
|
|
178
|
+
async function setUp(rscript, heraSrcPath, rLibs, logger, deps) {
|
|
179
|
+
const directory = await mkdtemp(join(tmpdir(), 'jovian-r-setup-'));
|
|
180
|
+
try {
|
|
181
|
+
const scriptFile = join(directory, 'setup.R');
|
|
182
|
+
await writeFile(scriptFile, R_SETUP_SCRIPT);
|
|
183
|
+
const env = { ...deps.env, ...(rLibs ? { R_LIBS: rLibs } : {}) };
|
|
184
|
+
let failure;
|
|
185
|
+
const otherOutput = [];
|
|
186
|
+
const onLine = (line) => {
|
|
187
|
+
if (line.startsWith('JOVIAN_R_SETUP_ERROR: '))
|
|
188
|
+
failure = line.slice('JOVIAN_R_SETUP_ERROR: '.length);
|
|
189
|
+
else if (line.startsWith('JOVIAN_R_SETUP: '))
|
|
190
|
+
logger.info(`R setup: ${line.slice('JOVIAN_R_SETUP: '.length)}`);
|
|
191
|
+
else if (line.trim())
|
|
192
|
+
otherOutput.push(line.trim());
|
|
193
|
+
};
|
|
194
|
+
const outcome = await deps.run(rscript, scriptFile, [heraSrcPath], env, onLine, deps.timeoutMs);
|
|
195
|
+
if (outcome.timedOut) {
|
|
196
|
+
throw new Error(`Setting up the R packages the kernel needs took longer than ${Math.round(deps.timeoutMs / 60000)} minutes and was stopped.`);
|
|
197
|
+
}
|
|
198
|
+
if (outcome.code !== 0) {
|
|
199
|
+
const unexplained = `Rscript exited with code ${outcome.code}${otherOutput.length ? `: ${otherOutput.slice(-5).join(' | ')}` : ''}`;
|
|
200
|
+
throw new Error(`Could not set up the R packages the kernel needs: ${failure ?? unexplained}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
await rm(directory, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=r-setup.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EngineOptions } from '../types/index.js';
|
|
2
|
+
/** Runs a command and returns its last non-empty stdout line, or undefined if it failed or printed nothing. */
|
|
3
|
+
export type Runner = (command: string, args: string[]) => string | undefined;
|
|
4
|
+
export interface DiscoveryContext {
|
|
5
|
+
run: Runner;
|
|
6
|
+
env: Record<string, string | undefined>;
|
|
7
|
+
platform: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Where R lives, when the caller did not say: $R_HOME, else what `R RHOME`
|
|
11
|
+
* prints (R's own answer, valid on every platform, if R is on PATH), else on
|
|
12
|
+
* Windows the install path R's installer records in the registry.
|
|
13
|
+
*/
|
|
14
|
+
export declare function discoverRHome(context?: DiscoveryContext): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Which Python to embed, when the caller did not say: $PYTHONHOME, else the
|
|
17
|
+
* installation prefix of the first python on PATH. sys.base_prefix, not
|
|
18
|
+
* sys.prefix: inside a virtual environment the latter is the venv, which has
|
|
19
|
+
* no libpython to load.
|
|
20
|
+
*/
|
|
21
|
+
export declare function discoverPythonHome(context?: DiscoveryContext): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The options with rHome (R sessions) or pythonHome (Python sessions) filled
|
|
24
|
+
* in when the caller left them out and the runtime could be found. Anything
|
|
25
|
+
* the caller passed is kept as it is; if nothing is found the field stays
|
|
26
|
+
* unset, and the kernel reports what it could not find.
|
|
27
|
+
*/
|
|
28
|
+
export declare function withDiscoveredRuntime(options: EngineOptions, context?: DiscoveryContext): EngineOptions;
|
|
29
|
+
//# sourceMappingURL=runtimes.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
const runCommand = (command, args) => {
|
|
3
|
+
try {
|
|
4
|
+
const output = execFileSync(command, args, {
|
|
5
|
+
encoding: 'utf8',
|
|
6
|
+
timeout: 10_000,
|
|
7
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
8
|
+
windowsHide: true
|
|
9
|
+
});
|
|
10
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
11
|
+
return lines[lines.length - 1];
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const defaultContext = () => ({ run: runCommand, env: process.env, platform: process.platform });
|
|
18
|
+
/**
|
|
19
|
+
* Where R lives, when the caller did not say: $R_HOME, else what `R RHOME`
|
|
20
|
+
* prints (R's own answer, valid on every platform, if R is on PATH), else on
|
|
21
|
+
* Windows the install path R's installer records in the registry.
|
|
22
|
+
*/
|
|
23
|
+
export function discoverRHome(context = defaultContext()) {
|
|
24
|
+
if (context.env.R_HOME)
|
|
25
|
+
return context.env.R_HOME;
|
|
26
|
+
const fromR = context.run('R', ['RHOME']);
|
|
27
|
+
if (fromR)
|
|
28
|
+
return fromR;
|
|
29
|
+
if (context.platform === 'win32') {
|
|
30
|
+
for (const hive of ['HKLM', 'HKCU']) {
|
|
31
|
+
const line = context.run('reg', ['query', `${hive}\\SOFTWARE\\R-core\\R`, '/v', 'InstallPath']);
|
|
32
|
+
const match = line?.match(/InstallPath\s+REG_SZ\s+(.+)$/);
|
|
33
|
+
if (match?.[1])
|
|
34
|
+
return match[1].trim();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Which Python to embed, when the caller did not say: $PYTHONHOME, else the
|
|
41
|
+
* installation prefix of the first python on PATH. sys.base_prefix, not
|
|
42
|
+
* sys.prefix: inside a virtual environment the latter is the venv, which has
|
|
43
|
+
* no libpython to load.
|
|
44
|
+
*/
|
|
45
|
+
export function discoverPythonHome(context = defaultContext()) {
|
|
46
|
+
if (context.env.PYTHONHOME)
|
|
47
|
+
return context.env.PYTHONHOME;
|
|
48
|
+
const script = 'import sys; print(sys.base_prefix)';
|
|
49
|
+
const candidates = [
|
|
50
|
+
['python3', ['-c', script]],
|
|
51
|
+
['python', ['-c', script]]
|
|
52
|
+
];
|
|
53
|
+
if (context.platform === 'win32')
|
|
54
|
+
candidates.push(['py', ['-3', '-c', script]]);
|
|
55
|
+
for (const [command, args] of candidates) {
|
|
56
|
+
const home = context.run(command, args);
|
|
57
|
+
if (home)
|
|
58
|
+
return home;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The options with rHome (R sessions) or pythonHome (Python sessions) filled
|
|
64
|
+
* in when the caller left them out and the runtime could be found. Anything
|
|
65
|
+
* the caller passed is kept as it is; if nothing is found the field stays
|
|
66
|
+
* unset, and the kernel reports what it could not find.
|
|
67
|
+
*/
|
|
68
|
+
export function withDiscoveredRuntime(options, context = defaultContext()) {
|
|
69
|
+
if (options.kernelType === 'python') {
|
|
70
|
+
if (options.pythonHome)
|
|
71
|
+
return options;
|
|
72
|
+
const pythonHome = discoverPythonHome(context);
|
|
73
|
+
return pythonHome ? { ...options, pythonHome } : options;
|
|
74
|
+
}
|
|
75
|
+
if (options.rHome)
|
|
76
|
+
return options;
|
|
77
|
+
const rHome = discoverRHome(context);
|
|
78
|
+
return rHome ? { ...options, rHome } : options;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=runtimes.js.map
|
|
@@ -122,7 +122,7 @@ export declare class Session extends EventEmitter {
|
|
|
122
122
|
* execute() timed out) first.
|
|
123
123
|
*/
|
|
124
124
|
interrupt(options?: {
|
|
125
|
-
timeout?: number;
|
|
125
|
+
timeout?: number | undefined;
|
|
126
126
|
}): Promise<boolean>;
|
|
127
127
|
/**
|
|
128
128
|
* The latest iopub `status` the kernel reported ('busy' while it is
|
|
@@ -145,7 +145,7 @@ export declare class Session extends EventEmitter {
|
|
|
145
145
|
* shutdown_request (stop()/restart()) -- the supervisor rejects those.
|
|
146
146
|
*/
|
|
147
147
|
request<T = any>(msgType: string, content?: Record<string, unknown>, options?: {
|
|
148
|
-
timeout?: number;
|
|
148
|
+
timeout?: number | undefined;
|
|
149
149
|
}): Promise<T>;
|
|
150
150
|
private trackExecutionState;
|
|
151
151
|
private routeComm;
|
|
@@ -209,11 +209,12 @@ export declare class Session extends EventEmitter {
|
|
|
209
209
|
kill(): void;
|
|
210
210
|
}
|
|
211
211
|
export declare class SessionManager {
|
|
212
|
+
private readonly logger;
|
|
212
213
|
private readonly supervisor;
|
|
213
214
|
private readonly sessions;
|
|
214
215
|
private exitHandlerRegistered;
|
|
215
216
|
/** Creates a new R session in its own OS process and waits for it to be ready. */
|
|
216
|
-
createSession(
|
|
217
|
+
createSession(requested?: EngineOptions): Promise<Session>;
|
|
217
218
|
/** Gracefully stops every session managed by this instance. */
|
|
218
219
|
stopAll(): Promise<void>;
|
|
219
220
|
/**
|
|
@@ -12,6 +12,9 @@ import { ErrorHandler } from '../handlers/error-handler.js';
|
|
|
12
12
|
import { DisplayHandler } from '../handlers/display-handler.js';
|
|
13
13
|
import { findFreePort, waitForPort } from '../utils/network.js';
|
|
14
14
|
import { SupervisorClient } from './supervisor-client.js';
|
|
15
|
+
import { withDiscoveredRuntime } from './runtimes.js';
|
|
16
|
+
import { ensureRPackages } from './r-setup.js';
|
|
17
|
+
import { bundledHeraSource } from './native-paths.js';
|
|
15
18
|
import { Comm } from './comm.js';
|
|
16
19
|
const DEFAULT_REQUEST_TIMEOUT_MS = 10000;
|
|
17
20
|
// How long stop() waits for the kernel's shutdown_reply after the
|
|
@@ -213,7 +216,7 @@ export class Session extends EventEmitter {
|
|
|
213
216
|
// The supervisor replaces a session's options wholesale, so send the
|
|
214
217
|
// merge -- a restart that only switches rHome must keep the
|
|
215
218
|
// workingDirectory, rLibs, ... the session was created with.
|
|
216
|
-
const mergedOptions = options ? { ...this.currentOptions, ...options } : undefined;
|
|
219
|
+
const mergedOptions = options ? withDiscoveredRuntime({ ...this.currentOptions, ...options }) : undefined;
|
|
217
220
|
// Reassigned synchronously, before awaiting anything below, so a
|
|
218
221
|
// concurrent execute()/createShiny() call that reads this.readyPromise
|
|
219
222
|
// while the restart is still in flight waits for the new connection
|
|
@@ -222,6 +225,8 @@ export class Session extends EventEmitter {
|
|
|
222
225
|
const shutdownReply = this.watchFor('shutdown_reply');
|
|
223
226
|
this.readyPromise = (async () => {
|
|
224
227
|
try {
|
|
228
|
+
if (mergedOptions)
|
|
229
|
+
await ensureRPackages(mergedOptions, this.logger);
|
|
225
230
|
await this.supervisor.restartSession(this.info, mergedOptions);
|
|
226
231
|
if (mergedOptions) {
|
|
227
232
|
this.currentOptions = mergedOptions;
|
|
@@ -776,11 +781,17 @@ export class Session extends EventEmitter {
|
|
|
776
781
|
}
|
|
777
782
|
}
|
|
778
783
|
export class SessionManager {
|
|
779
|
-
|
|
784
|
+
logger = new Logger();
|
|
785
|
+
supervisor = new SupervisorClient(this.logger);
|
|
780
786
|
sessions = new Set();
|
|
781
787
|
exitHandlerRegistered = false;
|
|
782
788
|
/** Creates a new R session in its own OS process and waits for it to be ready. */
|
|
783
|
-
async createSession(
|
|
789
|
+
async createSession(requested = {}) {
|
|
790
|
+
// Finds R / Python when rHome / pythonHome were not given (see runtimes.ts).
|
|
791
|
+
// An installed package brings its own copy of hera (none in a source checkout).
|
|
792
|
+
const options = withDiscoveredRuntime(requested.heraSrcPath ? requested : { ...requested, heraSrcPath: bundledHeraSource() });
|
|
793
|
+
// First R session only: installs hera and what it needs (see r-setup.ts).
|
|
794
|
+
await ensureRPackages(options, this.logger);
|
|
784
795
|
const info = await this.supervisor.createSession(options);
|
|
785
796
|
const session = new Session(info, options, this.supervisor);
|
|
786
797
|
this.sessions.add(session);
|
package/lib/types/engine.d.ts
CHANGED
|
@@ -9,12 +9,13 @@ export interface EngineOptions {
|
|
|
9
9
|
* supervisor actually uses (native/src/themisto/session_registry.cpp's
|
|
10
10
|
* SessionOptions::kernelType) and which kernel executable it spawns.
|
|
11
11
|
*/
|
|
12
|
-
kernelType?: 'r' | 'python';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
kernelType?: 'r' | 'python' | undefined;
|
|
13
|
+
/** R installation (`R RHOME`). Found from $R_HOME, `R RHOME` or the Windows registry when omitted. */
|
|
14
|
+
rHome?: string | undefined;
|
|
15
|
+
rPath?: string | undefined;
|
|
16
|
+
rLibs?: string | undefined;
|
|
16
17
|
/** Directory containing the pandoc binary, for bundled R installs that don't ship it on PATH. */
|
|
17
|
-
pandocPath?: string;
|
|
18
|
+
pandocPath?: string | undefined;
|
|
18
19
|
/**
|
|
19
20
|
* Source directory of the 'hera' R package (packages/hera in this repo).
|
|
20
21
|
* When set, Elara installs it via remotes::install_local() if it is
|
|
@@ -23,13 +24,13 @@ export interface EngineOptions {
|
|
|
23
24
|
* already installed in the library -- install or update it with
|
|
24
25
|
* `npm run hera:install`.
|
|
25
26
|
*/
|
|
26
|
-
heraSrcPath?: string;
|
|
27
|
-
/** Only used when kernelType is 'python' -- Carpo's equivalent of rHome. */
|
|
28
|
-
pythonHome?: string;
|
|
27
|
+
heraSrcPath?: string | undefined;
|
|
28
|
+
/** Only used when kernelType is 'python' -- Carpo's equivalent of rHome. Found from $PYTHONHOME or `python3`/`python` on PATH when omitted. */
|
|
29
|
+
pythonHome?: string | undefined;
|
|
29
30
|
/** Only used when kernelType is 'python' -- Carpo's equivalent of rPath. */
|
|
30
|
-
pythonPath?: string;
|
|
31
|
+
pythonPath?: string | undefined;
|
|
31
32
|
/** Only used when kernelType is 'python' -- not yet consulted by Carpo itself (see carpo::EnvironmentConfig). */
|
|
32
|
-
venvPath?: string;
|
|
33
|
+
venvPath?: string | undefined;
|
|
33
34
|
/**
|
|
34
35
|
* Directory the kernel process starts in -- what `getwd()` (R) /
|
|
35
36
|
* `os.getcwd()` (Python) report and what relative paths resolve against.
|
|
@@ -37,44 +38,44 @@ export interface EngineOptions {
|
|
|
37
38
|
* (i.e. the calling process's), which is rarely what you want for a
|
|
38
39
|
* notebook/project: set it to the project or document folder.
|
|
39
40
|
*/
|
|
40
|
-
workingDirectory?: string;
|
|
41
|
-
queueSize?: number;
|
|
42
|
-
enableLogging?: boolean;
|
|
43
|
-
enableMetrics?: boolean;
|
|
44
|
-
logger?: LoggerFunction;
|
|
41
|
+
workingDirectory?: string | undefined;
|
|
42
|
+
queueSize?: number | undefined;
|
|
43
|
+
enableLogging?: boolean | undefined;
|
|
44
|
+
enableMetrics?: boolean | undefined;
|
|
45
|
+
logger?: LoggerFunction | undefined;
|
|
45
46
|
}
|
|
46
47
|
export type EngineState = 'idle' | 'starting' | 'running' | 'stopping' | 'stopped' | 'error';
|
|
47
48
|
export interface ExecutionOptions {
|
|
48
|
-
silent?: boolean;
|
|
49
|
-
storeHistory?: boolean;
|
|
50
|
-
allowStdin?: boolean;
|
|
49
|
+
silent?: boolean | undefined;
|
|
50
|
+
storeHistory?: boolean | undefined;
|
|
51
|
+
allowStdin?: boolean | undefined;
|
|
51
52
|
/**
|
|
52
53
|
* When this execution fails, abort every execute() still waiting behind
|
|
53
54
|
* it in the queue instead of running them (Jupyter's stop_on_error) --
|
|
54
55
|
* their results come back with `aborted: true` and nothing having run.
|
|
55
56
|
* Also forwarded to the kernel in the execute_request itself.
|
|
56
57
|
*/
|
|
57
|
-
stopOnError?: boolean;
|
|
58
|
+
stopOnError?: boolean | undefined;
|
|
58
59
|
/**
|
|
59
60
|
* Expressions to evaluate in the kernel right after the code runs, as
|
|
60
61
|
* {name: expression} (Jupyter's user_expressions). Only evaluated when
|
|
61
62
|
* the code succeeded; each result -- or its own error -- comes back in
|
|
62
63
|
* `ExecutionResult.userExpressions` under the same name.
|
|
63
64
|
*/
|
|
64
|
-
userExpressions?: Record<string, string
|
|
65
|
+
userExpressions?: Record<string, string> | undefined;
|
|
65
66
|
/**
|
|
66
67
|
* Milliseconds to wait for the execution to finish before giving up
|
|
67
68
|
* (default 30000; 0 = no timeout, for calls meant to run indefinitely
|
|
68
69
|
* such as a Shiny app).
|
|
69
70
|
*/
|
|
70
|
-
timeout?: number;
|
|
71
|
+
timeout?: number | undefined;
|
|
71
72
|
/**
|
|
72
73
|
* When the timeout fires, also send the kernel an interrupt (default
|
|
73
74
|
* true) so it stops the code instead of carrying on with work no one is
|
|
74
75
|
* waiting for -- which would otherwise block everything queued behind it.
|
|
75
76
|
* Set false to leave the kernel running after a timeout.
|
|
76
77
|
*/
|
|
77
|
-
interruptOnTimeout?: boolean;
|
|
78
|
+
interruptOnTimeout?: boolean | undefined;
|
|
78
79
|
}
|
|
79
80
|
/** One evaluated user expression: its rich value, or the error evaluating it raised. */
|
|
80
81
|
export type UserExpressionResult = {
|
|
@@ -131,19 +132,19 @@ export interface ExecutionHistoryEntry {
|
|
|
131
132
|
*/
|
|
132
133
|
export interface KernelHistoryOptions {
|
|
133
134
|
/** Defaults to 'tail' -- the n most recent executions. */
|
|
134
|
-
histAccessType?: 'tail' | 'range' | 'search';
|
|
135
|
+
histAccessType?: 'tail' | 'range' | 'search' | undefined;
|
|
135
136
|
/** Include each entry's output alongside its input. Defaults to false -- the kernel doesn't actually record output today either way, so this currently only ever comes back empty. */
|
|
136
|
-
output?: boolean;
|
|
137
|
-
raw?: boolean;
|
|
137
|
+
output?: boolean | undefined;
|
|
138
|
+
raw?: boolean | undefined;
|
|
138
139
|
/** Max entries to return ('tail'/'search'). Defaults to 100. */
|
|
139
|
-
n?: number;
|
|
140
|
+
n?: number | undefined;
|
|
140
141
|
/** 'range' only. */
|
|
141
|
-
session?: number;
|
|
142
|
-
start?: number;
|
|
143
|
-
stop?: number;
|
|
142
|
+
session?: number | undefined;
|
|
143
|
+
start?: number | undefined;
|
|
144
|
+
stop?: number | undefined;
|
|
144
145
|
/** 'search' only -- a glob pattern (*, ?). */
|
|
145
|
-
pattern?: string;
|
|
146
|
-
unique?: boolean;
|
|
146
|
+
pattern?: string | undefined;
|
|
147
|
+
unique?: boolean | undefined;
|
|
147
148
|
}
|
|
148
149
|
/**
|
|
149
150
|
* One entry from a real history_reply: [session, line_number, input], or
|
|
@@ -179,13 +180,13 @@ export interface ShinyAppOptions {
|
|
|
179
180
|
/** Directory containing the Shiny app (server.R/ui.R or app.R). */
|
|
180
181
|
appDir: string;
|
|
181
182
|
/** Defaults to an OS-assigned free port. */
|
|
182
|
-
port?: number;
|
|
183
|
+
port?: number | undefined;
|
|
183
184
|
/** Defaults to '127.0.0.1'. */
|
|
184
|
-
host?: string;
|
|
185
|
+
host?: string | undefined;
|
|
185
186
|
/** Defaults to false -- the caller decides how/where to display the app. */
|
|
186
|
-
launchBrowser?: boolean;
|
|
187
|
+
launchBrowser?: boolean | undefined;
|
|
187
188
|
/** Max time to wait for the app to start accepting connections, in ms. Defaults to 10000. */
|
|
188
|
-
readyTimeout?: number;
|
|
189
|
+
readyTimeout?: number | undefined;
|
|
189
190
|
/**
|
|
190
191
|
* Environment variables to set (via Sys.setenv()) in the R session
|
|
191
192
|
* before launching the app -- e.g. rmncah's app.R reads
|
|
@@ -195,7 +196,7 @@ export interface ShinyAppOptions {
|
|
|
195
196
|
* that reads them after runApp() starts, since Sys.setenv() itself runs
|
|
196
197
|
* synchronously right before it in the same execute() call.
|
|
197
198
|
*/
|
|
198
|
-
env?: Record<string, string
|
|
199
|
+
env?: Record<string, string> | undefined;
|
|
199
200
|
}
|
|
200
201
|
export interface ShinyAppHandle {
|
|
201
202
|
host: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@damurka/jovian",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "David Kariuki",
|
|
6
6
|
"repository": {
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"LICENSE"
|
|
51
51
|
],
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@damurka/jovian-win32-x64": "0.1.
|
|
54
|
-
"@damurka/jovian-linux-x64": "0.1.
|
|
55
|
-
"@damurka/jovian-darwin-arm64": "0.1.
|
|
53
|
+
"@damurka/jovian-win32-x64": "0.1.1",
|
|
54
|
+
"@damurka/jovian-linux-x64": "0.1.1",
|
|
55
|
+
"@damurka/jovian-darwin-arm64": "0.1.1"
|
|
56
56
|
}
|
|
57
57
|
}
|