@usine/server 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -21
- package/dist/backend.d.ts +28 -5
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +40 -12
- package/dist/backend.js.map +1 -1
- package/dist/backup.d.ts +6 -3
- package/dist/backup.d.ts.map +1 -1
- package/dist/backup.js +25 -5
- package/dist/backup.js.map +1 -1
- package/dist/cli.d.ts +20 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +65 -15
- package/dist/cli.js.map +1 -1
- package/dist/export-run.d.ts +1 -1
- package/dist/export-run.d.ts.map +1 -1
- package/dist/export-run.js +3 -3
- package/dist/export-run.js.map +1 -1
- package/dist/failure-message.d.ts +13 -0
- package/dist/failure-message.d.ts.map +1 -0
- package/dist/failure-message.js +58 -0
- package/dist/failure-message.js.map +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +69 -23
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/instance-lock.d.ts +23 -0
- package/dist/instance-lock.d.ts.map +1 -0
- package/dist/instance-lock.js +58 -0
- package/dist/instance-lock.js.map +1 -0
- package/dist/main.js +4 -10
- package/dist/main.js.map +1 -1
- package/dist/reload-signal.d.ts +8 -0
- package/dist/reload-signal.d.ts.map +1 -0
- package/dist/reload-signal.js +21 -0
- package/dist/reload-signal.js.map +1 -0
- package/dist/shutdown.d.ts +5 -2
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +11 -5
- package/dist/shutdown.js.map +1 -1
- package/dist/up.d.ts +1 -1
- package/dist/up.d.ts.map +1 -1
- package/dist/up.js +29 -16
- package/dist/up.js.map +1 -1
- package/dist/web/assets/DialogTitle-DrHlwOSg.js +1 -0
- package/dist/web/assets/_agent-cBqm12lE.js +1 -0
- package/dist/web/assets/_loop-kf1h3qkS.js +1 -0
- package/dist/web/assets/_name-CKWcxUh0.js +2 -0
- package/dist/web/assets/_name-DCEDRHnF.js +1 -0
- package/dist/web/assets/_name-sJkGyn7O.js +1 -0
- package/dist/web/assets/_number-DbgcVg1N.js +1 -0
- package/dist/web/assets/_run-DqslP0y6.js +8 -0
- package/dist/web/assets/_worker-CH4UoJT_.js +1 -0
- package/dist/web/assets/action-DYT048Jm.js +1 -0
- package/dist/web/assets/agents-DIZolb0U.js +1 -0
- package/dist/web/assets/agents-DMhSMb9O.js +1 -0
- package/dist/web/assets/button-C4yvcSko.js +2 -0
- package/dist/web/assets/copy-BM1l6IGB.js +1 -0
- package/dist/web/assets/core-BUmsKffF.js +12 -0
- package/dist/web/assets/engine-javascript-D-6se_EF.js +141 -0
- package/dist/web/assets/error-alert-BrI00JbQ.js +1 -0
- package/dist/web/assets/file-error-C--_cWuj.js +1 -0
- package/dist/web/assets/index-BljdfRBm.js +10 -0
- package/dist/web/assets/index-YKcAk3No.css +2 -0
- package/dist/web/assets/inter-cyrillic-ext-opsz-normal-CfYZpDVp.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-opsz-normal-BFQXbQ-f.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-opsz-normal-BmPQvyvo.woff2 +0 -0
- package/dist/web/assets/inter-greek-opsz-normal-BM0NlUE_.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-opsz-normal-DpK-iCPk.woff2 +0 -0
- package/dist/web/assets/inter-latin-opsz-normal-BwkfbSeq.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-opsz-normal-Ci9T9ieq.woff2 +0 -0
- package/dist/web/assets/item-rBBCTV8x.js +1 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/assets/label-badge-ohqvZnY7.js +1 -0
- package/dist/web/assets/loops-CXM3f90o.js +1 -0
- package/dist/web/assets/loops-DWLXyUPN.js +1 -0
- package/dist/web/assets/markdown-D07-JkYB.js +29 -0
- package/dist/web/assets/moves-RiWgdCxQ.js +5 -0
- package/dist/web/assets/operator-copy-DsM7LJx6.js +301 -0
- package/dist/web/assets/outcome-YiIf6xbJ.js +5 -0
- package/dist/web/assets/project-not-found-CQVclgb0.js +1 -0
- package/dist/web/assets/query-DWN8IHdt.js +1 -0
- package/dist/web/assets/repositories-CoTEmYmS.js +1 -0
- package/dist/web/assets/repositories-Cq7zGzNb.js +1 -0
- package/dist/web/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/dist/web/assets/route-CE9U74a9.js +1 -0
- package/dist/web/assets/route-DNH2EwHP.js +1 -0
- package/dist/web/assets/routes-XAUJJqfj.js +1 -0
- package/dist/web/assets/run-again-DxjFCuNu.js +1 -0
- package/dist/web/assets/run-drawer-CyjxH5mf.js +1 -0
- package/dist/web/assets/section-BBnBOk-U.js +1 -0
- package/dist/web/assets/section-Cm-j3zHs.js +1 -0
- package/dist/web/assets/section-DkBCDv4D.js +1 -0
- package/dist/web/assets/settings-iZf872O5.js +1 -0
- package/dist/web/assets/status-badge-CNlyvwWL.js +1 -0
- package/dist/web/assets/table-B6NH8hTW.js +1 -0
- package/dist/web/assets/tasks-DhLW-1gg.js +1 -0
- package/dist/web/assets/usage-DPUTTLGs.js +1 -0
- package/dist/web/assets/use-now-CcFZyFvf.js +1 -0
- package/dist/web/assets/useNavigate-lXd3v3E0.js +1 -0
- package/dist/web/assets/usePositioner-WErrA3Ah.js +1 -0
- package/dist/web/assets/useScrollLock-BOb01sL8.js +1 -0
- package/dist/web/assets/useTriggerFocusGuards-B0FItfLc.js +1 -0
- package/dist/web/assets/utils-BEQKLGgi.js +1 -0
- package/dist/web/assets/with-selector-NsM_5qXG.js +1 -0
- package/dist/web/assets/workers-BgocBXT9.js +1 -0
- package/dist/web/assets/workers-Dw2uHY0Y.js +1 -0
- package/dist/web/index.html +13 -10
- package/package.json +13 -9
- package/dist/web/assets/_name-BHFMXhWe.js +0 -1
- package/dist/web/assets/_name-Cz2YmpSb.js +0 -1
- package/dist/web/assets/_name-D1NH8oUE.js +0 -6
- package/dist/web/assets/badge-BQobsECb.js +0 -142
- package/dist/web/assets/button-BoOukj77.js +0 -1
- package/dist/web/assets/core-Cg4Mi2IQ.js +0 -12
- package/dist/web/assets/empty-DbVYU7bi.js +0 -1
- package/dist/web/assets/engine-javascript-h-x4hGa4.js +0 -141
- package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/web/assets/index-CJ-Nk5DM.js +0 -10
- package/dist/web/assets/index-DxGXwXIP.css +0 -2
- package/dist/web/assets/loops-huxrr83I.js +0 -1
- package/dist/web/assets/operator-copy-BDZ_fazz.js +0 -1
- package/dist/web/assets/overview-DTxvvQ4b.js +0 -1
- package/dist/web/assets/rolldown-runtime-hePW80VL.js +0 -1
- package/dist/web/assets/routes-CbvAb2gn.js +0 -1
- package/dist/web/assets/run-drawer-3blrdBMZ.js +0 -157
- package/dist/web/assets/status-badge-BTkHx8qb.js +0 -1
- package/dist/web/assets/table-B8cLYNvq.js +0 -1
- package/dist/web/assets/useCompositeItem-CuLyiETJ.js +0 -1
- package/dist/web/assets/with-selector-N6m4X0X_.js +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @usine/server
|
|
2
2
|
|
|
3
|
-
The foreground CLI for a local [usine](https://github.com/basaltbytes/usine) factory
|
|
3
|
+
The foreground CLI for a local [usine](https://github.com/basaltbytes/usine) factory: a thin Node adapter over `@usine/orchestrator`. The library does the polling, schedule triggers, durable execution, worker composition, and operator actions; this package serves their JSON and web surfaces. The [CLI reference](https://docs.usine.dev/reference/configuration/cli/) lists every command, flag, and variable, and [Operate runs](https://docs.usine.dev/guides/operate-runs/) covers day-to-day use.
|
|
4
4
|
|
|
5
5
|
Run it from an instance folder:
|
|
6
6
|
|
|
@@ -8,49 +8,65 @@ Run it from an instance folder:
|
|
|
8
8
|
usine up
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
It prints `http://127.0.0.1:7331` and stays attached to the terminal. `--port <port>` or `USINE_PORT` changes the port; if the port is taken, the CLI names the conflict and suggests the override. `usine --version` prints the `@usine/server` version that the instance pins.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
One `usine up` runs per instance folder. It holds `.usine/instance.lock` from startup until every store closes, and writes its pid to `.usine/instance.pid`. A second `usine up` there names the running process and exits nonzero before opening a store. A draining process keeps the folder until it exits.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Without GitHub App credentials, it logs the typed error, leaves both trigger streams idle, and opens the setup screen in the default browser. The browser command times out after two seconds; a failed opener only logs a warning. Runs, transcripts, and loop pause state stay readable, and the instance folder can be reloaded, but interventions are unavailable. Supply `USINE_GITHUB_APP_ID` and `USINE_GITHUB_PRIVATE_KEY`, or `app_id` and `private_key` in a mode-0600 `.usine/github-app.json`. Credentials are read only at startup, so restart `usine up` after adding them, as the setup screen says.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
## Web UI
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Each project opens as a board of authored or proposed lanes; proposed lanes are read-only. To enable moves, define `repos[].lanes` in `usine.yaml` and reload (see the [lane example](../../packages/config/README.md)). Moving an item to an authored label-backed lane swaps only the labels in that map and keeps other labels; polling then evaluates label-driven loops. Cards show the latest run and open its drawer. The UI can approve, retry, or cancel a run, pause or unpause a loop, page back through older transcript rows, and tail parent and first-level child activity.
|
|
20
20
|
|
|
21
|
-
Run conversations
|
|
21
|
+
Run conversations remember the reader's filter, opened disclosures, and reading position across drawer reopens and page refreshes. Bookmarks are per run and browser origin, and unread activity stays pending while a saved position is restored. See [Returning to a conversation](../web/README.md#returning-to-a-conversation) for retention and history-recovery limits. Adjacent completed tools are summarized by their recorded actions, such as files read or changed and recognized test or build commands; expand a group to see each tool and result.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Direct browser requests for UI routes serve the app shell, so refreshing or sharing a project or configuration URL works. Unknown `/api/*` routes, missing `/assets/*` files, and other file-like paths return 404.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
`POST /api/cycle-budgets/reset` takes an ask identity, with the same origin guard and typed errors as other interventions. A stale ask returns 409, and setup mode rejects resets.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
After `pnpm build`, reload the page to use the new web bundle; no restart is needed.
|
|
28
|
+
|
|
29
|
+
## Reload the configuration
|
|
30
|
+
|
|
31
|
+
**Reload configuration** on any Configuration page, `POST /api/instance/reload`, and `SIGHUP` reload `usine.yaml`, `agents/`, and `loops/` without stopping the process. `SIGHUP` no longer ends the process; stop it with Ctrl+C or `SIGTERM`. The [CLI reference](https://docs.usine.dev/reference/configuration/cli/) describes what a reload applies. A refused reload keeps the running configuration and returns:
|
|
32
|
+
|
|
33
|
+
- 422 `FactoryConfigError` for an invalid `usine.yaml`;
|
|
34
|
+
- 409 `RestartRequiredError`, naming the field, for a changed `webhooks` block or a `workers` change that needs a restart (a worker's kind or settings, or a rename);
|
|
35
|
+
- 409 `ReloadPreflightError` if a worker, slot count, or agent brain that the reload adds fails preflight, or a new SSH worker's gateway admin token variable is unset.
|
|
36
|
+
|
|
37
|
+
An accepted reload's response names the loops, agents, labels, repos, and workers it added, removed, or changed. Until a removed worker's last attempt ends, Needs attention counts its running attempts, and `POST /api/workers/:worker/discard-leftovers` returns 409 `LeftoversDiscardRejectedError` with that count. GitHub credentials, the `workers` changes above, and the `webhooks` block apply only at startup.
|
|
38
|
+
|
|
39
|
+
## Back up and export
|
|
40
|
+
|
|
41
|
+
While the orchestrator runs, `usine backup` copies `.usine/runtime.sqlite` to the path you give, and `.usine/durable.sqlite` next to it with `.durable` before the extension (`/path/to/runtime-backup.durable.sqlite`):
|
|
28
42
|
|
|
29
43
|
```sh
|
|
30
44
|
usine backup /path/to/runtime-backup.sqlite
|
|
31
45
|
```
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
Both copies include committed WAL data and are owner-only. The command opens each source read-only, copies the durable storage first, needs no GitHub credentials, and never overwrites a file. Use a new name in an existing writable directory on a file system with hard links. If the runtime copy fails, the durable copy is removed. Without durable storage (no attempt has run yet), it writes only the runtime copy and says so. Errors give the path and cause.
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
Keep backups outside the instance folder, and back up the instance files and credentials separately. To restore, stop the orchestrator and restore both files of one backup:
|
|
36
50
|
|
|
37
|
-
|
|
51
|
+
1. Move `.usine/runtime.sqlite` and `.usine/durable.sqlite` aside, with their `-wal` and `-shm` files.
|
|
52
|
+
2. Copy the runtime backup to `.usine/runtime.sqlite` and the `.durable` backup to `.usine/durable.sqlite`.
|
|
53
|
+
3. Restart with the matching instance configuration and engine version.
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
An attempt that was running at backup time reattaches if its checkout under `.usine/workers` still exists. Without its checkout or the durable copy, it fails as interrupted and the run retries. An older backup has an older dedup ledger, so work done after it can run again.
|
|
56
|
+
|
|
57
|
+
`usine export-run` writes one run to a local JSON file:
|
|
40
58
|
|
|
41
59
|
```sh
|
|
42
60
|
usine export-run <runId>
|
|
43
61
|
usine export-run <runId> --output /path/to/evidence.json
|
|
44
62
|
```
|
|
45
63
|
|
|
46
|
-
The default
|
|
47
|
-
|
|
48
|
-
The versioned JSON contains `exportedAt`, the full durable Run `snapshot` (Event, effective task and Agent, repository revision, Step result, and all Attempts), and every saved transcript envelope for those Attempts in append order. The snapshot and transcripts come from one database transaction, including history beyond the drawer's current page. Active Runs continue accumulating history, so export a settled Run for its final evidence.
|
|
64
|
+
The default is `run-<encoded-runId>.json` in the current directory, with characters such as `/` percent-encoded; the output directory must exist. The command writes a complete owner-only file, never overwrites one, and prints its path. A missing run, unreadable store, encoding failure, or write failure exits nonzero.
|
|
49
65
|
|
|
50
|
-
|
|
66
|
+
The versioned JSON has `exportedAt`, the full run `snapshot` (event, effective task and agent, revision, step result, and all attempts), and every saved transcript envelope in append order, from one transaction, including history past the drawer's page. Active runs keep adding history, so export a settled run. The export opens `.usine/runtime.sqlite` read-only, without creating tables, migrating, starting attempts, or polling. Transcripts can contain repo content and tool output; check the file before you share it. For a restorable database, use `usine backup` ([ADR 0010](../../docs/adr/0010-single-sqlite-runtime-store.md)).
|
|
51
67
|
|
|
52
|
-
##
|
|
68
|
+
## Stop the server
|
|
53
69
|
|
|
54
|
-
|
|
70
|
+
Press Ctrl+C or send `SIGTERM` once to stop polling and drain admitted attempts. The server waits up to `max_attempt_minutes` from the request, or indefinitely if unset; a second signal stops the wait. Queued runs and automatic retries stay saved, and an attempt cut off by the deadline uses normal restart recovery. Start the next `usine up` after the process exits.
|
|
55
71
|
|
|
56
|
-
|
|
72
|
+
To clear a handled failure, select **Mark resolved** in the run drawer, opened from Needs attention or run history. Resolution survives restarts and keeps the failed outcome, transcript, and retry. A later failed attempt asks for attention again. After a question the button reads **Mark answered**; after a question or blocker, the loop then acts on the item's newest state.
|
package/dist/backend.d.ts
CHANGED
|
@@ -1,29 +1,52 @@
|
|
|
1
|
-
import type { LoadedInstance } from "@usine/config/instance";
|
|
2
1
|
import { LoopName } from "@usine/core/loop";
|
|
3
2
|
import { CycleBudgetAskId } from "@usine/core/cycle-budget";
|
|
3
|
+
import { AttemptId } from "@usine/core/attempt";
|
|
4
4
|
import { RunId } from "@usine/core/run";
|
|
5
|
-
import {
|
|
5
|
+
import { AgentName } from "@usine/core/agent";
|
|
6
|
+
import type { RunAgainResult } from "@usine/engine/engine";
|
|
7
|
+
import type { CurrentInstance, InstanceReload, InstanceReloadError } from "@usine/orchestrator/current-instance";
|
|
8
|
+
import type { WorkerName } from "@usine/core/worker";
|
|
9
|
+
import { type BoardMove, type ConfigurationView, type DeskView, type FactoryOverview, type RunDetailView, type RunGitHubChangesView, type TranscriptRequest, type Operator, type OperatorConfiguration, type OperatorError, type OperatorObservation, type EditAndRetry, type SessionIntervention, type TaskView } from "@usine/orchestrator/operator";
|
|
10
|
+
import type { ItemNumber } from "@usine/core/item";
|
|
11
|
+
import type { RepositoryName } from "@usine/core/repository";
|
|
6
12
|
import { Context, Effect, Layer, Schema } from "effect";
|
|
7
13
|
declare const BackendUnavailableError_base: Schema.Class<BackendUnavailableError, Schema.TaggedStruct<"BackendUnavailableError", {
|
|
8
14
|
readonly message: Schema.String;
|
|
9
15
|
}>, import("effect/Cause").YieldableError>;
|
|
10
16
|
export declare class BackendUnavailableError extends BackendUnavailableError_base {
|
|
11
17
|
}
|
|
12
|
-
export type BackendError = OperatorError | BackendUnavailableError;
|
|
18
|
+
export type BackendError = OperatorError | BackendUnavailableError | InstanceReloadError;
|
|
13
19
|
declare const FactoryBackend_base: Context.ServiceClass<FactoryBackend, "@usine/server/FactoryBackend", {
|
|
14
20
|
readonly overview: Effect.Effect<FactoryOverview, BackendError>;
|
|
15
21
|
readonly detail: (runId: RunId, request: TranscriptRequest) => Effect.Effect<RunDetailView, BackendError>;
|
|
22
|
+
readonly githubChanges: (runId: RunId) => Effect.Effect<RunGitHubChangesView, BackendError>;
|
|
16
23
|
readonly approve: (runId: RunId) => Effect.Effect<void, BackendError>;
|
|
17
24
|
readonly resetCycleBudget: (askId: CycleBudgetAskId) => Effect.Effect<void, BackendError>;
|
|
25
|
+
readonly resolveFailure: (runId: RunId, attemptId: AttemptId) => Effect.Effect<void, BackendError>;
|
|
18
26
|
readonly retry: (runId: RunId) => Effect.Effect<void, BackendError>;
|
|
27
|
+
readonly agents: Effect.Effect<ReadonlyArray<AgentName>, BackendError>;
|
|
28
|
+
readonly editAndRetry: (runId: RunId, edit: EditAndRetry) => Effect.Effect<void, BackendError>;
|
|
29
|
+
readonly runAgain: (runId: RunId, edit: EditAndRetry) => Effect.Effect<RunAgainResult, BackendError>;
|
|
30
|
+
readonly intervene: (runId: RunId, input: SessionIntervention) => Effect.Effect<void, BackendError>;
|
|
19
31
|
readonly cancel: (runId: RunId) => Effect.Effect<void, BackendError>;
|
|
32
|
+
readonly discardLeftovers: (worker: WorkerName) => Effect.Effect<void, BackendError>;
|
|
20
33
|
readonly moveBoardItem: (move: BoardMove) => Effect.Effect<void, BackendError>;
|
|
21
34
|
readonly pause: (loop: LoopName) => Effect.Effect<void, BackendError>;
|
|
22
35
|
readonly unpause: (loop: LoopName) => Effect.Effect<void, BackendError>;
|
|
36
|
+
readonly reload: Effect.Effect<InstanceReload, BackendError>;
|
|
37
|
+
readonly task: (repository: RepositoryName, item: ItemNumber) => Effect.Effect<TaskView, BackendError>;
|
|
38
|
+
readonly configuration: Effect.Effect<ConfigurationView, BackendError>;
|
|
39
|
+
readonly desk: Effect.Effect<DeskView, BackendError>;
|
|
23
40
|
}>;
|
|
24
41
|
export declare class FactoryBackend extends FactoryBackend_base {
|
|
25
42
|
}
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
type BackendInstance = Pick<CurrentInstance["Service"], "get" | "reload">;
|
|
44
|
+
/** Provides the backend of a running factory, which reads the current instance on each request. */
|
|
45
|
+
export declare const liveBackendLayer: (instances: BackendInstance, operator: Operator["Service"], configuration: OperatorConfiguration["Service"]) => Layer.Layer<FactoryBackend>;
|
|
46
|
+
/**
|
|
47
|
+
* Provides the setup-mode backend, where only the overview, configuration, and reload work. Other
|
|
48
|
+
* operations fail with `BackendUnavailableError`.
|
|
49
|
+
*/
|
|
50
|
+
export declare const needsSetupBackendLayer: (instances: BackendInstance, errorTag: string, observation: OperatorObservation["Service"], configuration: OperatorConfiguration["Service"]) => Layer.Layer<FactoryBackend>;
|
|
28
51
|
export {};
|
|
29
52
|
//# sourceMappingURL=backend.d.ts.map
|
package/dist/backend.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|
package/dist/backend.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { LoopName } from "@usine/core/loop";
|
|
2
2
|
import { CycleBudgetAskId } from "@usine/core/cycle-budget";
|
|
3
|
+
import { AttemptId } from "@usine/core/attempt";
|
|
3
4
|
import { RunId } from "@usine/core/run";
|
|
5
|
+
import { AgentName } from "@usine/core/agent";
|
|
4
6
|
import { NeedsSetupOverview, } from "@usine/orchestrator/operator";
|
|
5
7
|
import { Context, Effect, Layer, Schema } from "effect";
|
|
6
8
|
export class BackendUnavailableError extends Schema.TaggedError("@usine/server/BackendUnavailableError")("BackendUnavailableError", {
|
|
@@ -9,35 +11,61 @@ export class BackendUnavailableError extends Schema.TaggedError("@usine/server/B
|
|
|
9
11
|
}
|
|
10
12
|
export class FactoryBackend extends Context.Service()("@usine/server/FactoryBackend") {
|
|
11
13
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const rosterOf = (instances) => Effect.map(instances.get, ({ agents }) => agents.map(({ name }) => name));
|
|
15
|
+
/** Provides the backend of a running factory, which reads the current instance on each request. */
|
|
16
|
+
export const liveBackendLayer = (instances, operator, configuration) => Layer.succeed(FactoryBackend)({
|
|
17
|
+
overview: Effect.flatMap(instances.get, operator.overview),
|
|
18
|
+
detail: (runId, request) => Effect.flatMap(instances.get, (instance) => operator.detail(instance, runId, request)),
|
|
19
|
+
githubChanges: operator.githubChanges,
|
|
20
|
+
approve: (runId) => Effect.flatMap(instances.get, (instance) => operator.approve(instance, runId)),
|
|
16
21
|
resetCycleBudget: operator.resetCycleBudget,
|
|
17
|
-
retry: operator.retry,
|
|
22
|
+
retry: (runId) => Effect.flatMap(instances.get, (instance) => operator.retry(instance, runId)),
|
|
23
|
+
resolveFailure: operator.resolveFailure,
|
|
24
|
+
agents: rosterOf(instances),
|
|
25
|
+
editAndRetry: (runId, edit) => Effect.flatMap(instances.get, (instance) => operator.editAndRetry(instance, runId, edit)),
|
|
26
|
+
runAgain: (runId, edit) => Effect.flatMap(instances.get, (instance) => operator.runAgain(instance, runId, edit)),
|
|
27
|
+
intervene: (runId, input) => Effect.flatMap(instances.get, (instance) => operator.intervene(instance, runId, input)),
|
|
18
28
|
cancel: operator.cancel,
|
|
19
|
-
|
|
29
|
+
discardLeftovers: operator.discardLeftovers,
|
|
30
|
+
moveBoardItem: (move) => Effect.flatMap(instances.get, (instance) => operator.moveBoardItem(instance, move)),
|
|
20
31
|
pause: (loop) => operator.pause(loop).pipe(Effect.asVoid),
|
|
21
32
|
unpause: (loop) => operator.unpause(loop).pipe(Effect.asVoid),
|
|
33
|
+
reload: instances.reload,
|
|
34
|
+
task: (repository, item) => Effect.flatMap(instances.get, (instance) => operator.task(instance, repository, item)),
|
|
35
|
+
configuration: Effect.flatMap(instances.get, configuration.view),
|
|
36
|
+
desk: Effect.flatMap(instances.get, operator.desk),
|
|
22
37
|
});
|
|
23
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Provides the setup-mode backend, where only the overview, configuration, and reload work. Other
|
|
40
|
+
* operations fail with `BackendUnavailableError`.
|
|
41
|
+
*/
|
|
42
|
+
export const needsSetupBackendLayer = (instances, errorTag, observation, configuration) => {
|
|
24
43
|
const unavailable = new BackendUnavailableError({
|
|
25
|
-
message: "GitHub App
|
|
44
|
+
message: "The GitHub App is not set up. Set it up to use operator actions.",
|
|
26
45
|
});
|
|
27
|
-
const overview = observation
|
|
28
|
-
.overview(instance)
|
|
29
|
-
.pipe(Effect.map(({ projects, loops, needsYou }) => new NeedsSetupOverview({ errorTag, projects, loops, needsYou })));
|
|
46
|
+
const overview = Effect.flatMap(instances.get, observation.overview).pipe(Effect.map(({ projects, loops, needsYou }) => new NeedsSetupOverview({ errorTag, projects, loops, needsYou })));
|
|
30
47
|
const fail = () => Effect.fail(unavailable);
|
|
31
48
|
return Layer.succeed(FactoryBackend)({
|
|
32
49
|
overview,
|
|
33
|
-
detail: observation.detail,
|
|
50
|
+
detail: (runId, request) => Effect.flatMap(instances.get, (instance) => observation.detail(instance, runId, request)),
|
|
51
|
+
githubChanges: fail,
|
|
34
52
|
approve: fail,
|
|
35
53
|
resetCycleBudget: fail,
|
|
36
54
|
retry: fail,
|
|
55
|
+
resolveFailure: fail,
|
|
56
|
+
agents: rosterOf(instances),
|
|
57
|
+
editAndRetry: fail,
|
|
58
|
+
runAgain: fail,
|
|
59
|
+
intervene: fail,
|
|
37
60
|
cancel: fail,
|
|
61
|
+
discardLeftovers: fail,
|
|
38
62
|
moveBoardItem: fail,
|
|
39
63
|
pause: fail,
|
|
40
64
|
unpause: fail,
|
|
65
|
+
reload: instances.reload,
|
|
66
|
+
task: (repository, item) => Effect.flatMap(instances.get, (instance) => observation.task(instance, repository, item)),
|
|
67
|
+
configuration: Effect.flatMap(instances.get, configuration.view),
|
|
68
|
+
desk: Effect.flatMap(instances.get, observation.desk),
|
|
41
69
|
});
|
|
42
70
|
};
|
|
43
71
|
//# sourceMappingURL=backend.js.map
|
package/dist/backend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|
package/dist/backup.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { Effect, Path } from "effect";
|
|
2
|
-
/**
|
|
3
|
-
|
|
1
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* Copies the runtime database to `output` and the durable storage next to it, durable storage
|
|
4
|
+
* first. If the runtime copy fails, removes the durable copy.
|
|
5
|
+
*/
|
|
6
|
+
export declare const backup: (output: string) => Effect.Effect<void, import("@usine/durable/backup").DurableStorageBackupError | import("@usine/engine/local-sqlite").RuntimeStoreBackupError, FileSystem.FileSystem | Path.Path>;
|
|
4
7
|
//# sourceMappingURL=backup.d.ts.map
|
package/dist/backup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAQ3D;;;GAGG;AACH,eAAO,MAAM,MAAM,sMA6BjB,CAAC"}
|
package/dist/backup.js
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { InstanceDirectory } from "@usine/core/instance";
|
|
2
|
+
import { backup as backupDurableStorage } from "@usine/durable/backup";
|
|
2
3
|
import { backup as backupDatabase } from "@usine/engine/local-sqlite";
|
|
3
|
-
import { Console, Effect, Path } from "effect";
|
|
4
|
-
/**
|
|
4
|
+
import { Console, Effect, FileSystem, Path } from "effect";
|
|
5
|
+
/** Returns the durable storage copy path: `backup.sqlite` becomes `backup.durable.sqlite`. */
|
|
6
|
+
const durableDestination = (path, destination) => {
|
|
7
|
+
const { dir, name, ext } = path.parse(destination);
|
|
8
|
+
return path.join(dir, `${name}.durable${ext === "" ? ".sqlite" : ext}`);
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Copies the runtime database to `output` and the durable storage next to it, durable storage
|
|
12
|
+
* first. If the runtime copy fails, removes the durable copy.
|
|
13
|
+
*/
|
|
5
14
|
export const backup = Effect.fn("Cli.backup")(function* (output) {
|
|
6
15
|
const directory = InstanceDirectory.make(process.cwd());
|
|
16
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
7
17
|
const path = yield* Path.Path;
|
|
8
18
|
const destination = path.resolve(output);
|
|
9
|
-
|
|
10
|
-
yield*
|
|
11
|
-
yield*
|
|
19
|
+
const durable = durableDestination(path, destination);
|
|
20
|
+
yield* Effect.annotateCurrentSpan({ instanceDirectory: directory, destination, durable });
|
|
21
|
+
const copied = yield* backupDurableStorage(directory, durable);
|
|
22
|
+
yield* backupDatabase(directory, destination).pipe(Effect.tapError(() => copied
|
|
23
|
+
? fileSystem.remove(durable).pipe(Effect.tapError((error) => Effect.logWarning("Could not remove the durable copy of a failed backup", {
|
|
24
|
+
durable,
|
|
25
|
+
message: error.message,
|
|
26
|
+
})), Effect.ignore)
|
|
27
|
+
: Effect.void));
|
|
28
|
+
yield* Console.log(`Backed up the runtime store to ${destination}`);
|
|
29
|
+
yield* Console.log(copied
|
|
30
|
+
? `Backed up the durable storage to ${durable}`
|
|
31
|
+
: "No durable storage to back up: .usine/durable.sqlite does not exist");
|
|
12
32
|
});
|
|
13
33
|
//# sourceMappingURL=backup.js.map
|
package/dist/backup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,MAAM,IAAI,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE3D,8FAA8F;AAC9F,MAAM,kBAAkB,GAAG,CAAC,IAAe,EAAE,WAAmB,EAAU,EAAE;IAC1E,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,WAAW,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AAC1E,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAc;IACrE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACtD,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,oBAAoB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/D,KAAK,CAAC,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,IAAI,CAChD,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CACnB,MAAM;QACJ,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAC7B,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,UAAU,CAAC,sDAAsD,EAAE;YACxE,OAAO;YACP,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CACH,EACD,MAAM,CAAC,MAAM,CACd;QACH,CAAC,CAAC,MAAM,CAAC,IAAI,CAChB,CACF,CAAC;IACF,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,kCAAkC,WAAW,EAAE,CAAC,CAAC;IACpE,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAChB,MAAM;QACJ,CAAC,CAAC,oCAAoC,OAAO,EAAE;QAC/C,CAAC,CAAC,qEAAqE,CAC1E,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { Duration, Effect, FileSystem, Option, Schema } from "effect";
|
|
|
2
2
|
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
3
3
|
import type { ServeError } from "effect/unstable/http/HttpServerError";
|
|
4
4
|
export declare const DEFAULT_PORT = 7331;
|
|
5
|
+
/** The address that the HTTP server listens on and the port check probes. */
|
|
6
|
+
export declare const SERVER_HOST = "127.0.0.1";
|
|
5
7
|
export declare const BROWSER_OPEN_TIMEOUT = "2 seconds";
|
|
6
8
|
export declare const Port: Schema.brand<Schema.Int, "ServerPort">;
|
|
7
9
|
export type Port = typeof Port.Type;
|
|
8
|
-
export declare const CLI_USAGE = "Usage:\n usine up [--port <port>] [--allow-volatile-instance]\n usine backup <path>\n usine export-run <runId> [--output <path>]\n usine --help\n usine --version\n\nOptions:\n -p, --port <port> Listen on this port (default: 7331)\n --allow-volatile-instance Start even when the instance folder
|
|
10
|
+
export declare const CLI_USAGE = "Usage:\n usine up [--port <port>] [--allow-volatile-instance]\n usine backup <path>\n usine export-run <runId> [--output <path>]\n usine --help\n usine --version\n\nOptions:\n -p, --port <port> Listen on this port (default: 7331)\n --allow-volatile-instance Start even when the instance folder is in a temporary directory\n -o, --output <path> Write the run evidence to this file (default: run-<encoded-runId>.json)\n -h, --help Print this help\n -v, --version Print the installed @usine/server version\n\nEnvironment:\n USINE_PORT Listen on this port if --port is not set\n USINE_ALLOW_VOLATILE_INSTANCE Set to 1 for the same effect as --allow-volatile-instance";
|
|
9
11
|
declare const CliUsageError_base: Schema.Class<CliUsageError, Schema.TaggedStruct<"CliUsageError", {
|
|
10
12
|
readonly message: Schema.String;
|
|
11
13
|
}>, import("effect/Cause").YieldableError>;
|
|
@@ -18,7 +20,10 @@ export declare class CliVersionError extends CliVersionError_base {
|
|
|
18
20
|
}
|
|
19
21
|
declare const PortInUseError_base: Schema.Class<PortInUseError, Schema.TaggedStruct<"PortInUseError", {
|
|
20
22
|
readonly port: Schema.brand<Schema.Int, "ServerPort">;
|
|
23
|
+
/** The PID of the listening process, or `None` if lsof can't find it. */
|
|
24
|
+
readonly holder: Schema.Option<Schema.Int>;
|
|
21
25
|
}>, import("effect/Cause").YieldableError>;
|
|
26
|
+
/** Another process is listening on the HTTP port. */
|
|
22
27
|
export declare class PortInUseError extends PortInUseError_base {
|
|
23
28
|
}
|
|
24
29
|
declare const ServerStartError_base: Schema.Class<ServerStartError, Schema.TaggedStruct<"ServerStartError", {
|
|
@@ -31,7 +36,7 @@ declare const VolatileInstanceDirectoryError_base: Schema.Class<VolatileInstance
|
|
|
31
36
|
readonly directory: Schema.String;
|
|
32
37
|
readonly root: Schema.String;
|
|
33
38
|
}>, import("effect/Cause").YieldableError>;
|
|
34
|
-
/**
|
|
39
|
+
/** The instance folder is in a directory the OS may clear, which would delete the run history. */
|
|
35
40
|
export declare class VolatileInstanceDirectoryError extends VolatileInstanceDirectoryError_base {
|
|
36
41
|
}
|
|
37
42
|
declare const BrowserOpenError_base: Schema.Class<BrowserOpenError, Schema.TaggedStruct<"BrowserOpenError", {
|
|
@@ -73,15 +78,23 @@ export declare const CliCommand: Schema.Union<readonly [typeof HelpCommand, type
|
|
|
73
78
|
export type CliCommand = typeof CliCommand.Type;
|
|
74
79
|
/** Parses process arguments and the environment into one validated CLI command. */
|
|
75
80
|
export declare const parseCli: (args: readonly string[], environment: CliEnvironment) => Effect.Effect<BackupCommand | ExportRunCommand | HelpCommand | UpCommand | VersionCommand, CliUsageError, never>;
|
|
76
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Returns the directories the OS may clear: `os.tmpdir()`, `$TMPDIR`, and common temp locations.
|
|
83
|
+
*/
|
|
77
84
|
export declare const volatileRoots: (environmentTmpdir: Option.Option<string>) => ReadonlyArray<string>;
|
|
78
|
-
/** Returns the volatile root
|
|
85
|
+
/** Returns the volatile root that contains `directory`, if any, without resolving symlinks. */
|
|
79
86
|
export declare const volatileRoot: (directory: string, roots: ReadonlyArray<string>) => Option.Option<string>;
|
|
80
|
-
/** Reads the version of this installed server from its package.json. */
|
|
81
87
|
export declare const packageVersion: () => Effect.Effect<string, CliVersionError, FileSystem.FileSystem>;
|
|
82
|
-
/**
|
|
88
|
+
/** Returns `PortInUseError` for `EADDRINUSE`, otherwise `ServerStartError`. */
|
|
83
89
|
export declare const serverStartError: (port: Port, error: ServeError) => PortInUseError | ServerStartError;
|
|
84
|
-
/**
|
|
90
|
+
/** Fails with `PortInUseError`, with the listening PID if found. */
|
|
91
|
+
export declare const portInUse: (port: number & import("effect/Brand").Brand<"ServerPort">) => Effect.Effect<never, PortInUseError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
92
|
+
/**
|
|
93
|
+
* Fails with `PortInUseError` if another process is listening on `port`. Call it before `usine up`
|
|
94
|
+
* builds its runtime.
|
|
95
|
+
*/
|
|
96
|
+
export declare const refuseTakenPort: (port: number & import("effect/Brand").Brand<"ServerPort">) => Effect.Effect<undefined, PortInUseError | ServerStartError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
97
|
+
/** Opens the setup page in a browser. Call it only after the HTTP server has bound. */
|
|
85
98
|
export declare const openBrowser: (url: string, timeout?: Duration.Input | undefined) => Effect.Effect<undefined, BrowserOpenError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
86
99
|
export {};
|
|
87
100
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyB,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7F,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAEvE,eAAO,MAAM,YAAY,OAAQ,CAAC;AAClC,6EAA6E;AAC7E,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAGhD,eAAO,MAAM,IAAI,wCAEiB,CAAC;AACnC,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC;AAIpC,eAAO,MAAM,SAAS,ovBAgBqE,CAAC;;;;AAE5F,qBAAa,aAAc,SAAQ,kBAGlC;CAAG;;;;AAEJ,qBAAa,eAAgB,SAAQ,oBAEW;CAAG;;;IAOjD,yEAAyE;;;AAL3E,qDAAqD;AACrD,qBAAa,cAAe,SAAQ,mBAMlC;CAAG;;;;;AAEL,qBAAa,gBAAiB,SAAQ,qBAEuB;CAAG;;;;;AAEhE,kGAAkG;AAClG,qBAAa,8BAA+B,SAAQ,mCAKlD;CAAG;;;;;AAEL,qBAAa,gBAAiB,SAAQ,qBAKpC;CAAG;;AAEL,qBAAa,WAAY,SAAQ,gBAGhC;CAAG;;AAEJ,qBAAa,cAAe,SAAQ,mBAEb;CAAG;;;;;AAE1B,qBAAa,SAAU,SAAQ,cAG9B;CAAG;;;;;AAEJ,4EAA4E;AAC5E,qBAAa,gBAAiB,SAAQ,qBAKpC;CAAG;;;;AAEL,0EAA0E;AAC1E,qBAAa,aAAc,SAAQ,kBAGlC;CAAG;AAEJ,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,UAAU,qIAMrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAyEhD,mFAAmF;AACnF,eAAO,MAAM,QAAQ,4KAmBnB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,sBAAuB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAG,aAAa,CAAC,MAAM,CAW5D,CAAC;AAKlC,+FAA+F;AAC/F,eAAO,MAAM,YAAY,cACZ,MAAM,SACV,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,CAGtB,CAAC;AAMF,eAAO,MAAM,cAAc,qEAUzB,CAAC;AAIH,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,SAAU,IAAI,SAAS,UAAU,sCAGN,CAAC;AA6BzD,oEAAoE;AACpE,eAAO,MAAM,SAAS,8IAOpB,CAAC;AAkBH;;;GAGG;AACH,eAAO,MAAM,eAAe,qKAE1B,CAAC;AAUH,uFAAuF;AACvF,eAAO,MAAM,WAAW,4IAyBtB,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { createServer } from "node:net";
|
|
1
2
|
import { tmpdir } from "node:os";
|
|
2
3
|
import { resolve, sep } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { RunId } from "@usine/core/run";
|
|
5
|
-
import { Console, Duration, Effect, FileSystem, Option, Schema } from "effect";
|
|
6
|
+
import { Array as Arr, Console, Duration, Effect, FileSystem, Option, Schema } from "effect";
|
|
6
7
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
7
8
|
export const DEFAULT_PORT = 7_331;
|
|
9
|
+
/** The address that the HTTP server listens on and the port check probes. */
|
|
10
|
+
export const SERVER_HOST = "127.0.0.1";
|
|
8
11
|
export const BROWSER_OPEN_TIMEOUT = "2 seconds";
|
|
12
|
+
const PORT_HOLDER_TIMEOUT = "2 seconds";
|
|
9
13
|
export const Port = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65_535 }, { message: "Expected a TCP port" })).pipe(Schema.brand("ServerPort"));
|
|
10
14
|
const PortFromString = Schema.NumberFromString.pipe(Schema.decodeTo(Port));
|
|
11
15
|
export const CLI_USAGE = `Usage:
|
|
@@ -17,23 +21,28 @@ export const CLI_USAGE = `Usage:
|
|
|
17
21
|
|
|
18
22
|
Options:
|
|
19
23
|
-p, --port <port> Listen on this port (default: 7331)
|
|
20
|
-
--allow-volatile-instance Start even when the instance folder
|
|
21
|
-
-o, --output <path> Write
|
|
22
|
-
-h, --help Print this
|
|
23
|
-
-v, --version Print the
|
|
24
|
+
--allow-volatile-instance Start even when the instance folder is in a temporary directory
|
|
25
|
+
-o, --output <path> Write the run evidence to this file (default: run-<encoded-runId>.json)
|
|
26
|
+
-h, --help Print this help
|
|
27
|
+
-v, --version Print the installed @usine/server version
|
|
24
28
|
|
|
25
29
|
Environment:
|
|
26
|
-
USINE_PORT Listen on this port
|
|
27
|
-
USINE_ALLOW_VOLATILE_INSTANCE
|
|
30
|
+
USINE_PORT Listen on this port if --port is not set
|
|
31
|
+
USINE_ALLOW_VOLATILE_INSTANCE Set to 1 for the same effect as --allow-volatile-instance`;
|
|
28
32
|
export class CliUsageError extends Schema.TaggedError("@usine/server/CliUsageError")("CliUsageError", { message: Schema.String }) {
|
|
29
33
|
}
|
|
30
34
|
export class CliVersionError extends Schema.TaggedError("@usine/server/CliVersionError")("CliVersionError", { cause: Schema.Defect() }) {
|
|
31
35
|
}
|
|
32
|
-
|
|
36
|
+
/** Another process is listening on the HTTP port. */
|
|
37
|
+
export class PortInUseError extends Schema.TaggedError("@usine/server/PortInUseError")("PortInUseError", {
|
|
38
|
+
port: Port,
|
|
39
|
+
/** The PID of the listening process, or `None` if lsof can't find it. */
|
|
40
|
+
holder: Schema.Option(Schema.Int),
|
|
41
|
+
}) {
|
|
33
42
|
}
|
|
34
43
|
export class ServerStartError extends Schema.TaggedError("@usine/server/ServerStartError")("ServerStartError", { port: Port, cause: Schema.Defect() }) {
|
|
35
44
|
}
|
|
36
|
-
/**
|
|
45
|
+
/** The instance folder is in a directory the OS may clear, which would delete the run history. */
|
|
37
46
|
export class VolatileInstanceDirectoryError extends Schema.TaggedError("@usine/server/VolatileInstanceDirectoryError")("VolatileInstanceDirectoryError", {
|
|
38
47
|
directory: Schema.String,
|
|
39
48
|
root: Schema.String,
|
|
@@ -137,7 +146,9 @@ export const parseCli = Effect.fn("Cli.parse")((args, environment) => {
|
|
|
137
146
|
return Effect.fail(usageError());
|
|
138
147
|
return upCommand(args.slice(1), environment);
|
|
139
148
|
});
|
|
140
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Returns the directories the OS may clear: `os.tmpdir()`, `$TMPDIR`, and common temp locations.
|
|
151
|
+
*/
|
|
141
152
|
export const volatileRoots = (environmentTmpdir) => [
|
|
142
153
|
...new Set([
|
|
143
154
|
tmpdir(),
|
|
@@ -150,13 +161,12 @@ export const volatileRoots = (environmentTmpdir) => [
|
|
|
150
161
|
.filter((root) => root.length > 0)
|
|
151
162
|
.map((root) => resolve(root));
|
|
152
163
|
const isUnder = (directory, root) => directory === root || directory.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
|
|
153
|
-
/** Returns the volatile root
|
|
164
|
+
/** Returns the volatile root that contains `directory`, if any, without resolving symlinks. */
|
|
154
165
|
export const volatileRoot = (directory, roots) => {
|
|
155
166
|
const resolved = resolve(directory);
|
|
156
167
|
return Option.fromNullishOr(roots.find((root) => isUnder(resolved, root)));
|
|
157
168
|
};
|
|
158
169
|
const PackageMetadataFromJson = Schema.fromJsonString(Schema.Struct({ version: Schema.NonEmptyString }));
|
|
159
|
-
/** Reads the version of this installed server from its package.json. */
|
|
160
170
|
export const packageVersion = Effect.fn("Cli.packageVersion")(function* () {
|
|
161
171
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
162
172
|
const packagePath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
@@ -167,10 +177,50 @@ export const packageVersion = Effect.fn("Cli.packageVersion")(function* () {
|
|
|
167
177
|
return metadata.version;
|
|
168
178
|
});
|
|
169
179
|
const AddressInUse = Schema.Struct({ code: Schema.Literal("EADDRINUSE") });
|
|
170
|
-
/**
|
|
180
|
+
/** Returns `PortInUseError` for `EADDRINUSE`, otherwise `ServerStartError`. */
|
|
171
181
|
export const serverStartError = (port, error) => Schema.is(AddressInUse)(error.cause)
|
|
172
|
-
? new PortInUseError({ port })
|
|
182
|
+
? new PortInUseError({ port, holder: Option.none() })
|
|
173
183
|
: new ServerStartError({ port, cause: error.cause });
|
|
184
|
+
const ProcessIdFromString = Schema.NumberFromString.pipe(Schema.decodeTo(Schema.Int));
|
|
185
|
+
/**
|
|
186
|
+
* Returns the PID listening on `port` from lsof, or `None` if lsof is missing or can't see the
|
|
187
|
+
* process.
|
|
188
|
+
*/
|
|
189
|
+
const portHolder = Effect.fn("Cli.portHolder")(function* (port) {
|
|
190
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
191
|
+
const lines = yield* spawner
|
|
192
|
+
.lines(ChildProcess.make("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
|
|
193
|
+
stdin: "ignore",
|
|
194
|
+
stderr: "ignore",
|
|
195
|
+
}))
|
|
196
|
+
.pipe(Effect.timeout(PORT_HOLDER_TIMEOUT), Effect.tapError((cause) => Effect.logDebug("Could not look up the process listening on the port", { port, cause })), Effect.orElseSucceed(() => []));
|
|
197
|
+
return Arr.findFirst(lines, (line) => Schema.decodeUnknownOption(ProcessIdFromString)(line.trim()));
|
|
198
|
+
});
|
|
199
|
+
/** Fails with `PortInUseError`, with the listening PID if found. */
|
|
200
|
+
export const portInUse = Effect.fn("Cli.portInUse")(function* (port) {
|
|
201
|
+
const holder = yield* portHolder(port);
|
|
202
|
+
yield* Effect.logWarning("Another process listens on the HTTP port", {
|
|
203
|
+
port,
|
|
204
|
+
holder: Option.getOrUndefined(holder),
|
|
205
|
+
});
|
|
206
|
+
return yield* new PortInUseError({ port, holder });
|
|
207
|
+
});
|
|
208
|
+
const portTaken = (port) => Effect.callback((resume) => {
|
|
209
|
+
const server = createServer();
|
|
210
|
+
server.once("error", (cause) => resume(Schema.is(AddressInUse)(cause)
|
|
211
|
+
? Effect.succeed(true)
|
|
212
|
+
: Effect.fail(new ServerStartError({ port, cause }))));
|
|
213
|
+
server.listen({ host: SERVER_HOST, port }, () => server.close(() => resume(Effect.succeed(false))));
|
|
214
|
+
return Effect.sync(() => server.close());
|
|
215
|
+
});
|
|
216
|
+
/**
|
|
217
|
+
* Fails with `PortInUseError` if another process is listening on `port`. Call it before `usine up`
|
|
218
|
+
* builds its runtime.
|
|
219
|
+
*/
|
|
220
|
+
export const refuseTakenPort = Effect.fn("Cli.refuseTakenPort")(function* (port) {
|
|
221
|
+
if (yield* portTaken(port))
|
|
222
|
+
return yield* portInUse(port);
|
|
223
|
+
});
|
|
174
224
|
const browserCommand = (url) => {
|
|
175
225
|
if (process.platform === "darwin")
|
|
176
226
|
return { command: "open", args: [url] };
|
|
@@ -179,7 +229,7 @@ const browserCommand = (url) => {
|
|
|
179
229
|
}
|
|
180
230
|
return { command: "xdg-open", args: [url] };
|
|
181
231
|
};
|
|
182
|
-
/** Opens the setup page in a browser
|
|
232
|
+
/** Opens the setup page in a browser. Call it only after the HTTP server has bound. */
|
|
183
233
|
export const openBrowser = Effect.fn("Cli.openBrowser")(function* (url, timeout = BROWSER_OPEN_TIMEOUT) {
|
|
184
234
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
185
235
|
const { command, args } = browserCommand(url);
|