slopkit 0.2.0 → 0.4.0-rc.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/DEPS.md +5 -3
- package/README.md +103 -1
- package/RULES.md +26 -6
- package/dist/api.d.ts +93 -0
- package/dist/api.js +111 -0
- package/dist/client.d.ts +21 -4
- package/dist/client.js +21 -2
- package/dist/conformance/agent-access.test.d.ts +1 -0
- package/dist/conformance/agent-access.test.js +74 -0
- package/dist/conformance/client.test.js +7 -7
- package/dist/conformance/fixtures.d.ts +1 -0
- package/dist/conformance/fixtures.js +5 -1
- package/dist/conformance/outbox-bound.test.d.ts +1 -0
- package/dist/conformance/outbox-bound.test.js +268 -0
- package/dist/conformance/paging-state.test.js +1 -0
- package/dist/conformance/push-bytes.test.d.ts +1 -0
- package/dist/conformance/push-bytes.test.js +174 -0
- package/dist/conformance/push-refusal.test.d.ts +1 -0
- package/dist/conformance/push-refusal.test.js +644 -0
- package/dist/conformance/states.test.js +19 -8
- package/dist/contract.d.ts +19 -6
- package/dist/contract.js +76 -9
- package/dist/server/auth.d.ts +4 -3
- package/dist/server/auth.js +14 -6
- package/dist/server/cli.d.ts +117 -0
- package/dist/server/cli.js +1534 -0
- package/dist/server/config.d.ts +4 -1
- package/dist/server/config.js +2 -2
- package/dist/server/keys.d.ts +17 -0
- package/dist/server/keys.js +127 -0
- package/dist/server/pull.js +158 -67
- package/dist/server/store.d.ts +101 -0
- package/dist/server/store.js +347 -0
- package/dist/server/sync.d.ts +158 -0
- package/dist/server/sync.js +293 -0
- package/dist/server/web.d.ts +62 -9
- package/dist/server/web.js +260 -35
- package/dist/states.d.ts +52 -4
- package/dist/states.js +20 -0
- package/dist/sync.d.ts +86 -2
- package/dist/sync.js +277 -59
- package/dist/test/agents.d.ts +19 -0
- package/dist/test/agents.js +62 -0
- package/dist/test/page.d.ts +91 -0
- package/dist/test/page.js +763 -0
- package/dist/test/states.d.ts +44 -7
- package/dist/test/states.js +152 -0
- package/dist/web/replica.d.ts +7 -0
- package/dist/web/replica.js +260 -47
- package/dist/web/runtime.d.ts +102 -0
- package/dist/web/runtime.js +335 -0
- package/package.json +39 -1
package/dist/contract.d.ts
CHANGED
|
@@ -12,13 +12,23 @@ export declare function endpoint<Req, Res>(method: "GET" | "POST", path: string,
|
|
|
12
12
|
}): Endpoint<Req, Res>;
|
|
13
13
|
/**
|
|
14
14
|
* Why a call failed. `signed-out`: the login is gone (the only kind that reloads the page). `forbidden`: signed in but
|
|
15
|
-
* not allowed. `invalid`: the request
|
|
16
|
-
*
|
|
15
|
+
* not allowed. `invalid`: the request broke the contract. `conflict`: refused for the current state. `upstream`: a
|
|
16
|
+
* service behind the server failed. `unavailable`: the server is up but can't now. `offline`: no answer. `unreadable`:
|
|
17
|
+
* a reply that broke the contract (not JSON, or failing its schema): it may have acted, and the server never sends it.
|
|
18
|
+
*
|
|
19
|
+
* `refused`: the server's own refusal, its kit error body naming a kind that matches the status; only then can it be
|
|
20
|
+
* final, since a proxy can answer any status. `path`: where a refused request broke its schema. `retryAfterMs`: the
|
|
21
|
+
* provider's `Retry-After`, a floor for the next try.
|
|
17
22
|
*/
|
|
18
23
|
export type Failure = {
|
|
19
|
-
kind: "signed-out" | "forbidden" | "invalid" | "conflict" | "upstream" | "unavailable" | "offline";
|
|
24
|
+
kind: "signed-out" | "forbidden" | "invalid" | "conflict" | "upstream" | "unavailable" | "offline" | "unreadable";
|
|
20
25
|
message: string;
|
|
26
|
+
refused?: true;
|
|
27
|
+
path?: string;
|
|
28
|
+
retryAfterMs?: number;
|
|
21
29
|
};
|
|
30
|
+
/** The kinds a server answers with: a reply it can't read is the client's observation, never a server's. */
|
|
31
|
+
export type RefusalKind = Exclude<Failure["kind"], "unreadable">;
|
|
22
32
|
export type Result<T> = {
|
|
23
33
|
ok: true;
|
|
24
34
|
value: T;
|
|
@@ -26,12 +36,15 @@ export type Result<T> = {
|
|
|
26
36
|
ok: false;
|
|
27
37
|
failure: Failure;
|
|
28
38
|
};
|
|
29
|
-
export declare const STATUS: Record<
|
|
39
|
+
export declare const STATUS: Record<RefusalKind, number>;
|
|
40
|
+
/** The longest a `Retry-After` defers the next try: a bad header can delay sync, never stop it. */
|
|
41
|
+
export declare const RETRY_AFTER_MAX_MS = 3600000;
|
|
30
42
|
/** GET requests carry their input as one `q` query parameter of JSON; POST as the body. */
|
|
31
43
|
export declare function encodeUrl<Req>(route: Endpoint<Req, unknown>, request: Req): string;
|
|
32
44
|
/**
|
|
33
|
-
* Call an endpoint from a browser (or any fetch). Every outcome is a `Result`: thrown errors,
|
|
34
|
-
* proxy's login page means signed out), error statuses and
|
|
45
|
+
* Call an endpoint from a browser (or any fetch). Every outcome is a `Result`: thrown errors, redirects and successful
|
|
46
|
+
* HTML pages (a proxy's login page means signed out), error statuses, and successes that aren't JSON or break the
|
|
47
|
+
* schema (`unreadable`) all become a `Failure`. An error is `refused` only with the kit's own body.
|
|
35
48
|
*/
|
|
36
49
|
export declare function call<Req, Res>(route: Endpoint<Req, Res>, request: Req, options?: {
|
|
37
50
|
fetch?: typeof fetch;
|
package/dist/contract.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* server (which checks requests and its own replies) and the page (which checks replies, since a proxy in between can
|
|
4
4
|
* answer anything, such as an HTML login page). Failures are a closed set of kinds; a caller handles each by kind.
|
|
5
5
|
*/
|
|
6
|
-
import { check, object, string } from "./schema.js";
|
|
6
|
+
import { check, object, optional, string } from "./schema.js";
|
|
7
7
|
export function endpoint(method, path, request, response, options = {}) {
|
|
8
8
|
return { method, path, request, response, person: options.person ?? false };
|
|
9
9
|
}
|
|
@@ -16,8 +16,25 @@ export const STATUS = {
|
|
|
16
16
|
unavailable: 503,
|
|
17
17
|
offline: 503,
|
|
18
18
|
};
|
|
19
|
+
const KINDS = [
|
|
20
|
+
"signed-out",
|
|
21
|
+
"forbidden",
|
|
22
|
+
"invalid",
|
|
23
|
+
"conflict",
|
|
24
|
+
"upstream",
|
|
25
|
+
"unavailable",
|
|
26
|
+
"offline",
|
|
27
|
+
];
|
|
28
|
+
/** A body that was due as JSON and wasn't. */
|
|
29
|
+
const UNREADABLE = Symbol("unreadable");
|
|
19
30
|
/** A failure's body, as `serveWeb` answers it: its message is shown when there is one. */
|
|
20
31
|
const ERROR_BODY = object({ message: string() });
|
|
32
|
+
/** The kit's own error body: a kind it can send, its message, and the schema path of a refused request. */
|
|
33
|
+
const KIT_BODY = object({ kind: string(), message: string(), path: optional(string()) });
|
|
34
|
+
/**
|
|
35
|
+
* A status's kind when no kit body says otherwise. A wrong endpoint or method and a body past the server's limit are
|
|
36
|
+
* `invalid` (no command path: incompatible); a timeout or rate limit is `unavailable`, retried.
|
|
37
|
+
*/
|
|
21
38
|
function kindOf(status) {
|
|
22
39
|
if (status === 401) {
|
|
23
40
|
return "signed-out";
|
|
@@ -28,6 +45,9 @@ function kindOf(status) {
|
|
|
28
45
|
if (status === 409) {
|
|
29
46
|
return "conflict";
|
|
30
47
|
}
|
|
48
|
+
if (status === 408 || status === 429) {
|
|
49
|
+
return "unavailable";
|
|
50
|
+
}
|
|
31
51
|
if (status === 502 || status === 504) {
|
|
32
52
|
return "upstream";
|
|
33
53
|
}
|
|
@@ -36,6 +56,21 @@ function kindOf(status) {
|
|
|
36
56
|
}
|
|
37
57
|
return "invalid";
|
|
38
58
|
}
|
|
59
|
+
/** The longest a `Retry-After` defers the next try: a bad header can delay sync, never stop it. */
|
|
60
|
+
export const RETRY_AFTER_MAX_MS = 3_600_000;
|
|
61
|
+
/**
|
|
62
|
+
* `Retry-After` as milliseconds from now, capped at `RETRY_AFTER_MAX_MS`: delay seconds or an HTTP date; undefined
|
|
63
|
+
* when absent or unreadable.
|
|
64
|
+
*/
|
|
65
|
+
function retryAfter(value) {
|
|
66
|
+
if (value === null) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const at = /^\d+$/.test(value.trim())
|
|
70
|
+
? Number(value.trim()) * 1000
|
|
71
|
+
: Date.parse(value) - Date.now();
|
|
72
|
+
return Number.isNaN(at) ? undefined : Math.min(RETRY_AFTER_MAX_MS, Math.max(0, at));
|
|
73
|
+
}
|
|
39
74
|
/** GET requests carry their input as one `q` query parameter of JSON; POST as the body. */
|
|
40
75
|
export function encodeUrl(route, request) {
|
|
41
76
|
if (route.method === "POST") {
|
|
@@ -44,8 +79,9 @@ export function encodeUrl(route, request) {
|
|
|
44
79
|
return `${route.path}?q=${encodeURIComponent(JSON.stringify(request))}`;
|
|
45
80
|
}
|
|
46
81
|
/**
|
|
47
|
-
* Call an endpoint from a browser (or any fetch). Every outcome is a `Result`: thrown errors,
|
|
48
|
-
* proxy's login page means signed out), error statuses and
|
|
82
|
+
* Call an endpoint from a browser (or any fetch). Every outcome is a `Result`: thrown errors, redirects and successful
|
|
83
|
+
* HTML pages (a proxy's login page means signed out), error statuses, and successes that aren't JSON or break the
|
|
84
|
+
* schema (`unreadable`) all become a `Failure`. An error is `refused` only with the kit's own body.
|
|
49
85
|
*/
|
|
50
86
|
export async function call(route, request, options = {}) {
|
|
51
87
|
const send = options.fetch ?? fetch;
|
|
@@ -70,29 +106,60 @@ export async function call(route, request, options = {}) {
|
|
|
70
106
|
failure: { kind: "offline", message: error instanceof Error ? error.message : String(error) },
|
|
71
107
|
};
|
|
72
108
|
}
|
|
73
|
-
// A redirect (an opaque one, in a browser) or
|
|
109
|
+
// A redirect (an opaque one, in a browser) or a successful HTML page is a proxy sending the browser to sign in. An
|
|
110
|
+
// HTML error page keeps its status's kind.
|
|
74
111
|
const type = response.headers.get("content-type") ?? "";
|
|
75
112
|
if (response.type === "opaqueredirect" ||
|
|
76
113
|
(response.status >= 300 && response.status < 400) ||
|
|
77
|
-
type.includes("text/html")) {
|
|
114
|
+
(response.ok && type.includes("text/html"))) {
|
|
78
115
|
return { ok: false, failure: { kind: "signed-out", message: "the login expired" } };
|
|
79
116
|
}
|
|
80
117
|
const body = type.includes("application/json")
|
|
81
|
-
? await response.json().catch(() =>
|
|
82
|
-
:
|
|
118
|
+
? await response.json().catch(() => UNREADABLE)
|
|
119
|
+
: UNREADABLE;
|
|
83
120
|
if (!response.ok) {
|
|
121
|
+
const kit = check(KIT_BODY, body);
|
|
122
|
+
const named = kit.ok ? KINDS.find((each) => each === kit.value.kind) : undefined;
|
|
123
|
+
// Only the kit's own body, naming a kind its status carries, is the server's refusal; anything else is a status.
|
|
124
|
+
const own = kit.ok && named !== undefined && STATUS[named] === response.status;
|
|
125
|
+
const kind = own ? named : kindOf(response.status);
|
|
126
|
+
const after = kind === "unavailable" ? retryAfter(response.headers.get("retry-after")) : undefined;
|
|
127
|
+
if (own) {
|
|
128
|
+
const path = kit.value.path;
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
failure: {
|
|
132
|
+
kind,
|
|
133
|
+
message: kit.value.message,
|
|
134
|
+
refused: true,
|
|
135
|
+
...(path === undefined ? {} : { path }),
|
|
136
|
+
...(after === undefined ? {} : { retryAfterMs: after }),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
84
140
|
const error = check(ERROR_BODY, body);
|
|
85
141
|
return {
|
|
86
142
|
ok: false,
|
|
87
143
|
failure: {
|
|
88
|
-
kind
|
|
144
|
+
kind,
|
|
89
145
|
message: error.ok ? error.value.message : `HTTP ${response.status}`,
|
|
146
|
+
...(after === undefined ? {} : { retryAfterMs: after }),
|
|
90
147
|
},
|
|
91
148
|
};
|
|
92
149
|
}
|
|
150
|
+
// A success the page can't read may still have acted: unreadable, never a refusal.
|
|
151
|
+
if (body === UNREADABLE) {
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
failure: { kind: "unreadable", message: `reply: not JSON (HTTP ${response.status})` },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
93
157
|
const parsed = check(route.response, body);
|
|
94
158
|
if (!parsed.ok) {
|
|
95
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
failure: { kind: "unreadable", message: `reply: ${parsed.error.message}` },
|
|
162
|
+
};
|
|
96
163
|
}
|
|
97
164
|
return { ok: true, value: parsed.value };
|
|
98
165
|
}
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -12,15 +12,16 @@ export declare class WebAuth {
|
|
|
12
12
|
private writing;
|
|
13
13
|
private readonly pending;
|
|
14
14
|
private readonly env;
|
|
15
|
+
private readonly keys;
|
|
15
16
|
constructor(config: WebConfig, home: string, env: Readonly<Record<string, string | undefined>>);
|
|
16
17
|
get oidc(): boolean;
|
|
17
18
|
private allowed;
|
|
18
19
|
/**
|
|
19
|
-
* The allowed identities of a request: from
|
|
20
|
-
* else from the session token (the session cookie's value). `undefined` means not signed in; `false`, signed in but
|
|
20
|
+
* The allowed identities of a request: from an agent's bearer key when it sends one, else the proxy's JWT when it
|
|
21
|
+
* sends one (a bad one of either is refused, never ignored), else from the session token (the session cookie's value). `undefined` means not signed in; `false`, signed in but
|
|
21
22
|
* not allowed.
|
|
22
23
|
*/
|
|
23
|
-
identify(proxyJwt: string | undefined, token: string | undefined): Promise<Identities | false | undefined>;
|
|
24
|
+
identify(proxyJwt: string | undefined, token: string | undefined, bearer?: string): Promise<Identities | false | undefined>;
|
|
24
25
|
private discover;
|
|
25
26
|
/** Where to send a browser to sign in, with PKCE; `redirect` is this server's own callback URL. */
|
|
26
27
|
loginUrl(redirect: string): Promise<string>;
|
package/dist/server/auth.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Who a web request is:
|
|
3
|
-
* own OpenID Connect login (authorization code with
|
|
4
|
-
* Either way the identity must be on `web.allow`.
|
|
2
|
+
* Who a web request is: an agent's key (`Authorization: Bearer`, see `keys.ts`), Cloudflare Access's signed JWT
|
|
3
|
+
* (`Cf-Access-Jwt-Assertion`), or a session from this server's own OpenID Connect login (authorization code with
|
|
4
|
+
* PKCE). The issuer, audience and claims come from `web.json`. Either way the identity must be on `web.allow`.
|
|
5
5
|
*/
|
|
6
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
7
7
|
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
10
|
+
import { Keys } from "./keys.js";
|
|
10
11
|
const SESSION_DAYS = 7;
|
|
11
12
|
const PENDING_MS = 10 * 60_000;
|
|
12
13
|
/** A claim by dotted path, as a string, or `undefined`. */
|
|
@@ -42,6 +43,7 @@ export class WebAuth {
|
|
|
42
43
|
writing = Promise.resolve();
|
|
43
44
|
pending = new Map();
|
|
44
45
|
env;
|
|
46
|
+
keys;
|
|
45
47
|
constructor(config, home, env) {
|
|
46
48
|
if (config.access === undefined && config.oidc === undefined) {
|
|
47
49
|
throw new Error("web needs a login: set web.access (an identity-aware proxy) or web.oidc in web.json");
|
|
@@ -52,6 +54,7 @@ export class WebAuth {
|
|
|
52
54
|
this.config = config;
|
|
53
55
|
this.env = env;
|
|
54
56
|
this.sessionsPath = join(home, "web-sessions.json");
|
|
57
|
+
this.keys = new Keys(home);
|
|
55
58
|
this.accessKeys =
|
|
56
59
|
config.access === undefined
|
|
57
60
|
? undefined
|
|
@@ -66,11 +69,16 @@ export class WebAuth {
|
|
|
66
69
|
: undefined;
|
|
67
70
|
}
|
|
68
71
|
/**
|
|
69
|
-
* The allowed identities of a request: from
|
|
70
|
-
* else from the session token (the session cookie's value). `undefined` means not signed in; `false`, signed in but
|
|
72
|
+
* The allowed identities of a request: from an agent's bearer key when it sends one, else the proxy's JWT when it
|
|
73
|
+
* sends one (a bad one of either is refused, never ignored), else from the session token (the session cookie's value). `undefined` means not signed in; `false`, signed in but
|
|
71
74
|
* not allowed.
|
|
72
75
|
*/
|
|
73
|
-
async identify(proxyJwt, token) {
|
|
76
|
+
async identify(proxyJwt, token, bearer) {
|
|
77
|
+
// A bearer decides alone: one that doesn't verify is refused, never passed over for a cookie or a proxy's header.
|
|
78
|
+
if (bearer !== undefined) {
|
|
79
|
+
const name = await this.keys.verify(bearer);
|
|
80
|
+
return name === undefined ? false : (this.allowed([`key:${name}`]) ?? false);
|
|
81
|
+
}
|
|
74
82
|
if (proxyJwt !== undefined) {
|
|
75
83
|
const access = this.config.access;
|
|
76
84
|
if (access === undefined || this.accessKeys === undefined) {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { WebConfig } from "./config.ts";
|
|
2
|
+
import { Jobs } from "./jobs.ts";
|
|
3
|
+
import type { Job, JobEnd } from "./jobs.ts";
|
|
4
|
+
import { Log } from "./log.ts";
|
|
5
|
+
import type { Logger } from "./log.ts";
|
|
6
|
+
/** What a release says of itself: its version and the store schema it writes (`<name> version --json`). */
|
|
7
|
+
export type ReleaseInfo = {
|
|
8
|
+
version: string;
|
|
9
|
+
schema: number;
|
|
10
|
+
};
|
|
11
|
+
export type Installed = {
|
|
12
|
+
ok: true;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
reason: string;
|
|
16
|
+
tail: string[];
|
|
17
|
+
};
|
|
18
|
+
/** The automatic rollback that put `version` in place, if it is the one running: what it replaced and when. */
|
|
19
|
+
export declare function rolledBackTo(home: string, version: string): {
|
|
20
|
+
from: string;
|
|
21
|
+
at: string;
|
|
22
|
+
} | null;
|
|
23
|
+
/** Whether a server holds the home's lock, without creating the lock file. */
|
|
24
|
+
export declare function running(home: string): boolean;
|
|
25
|
+
/** What an update job leaves when done: the release it installed and the version it replaced. */
|
|
26
|
+
export type UpdateResult = {
|
|
27
|
+
release?: string;
|
|
28
|
+
from?: string;
|
|
29
|
+
};
|
|
30
|
+
/** How one update job ended, as a server reports it. */
|
|
31
|
+
export type UpdateRecord = {
|
|
32
|
+
job: number;
|
|
33
|
+
release: string;
|
|
34
|
+
from: string;
|
|
35
|
+
result: "installed" | "failed";
|
|
36
|
+
reason: string | null;
|
|
37
|
+
at: string;
|
|
38
|
+
};
|
|
39
|
+
/** An update job (`slopkit/server/jobs`, kind `update`): one open at a time; every tap while it is open joins it. */
|
|
40
|
+
export declare const UPDATE_KIND = "update";
|
|
41
|
+
/** An update job asked for, and whether it is still due to run. */
|
|
42
|
+
export type AskedUpdate = {
|
|
43
|
+
id: number;
|
|
44
|
+
due: boolean;
|
|
45
|
+
};
|
|
46
|
+
/** Ask for an update to `release` from `version` (joins one already open): the job's id, and whether still due. */
|
|
47
|
+
export declare function askUpdate(jobs: Jobs<UpdateResult>, release: string, version: string, by: {
|
|
48
|
+
client: string;
|
|
49
|
+
id: number;
|
|
50
|
+
}, now?: number): AskedUpdate;
|
|
51
|
+
/** Claim the asked update before the server stops: running, "installing <release> from <version>", committed. */
|
|
52
|
+
export declare function beginUpdate(jobs: Jobs<UpdateResult>, version: string): {
|
|
53
|
+
id: number;
|
|
54
|
+
release: string;
|
|
55
|
+
from: string;
|
|
56
|
+
} | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* At start, before serving: how an update job a stop left running is settled. The install began: installed when this
|
|
59
|
+
* is another version than the one it replaced (the reconciliation read), else failed as interrupted.
|
|
60
|
+
*/
|
|
61
|
+
export declare function recoverUpdate(job: Job<UpdateResult>, version: string): JobEnd<UpdateResult>;
|
|
62
|
+
/** At start, after running jobs are settled: an update still due failed, nothing changed. Returns how many. */
|
|
63
|
+
export declare function settleAskedUpdates(jobs: Jobs<UpdateResult>): number;
|
|
64
|
+
/** The newest `kept` finished updates, newest first. */
|
|
65
|
+
export declare function updateRecords(jobs: Jobs<UpdateResult>, kept: number): UpdateRecord[];
|
|
66
|
+
/** The open update job's id, if one is asked or installing. */
|
|
67
|
+
export declare function updating(jobs: Jobs<UpdateResult>): number | null;
|
|
68
|
+
/** The newer `edge` commit when this is an edge build behind it; a hint only, nothing installs it. */
|
|
69
|
+
export declare function newerEdge(root: URL | string, env?: Readonly<Record<string, string | undefined>>): Promise<string | undefined>;
|
|
70
|
+
/** What an app's `serve` gets: its home, what it is, the log, the web settings, and a way to ask for an update. */
|
|
71
|
+
export type ServeContext = {
|
|
72
|
+
home: string;
|
|
73
|
+
version: string;
|
|
74
|
+
log: Log;
|
|
75
|
+
/** The server's own log lines (`server`). */
|
|
76
|
+
say: Logger;
|
|
77
|
+
/** `web.json`, or why the web is off. */
|
|
78
|
+
web: WebConfig | {
|
|
79
|
+
off: string;
|
|
80
|
+
};
|
|
81
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
82
|
+
/** Stop the server for the update the app's store has asked (`askUpdate`), then start the same command again. */
|
|
83
|
+
update: () => void;
|
|
84
|
+
};
|
|
85
|
+
/** What an app's `serve` hands back once it serves. */
|
|
86
|
+
export type Served = {
|
|
87
|
+
/** Its web server's address, when it has one. */
|
|
88
|
+
url?: string;
|
|
89
|
+
close: () => Promise<void>;
|
|
90
|
+
/** The asked update job, claimed running before anything stops (`beginUpdate`). */
|
|
91
|
+
claim?: () => {
|
|
92
|
+
id: number;
|
|
93
|
+
release: string;
|
|
94
|
+
} | undefined;
|
|
95
|
+
};
|
|
96
|
+
export type CliOptions = {
|
|
97
|
+
/** The binary's name: `<NAME>_HOME`, `~/.<name>`, `<name>.sqlite`, `<name>.tgz`. */
|
|
98
|
+
name: string;
|
|
99
|
+
/** The installed package's directory (`new URL("../", import.meta.url)`). */
|
|
100
|
+
root: URL | string;
|
|
101
|
+
/** The store schema this release writes (`schemaVersion`). */
|
|
102
|
+
schema: number;
|
|
103
|
+
/** What `serve` does, for the help: "<name> serve: Run the server on the home: <serves>". */
|
|
104
|
+
serves: string;
|
|
105
|
+
serve: (context: ServeContext) => Promise<Served>;
|
|
106
|
+
/** The app's own commands, beside the kit's. */
|
|
107
|
+
commands?: Record<string, {
|
|
108
|
+
usage: string;
|
|
109
|
+
help: string;
|
|
110
|
+
run: (args: string[], home: string) => Promise<number>;
|
|
111
|
+
}>;
|
|
112
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
113
|
+
/** How long a server stays up before its release is proven here (default an hour). */
|
|
114
|
+
provenAfterMs?: number;
|
|
115
|
+
};
|
|
116
|
+
/** Run the command line: the exit code. */
|
|
117
|
+
export declare function runCli(args: readonly string[], options: CliOptions): Promise<number>;
|