@procedures/mark 0.41.0 → 0.42.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/dist/args.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import { type CommandLike, type ParsedCommandLine as KitParsedCommandLine } from "@procedures/cli-kit";
9
9
  export { findChildren, findProcedure, procedureFlags } from "@procedures/cli-kit";
10
10
  /** The long names of the global flags */
11
- export type GlobalName = "help" | "version" | "verbose" | "format" | "local" | "json" | "no-input" | "fs-root" | "interactive" | "server" | "port" | "host" | "transport" | "events" | "probe-port";
11
+ export type GlobalName = "help" | "version" | "verbose" | "format" | "local" | "json" | "no-input" | "fs-root" | "interactive" | "server" | "port" | "host" | "transport" | "events" | "probe-port" | "idle-timeout";
12
12
  /** The global flags that the command line gives: true for a flag, text for a flag with a value */
13
13
  export type Globals = Partial<Record<GlobalName, string | boolean>>;
14
14
  export interface ParsedCommandLine<T extends CommandLike = CommandLike> extends Omit<KitParsedCommandLine<T>, "globals"> {
@@ -1 +1 @@
1
- {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,iBAAiB,IAAI,oBAAoB,EAC/C,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAElF,yCAAyC;AACzC,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,SAAS,GACT,SAAS,GACT,QAAQ,GACR,OAAO,GACP,MAAM,GACN,UAAU,GACV,SAAS,GACT,aAAa,GACb,QAAQ,GACR,MAAM,GACN,MAAM,GACN,WAAW,GACX,QAAQ,GACR,YAAY,CAAC;AAEjB,kGAAkG;AAClG,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,CAAE,SAAQ,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;IACtH,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAKD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAEnH;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAE/H"}
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,iBAAiB,IAAI,oBAAoB,EAC/C,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAElF,yCAAyC;AACzC,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,SAAS,GACT,SAAS,GACT,QAAQ,GACR,OAAO,GACP,MAAM,GACN,UAAU,GACV,SAAS,GACT,aAAa,GACb,QAAQ,GACR,MAAM,GACN,MAAM,GACN,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,cAAc,CAAC;AAEnB,kGAAkG;AAClG,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,CAAE,SAAQ,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;IACtH,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAKD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAEnH;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAE/H"}
package/dist/args.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,gBAAgB,IAAI,mBAAmB,EACvC,mBAAmB,IAAI,sBAAsB,GAG9C,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA4BlF,qGAAqG;AACrG,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAW,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,OAAO,sBAAsB,CAAC,IAAI,EAAE,IAAI,CAA2D,CAAC;AACtG,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAwB,IAAuB,EAAE,UAAwB;IACvG,OAAO,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAyB,CAAC;AAC7E,CAAC"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,gBAAgB,IAAI,mBAAmB,EACvC,mBAAmB,IAAI,sBAAsB,GAG9C,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA6BlF,qGAAqG;AACrG,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAW,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,OAAO,sBAAsB,CAAC,IAAI,EAAE,IAAI,CAA2D,CAAC;AACtG,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAwB,IAAuB,EAAE,UAAwB;IACvG,OAAO,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAyB,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The auto-warm of `mark` (CLI-27): start the background host after a cold command.
3
+ *
4
+ * - The setting `MARK_AUTO_WARM` has the values `never` (the default) and `after-cold`. The
5
+ * default is `never`, because each background host holds the procedures in memory.
6
+ * - With `after-cold`, a command that ran in this process (the cold mode) starts `mark --server`
7
+ * as a detached process before it exits. Thus the next command in this folder finds a warm host.
8
+ * - The background host stops by itself after `MARK_IDLE_TIMEOUT_MS` with no call (the default is
9
+ * 10 minutes). The spawn gives it `--idle-timeout`. A host that a user starts has no idle stop.
10
+ * - The command does not start a host when a warm host of this folder runs, or when another
11
+ * command starts one (the start claim). It also does not start one when another program holds the port.
12
+ * - The auto-warm does not change the result or the exit code of the command at any time.
13
+ *
14
+ * The spawn is `spawnMarkServer()` of `@procedures/server`, the start of `server.start`. It does
15
+ * not use `runCommand()` or `shell.run`, because the host must outlive the command.
16
+ * `SERVER-LAYERS.md` records this exception.
17
+ */
18
+ /** The values of the setting `MARK_AUTO_WARM`. */
19
+ export declare const AUTO_WARM_SETTINGS: readonly ["never", "after-cold"];
20
+ /** The setting of the auto-warm: `never`, or `after-cold`. */
21
+ export type AutoWarmSetting = (typeof AUTO_WARM_SETTINGS)[number];
22
+ /** The environment variable of the setting. */
23
+ export declare const AUTO_WARM_ENV = "MARK_AUTO_WARM";
24
+ /** The environment variable of the idle stop of the host that the auto-warm starts, in milliseconds. */
25
+ export declare const IDLE_TIMEOUT_ENV = "MARK_IDLE_TIMEOUT_MS";
26
+ /** The environment variable of the port of the host that the auto-warm starts. */
27
+ export declare const AUTO_WARM_PORT_ENV = "MARK_AUTO_WARM_PORT";
28
+ /** The default idle stop of the host that the auto-warm starts: 10 minutes. */
29
+ export declare const DEFAULT_IDLE_TIMEOUT_MS = 600000;
30
+ /** The default port of the host that the auto-warm starts: the default port of `mark --server`. */
31
+ export declare const DEFAULT_AUTO_WARM_PORT = 3000;
32
+ /**
33
+ * The age after which a start claim is stale: 60 s. The startup of a host has a cap of 30 s. A
34
+ * claim also goes stale when its process ended.
35
+ */
36
+ export declare const START_CLAIM_MAX_AGE_MS = 60000;
37
+ /** The configuration of the auto-warm. */
38
+ export interface AutoWarmConfig {
39
+ readonly setting: AutoWarmSetting;
40
+ /** The idle stop of the host, in milliseconds. */
41
+ readonly idleTimeoutMs: number;
42
+ /** The port of the host. */
43
+ readonly port: number;
44
+ /** The address of the host: loopback. */
45
+ readonly host: string;
46
+ }
47
+ /**
48
+ * This function reads the milliseconds of an idle stop: a whole number of 1 or more. An
49
+ * invalid value is an error, and the error names the option.
50
+ */
51
+ export declare function parseIdleTimeout(value: string, option?: string): number;
52
+ /**
53
+ * This function gives the configuration of the auto-warm from the environment. An unset or empty
54
+ * variable gives its default. An invalid value is an error.
55
+ */
56
+ export declare function autoWarmConfig(env?: Readonly<Record<string, string | undefined>>): AutoWarmConfig;
57
+ /** The facts of the decision of the auto-warm. */
58
+ export interface WarmFacts {
59
+ readonly setting: AutoWarmSetting;
60
+ /** True when a warm host of this folder, build and root runs (`findServer()`). */
61
+ readonly warmHost: boolean;
62
+ /** True when another command claimed the start of a host, and the claim is not stale. */
63
+ readonly starting: boolean;
64
+ /** True when a server or another program holds the port. */
65
+ readonly portBusy: boolean;
66
+ }
67
+ /** The decision of the auto-warm: start a host, or not, and why. */
68
+ export type WarmDecision = {
69
+ readonly spawn: true;
70
+ } | {
71
+ readonly spawn: false;
72
+ readonly reason: string;
73
+ };
74
+ /** This function decides if a cold command starts the background host. */
75
+ export declare function decideAutoWarm(facts: WarmFacts): WarmDecision;
76
+ /** The steps of the auto-warm. The tests give fakes, so no test starts a process or opens a port. */
77
+ export interface AutoWarmSteps {
78
+ /** This function tells if a warm host of this folder, build and root runs. */
79
+ findWarmHost(): Promise<boolean>;
80
+ /** This function claims the start of the host. It gives false when another command has a claim that is not stale. */
81
+ claimStart(): boolean;
82
+ /** This function writes the process ID of the new host in the claim. */
83
+ recordStart(pid: number): void;
84
+ /** This function removes the claim of this command. */
85
+ releaseStart(): void;
86
+ /** This function tells if a server or another program holds the port. */
87
+ portBusy(): Promise<boolean>;
88
+ /** This function starts the host as a detached process, and gives its process ID. */
89
+ spawn(): number | undefined;
90
+ }
91
+ /**
92
+ * This function does the auto-warm after a cold command, and gives the decision. The claim
93
+ * makes the decision atomic: of two cold commands at the same time, only one starts a host.
94
+ */
95
+ export declare function autoWarm(setting: AutoWarmSetting, steps: AutoWarmSteps): Promise<WarmDecision>;
96
+ /** The options of `startClaim()`. */
97
+ export interface StartClaimOptions {
98
+ /** The folder of the claim. The default is `~/.mark`. */
99
+ readonly dir?: string | undefined;
100
+ /** This function gives the time, in milliseconds. The default is `Date.now`. */
101
+ readonly now?: (() => number) | undefined;
102
+ /** This function tells if a process runs. The default is `process.kill(pid, 0)`. */
103
+ readonly alive?: ((pid: number) => boolean) | undefined;
104
+ /** The age after which a claim is stale. The default is `START_CLAIM_MAX_AGE_MS`. */
105
+ readonly maxAgeMs?: number | undefined;
106
+ }
107
+ /**
108
+ * This function gives the start claim of a port: the file `~/.mark/auto-warm-<port>.claim`. The
109
+ * file is made with the flag `wx`, so only one command gets the claim. A claim is stale after
110
+ * `maxAgeMs`, or when its process ended. A stale claim goes, and the next command can claim again.
111
+ */
112
+ export declare function startClaim(port: number, options?: StartClaimOptions): Pick<AutoWarmSteps, "claimStart" | "recordStart" | "releaseStart">;
113
+ /** This function tells if something accepts a connection on a port, within the time limit. */
114
+ export declare function portAnswers(host: string, port: number, timeoutMs?: number): Promise<boolean>;
115
+ /** Where a cold command runs: the folder, the build of `mark` and the root of the files. */
116
+ export interface WarmContext {
117
+ readonly cwd: string;
118
+ readonly build: string;
119
+ readonly fsRoot?: string | undefined;
120
+ }
121
+ /**
122
+ * This function gives the steps of the auto-warm with the lockfiles, the start claim of the port
123
+ * and `spawnMarkServer()` of `@procedures/server`.
124
+ */
125
+ export declare function autoWarmSteps(config: AutoWarmConfig, context: WarmContext, spawnHost: (options: {
126
+ port: number;
127
+ host: string;
128
+ idleTimeoutMs: number;
129
+ fsRoot?: string | undefined;
130
+ }) => {
131
+ readonly pid?: number | undefined;
132
+ }): AutoWarmSteps;
133
+ //# sourceMappingURL=auto-warm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-warm.d.ts","sourceRoot":"","sources":["../src/auto-warm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,kDAAkD;AAClD,eAAO,MAAM,kBAAkB,YAAI,OAAO,EAAE,YAAY,CAAU,CAAC;AAEnE,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,+CAA+C;AAC/C,eAAO,MAAM,aAAa,mBAAmB,CAAC;AAE9C,wGAAwG;AACxG,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAExD,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAE/C,mGAAmG;AACnG,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,kDAAkD;IAClD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAmB,GAAG,MAAM,CAMjF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GAAG,cAAc,CAkB9G;AAED,kDAAkD;AAClD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,oEAAoE;AACpE,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzG,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,CAM7D;AAED,qGAAqG;AACrG,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,qHAAqH;IACrH,UAAU,IAAI,OAAO,CAAC;IACtB,wEAAwE;IACxE,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,uDAAuD;IACvD,YAAY,IAAI,IAAI,CAAC;IACrB,yEAAyE;IACzE,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,qFAAqF;IACrF,KAAK,IAAI,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAyBpG;AAQD,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,gFAAgF;IAChF,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,oFAAoF;IACpF,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;IACxD,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAYD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,IAAI,CAAC,aAAa,EAAE,YAAY,GAAG,aAAa,GAAG,cAAc,CAAC,CAmD5I;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWzF;AAED,4FAA4F;AAC5F,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,KAAK;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAChJ,aAAa,CAmBf"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * The auto-warm of `mark` (CLI-27): start the background host after a cold command.
3
+ *
4
+ * - The setting `MARK_AUTO_WARM` has the values `never` (the default) and `after-cold`. The
5
+ * default is `never`, because each background host holds the procedures in memory.
6
+ * - With `after-cold`, a command that ran in this process (the cold mode) starts `mark --server`
7
+ * as a detached process before it exits. Thus the next command in this folder finds a warm host.
8
+ * - The background host stops by itself after `MARK_IDLE_TIMEOUT_MS` with no call (the default is
9
+ * 10 minutes). The spawn gives it `--idle-timeout`. A host that a user starts has no idle stop.
10
+ * - The command does not start a host when a warm host of this folder runs, or when another
11
+ * command starts one (the start claim). It also does not start one when another program holds the port.
12
+ * - The auto-warm does not change the result or the exit code of the command at any time.
13
+ *
14
+ * The spawn is `spawnMarkServer()` of `@procedures/server`, the start of `server.start`. It does
15
+ * not use `runCommand()` or `shell.run`, because the host must outlive the command.
16
+ * `SERVER-LAYERS.md` records this exception.
17
+ */
18
+ import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
+ import { connect } from "node:net";
21
+ import { checkServer, findServer, getMarkDir, readLockfileForPort } from "@procedures/peer/lockfile";
22
+ /** The values of the setting `MARK_AUTO_WARM`. */
23
+ export const AUTO_WARM_SETTINGS = ["never", "after-cold"];
24
+ /** The environment variable of the setting. */
25
+ export const AUTO_WARM_ENV = "MARK_AUTO_WARM";
26
+ /** The environment variable of the idle stop of the host that the auto-warm starts, in milliseconds. */
27
+ export const IDLE_TIMEOUT_ENV = "MARK_IDLE_TIMEOUT_MS";
28
+ /** The environment variable of the port of the host that the auto-warm starts. */
29
+ export const AUTO_WARM_PORT_ENV = "MARK_AUTO_WARM_PORT";
30
+ /** The default idle stop of the host that the auto-warm starts: 10 minutes. */
31
+ export const DEFAULT_IDLE_TIMEOUT_MS = 600_000;
32
+ /** The default port of the host that the auto-warm starts: the default port of `mark --server`. */
33
+ export const DEFAULT_AUTO_WARM_PORT = 3000;
34
+ /**
35
+ * The age after which a start claim is stale: 60 s. The startup of a host has a cap of 30 s. A
36
+ * claim also goes stale when its process ended.
37
+ */
38
+ export const START_CLAIM_MAX_AGE_MS = 60_000;
39
+ /**
40
+ * This function reads the milliseconds of an idle stop: a whole number of 1 or more. An
41
+ * invalid value is an error, and the error names the option.
42
+ */
43
+ export function parseIdleTimeout(value, option = "--idle-timeout") {
44
+ const timeout = /^\d+$/.test(value.trim()) ? Number(value.trim()) : Number.NaN;
45
+ if (!Number.isSafeInteger(timeout) || timeout < 1) {
46
+ throw new Error(`Invalid ${option}: "${value}". Give a number of milliseconds of 1 or more.`);
47
+ }
48
+ return timeout;
49
+ }
50
+ /**
51
+ * This function gives the configuration of the auto-warm from the environment. An unset or empty
52
+ * variable gives its default. An invalid value is an error.
53
+ */
54
+ export function autoWarmConfig(env = process.env) {
55
+ const given = env[AUTO_WARM_ENV]?.trim() ?? "";
56
+ const setting = given === "" ? "never" : AUTO_WARM_SETTINGS.find((each) => each === given);
57
+ if (setting === undefined) {
58
+ throw new Error(`Invalid ${AUTO_WARM_ENV}: "${given}". Give one of: ${AUTO_WARM_SETTINGS.join(", ")}.`);
59
+ }
60
+ const idle = env[IDLE_TIMEOUT_ENV]?.trim() ?? "";
61
+ const portText = env[AUTO_WARM_PORT_ENV]?.trim() ?? "";
62
+ const port = portText === "" ? DEFAULT_AUTO_WARM_PORT : Number(portText);
63
+ if (!/^\d+$/.test(portText === "" ? "0" : portText) || !Number.isInteger(port) || port < 1 || port > 65535) {
64
+ throw new Error(`Invalid ${AUTO_WARM_PORT_ENV}: "${portText}". Give a number from 1 to 65535.`);
65
+ }
66
+ return {
67
+ setting,
68
+ idleTimeoutMs: idle === "" ? DEFAULT_IDLE_TIMEOUT_MS : parseIdleTimeout(idle, IDLE_TIMEOUT_ENV),
69
+ port,
70
+ host: "127.0.0.1",
71
+ };
72
+ }
73
+ /** This function decides if a cold command starts the background host. */
74
+ export function decideAutoWarm(facts) {
75
+ if (facts.setting === "never")
76
+ return { spawn: false, reason: `${AUTO_WARM_ENV} is never` };
77
+ if (facts.warmHost)
78
+ return { spawn: false, reason: "a warm host runs" };
79
+ if (facts.starting)
80
+ return { spawn: false, reason: "another command starts the host" };
81
+ if (facts.portBusy)
82
+ return { spawn: false, reason: "the port is in use" };
83
+ return { spawn: true };
84
+ }
85
+ /**
86
+ * This function does the auto-warm after a cold command, and gives the decision. The claim
87
+ * makes the decision atomic: of two cold commands at the same time, only one starts a host.
88
+ */
89
+ export async function autoWarm(setting, steps) {
90
+ const facts = { setting, warmHost: false, starting: false, portBusy: false };
91
+ if (setting === "never")
92
+ return decideAutoWarm(facts);
93
+ facts.warmHost = await steps.findWarmHost();
94
+ if (facts.warmHost)
95
+ return decideAutoWarm(facts);
96
+ facts.starting = !steps.claimStart();
97
+ if (facts.starting)
98
+ return decideAutoWarm(facts);
99
+ try {
100
+ facts.portBusy = await steps.portBusy();
101
+ const decision = decideAutoWarm(facts);
102
+ if (!decision.spawn) {
103
+ steps.releaseStart();
104
+ return decision;
105
+ }
106
+ const pid = steps.spawn();
107
+ if (pid === undefined) {
108
+ steps.releaseStart();
109
+ return { spawn: false, reason: "the host process did not start" };
110
+ }
111
+ steps.recordStart(pid);
112
+ return decision;
113
+ }
114
+ catch (error) {
115
+ steps.releaseStart();
116
+ throw error;
117
+ }
118
+ }
119
+ /** True when a process runs. A process of another user also runs (`EPERM`). */
120
+ function processRuns(pid) {
121
+ try {
122
+ process.kill(pid, 0);
123
+ return true;
124
+ }
125
+ catch (error) {
126
+ return error.code === "EPERM";
127
+ }
128
+ }
129
+ /**
130
+ * This function gives the start claim of a port: the file `~/.mark/auto-warm-<port>.claim`. The
131
+ * file is made with the flag `wx`, so only one command gets the claim. A claim is stale after
132
+ * `maxAgeMs`, or when its process ended. A stale claim goes, and the next command can claim again.
133
+ */
134
+ export function startClaim(port, options = {}) {
135
+ const dir = options.dir ?? getMarkDir();
136
+ const file = path.join(dir, `auto-warm-${port}.claim`);
137
+ const now = options.now ?? Date.now;
138
+ const alive = options.alive ?? processRuns;
139
+ const maxAgeMs = options.maxAgeMs ?? START_CLAIM_MAX_AGE_MS;
140
+ let owned = false;
141
+ const write = (claim, flag) => {
142
+ fs.writeFileSync(file, JSON.stringify(claim), { flag, mode: 0o600 });
143
+ };
144
+ const stale = () => {
145
+ let claim;
146
+ try {
147
+ claim = JSON.parse(fs.readFileSync(file, "utf8"));
148
+ }
149
+ catch (error) {
150
+ // No file: the claim went at this time. A file that does not parse is stale.
151
+ return error.code !== "ENOENT";
152
+ }
153
+ if (typeof claim.at !== "number" || now() - claim.at >= maxAgeMs)
154
+ return true;
155
+ return claim.pid !== undefined && !alive(claim.pid);
156
+ };
157
+ const tryClaim = () => {
158
+ try {
159
+ write({ at: now() }, "wx");
160
+ return true;
161
+ }
162
+ catch (error) {
163
+ if (error.code === "EEXIST")
164
+ return false;
165
+ throw error;
166
+ }
167
+ };
168
+ return {
169
+ claimStart() {
170
+ fs.mkdirSync(dir, { recursive: true });
171
+ if (tryClaim()) {
172
+ owned = true;
173
+ return true;
174
+ }
175
+ if (!stale())
176
+ return false;
177
+ fs.rmSync(file, { force: true });
178
+ owned = tryClaim();
179
+ return owned;
180
+ },
181
+ recordStart(pid) {
182
+ if (owned)
183
+ write({ at: now(), pid }, "w");
184
+ },
185
+ releaseStart() {
186
+ if (!owned)
187
+ return;
188
+ owned = false;
189
+ fs.rmSync(file, { force: true });
190
+ },
191
+ };
192
+ }
193
+ /** This function tells if something accepts a connection on a port, within the time limit. */
194
+ export function portAnswers(host, port, timeoutMs = 500) {
195
+ return new Promise((resolve) => {
196
+ const socket = connect({ host, port });
197
+ const done = (answers) => {
198
+ socket.destroy();
199
+ resolve(answers);
200
+ };
201
+ socket.setTimeout(timeoutMs, () => done(false));
202
+ socket.once("connect", () => done(true));
203
+ socket.once("error", () => done(false));
204
+ });
205
+ }
206
+ /**
207
+ * This function gives the steps of the auto-warm with the lockfiles, the start claim of the port
208
+ * and `spawnMarkServer()` of `@procedures/server`.
209
+ */
210
+ export function autoWarmSteps(config, context, spawnHost) {
211
+ const claim = startClaim(config.port);
212
+ return {
213
+ ...claim,
214
+ findWarmHost: async () => (await findServer({ cwd: context.cwd, build: context.build, fsRoot: context.fsRoot })) !== null,
215
+ portBusy: async () => {
216
+ // A server of a lockfile on the port: of another folder, build or root
217
+ const lockfile = readLockfileForPort(config.port);
218
+ if (lockfile !== null && (await checkServer(lockfile)) !== "dead")
219
+ return true;
220
+ return portAnswers(config.host, config.port);
221
+ },
222
+ spawn: () => spawnHost({
223
+ port: config.port,
224
+ host: config.host,
225
+ idleTimeoutMs: config.idleTimeoutMs,
226
+ ...(context.fsRoot !== undefined ? { fsRoot: context.fsRoot } : {}),
227
+ }).pid,
228
+ };
229
+ }
230
+ //# sourceMappingURL=auto-warm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-warm.js","sourceRoot":"","sources":["../src/auto-warm.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts CHANGED
@@ -9,6 +9,7 @@ import type { AnyProcedure, ProcedureRegistry } from "@procedures/client";
9
9
  import type { RunOptions } from "@mark1russell7/service";
10
10
  import { type ManifestCommand } from "./manifest.js";
11
11
  import { type Asker, type Terminal } from "./ask.js";
12
+ import { type AutoWarmConfig } from "./auto-warm.js";
12
13
  /**
13
14
  * CLI execution context, created once and reused
14
15
  */
@@ -55,6 +56,11 @@ export interface ExecuteOptions {
55
56
  * then installs no signal handler, does not end the process, and records its exit code in `process.exitCode`.
56
57
  */
57
58
  ownsProcess?: boolean | undefined;
59
+ /**
60
+ * The auto-warm (CLI-27). With the setting `after-cold`, a cold command starts the background
61
+ * host before it ends. Without it, no command starts a host. The bin reads it from `MARK_AUTO_WARM`.
62
+ */
63
+ autoWarm?: AutoWarmConfig | undefined;
58
64
  }
59
65
  /**
60
66
  * Initialize the CLI context (load ecosystem, create client)
@@ -81,7 +87,7 @@ export declare function initCli(verbose?: boolean, fsRoot?: string): Promise<Cli
81
87
  */
82
88
  export declare function executeArgs(argv: string[], ctx: CliContext | ManifestContext, options?: ExecuteOptions): Promise<void>;
83
89
  /** The options of `run()` and `main()`: the process of the service of each command (CLI-26). */
84
- export type RunCliOptions = Pick<ExecuteOptions, "run" | "ownsProcess">;
90
+ export type RunCliOptions = Pick<ExecuteOptions, "run" | "ownsProcess" | "autoWarm">;
85
91
  /**
86
92
  * Run the CLI. By default, the caller owns the process: a command records its exit code in
87
93
  * `process.exitCode`, and the process does not end. The bin gives `ownsProcess: true`.
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAGH,OAAO,KAAK,EAAU,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAKrE,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAqE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AA+FxH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC,cAAc,oBAAoB,EAAE,MAAM,CAAC,CAAC;IACjE,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;CACjC;AAED,uHAAuH;AACvH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,CAAC,EAAE,KAAK,CAAC;IACZ;;;OAGG;IACH,GAAG,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACnC;AAgBD;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,OAAO,UAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBnF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GAAG,eAAe,EACjC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAiQf;AAkID,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,aAAa,CAAC,CAAC;AAExE;;;GAGG;AACH,iBAAe,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsG7E;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9F;AA2BD,OAAO,EAAE,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAGH,OAAO,KAAK,EAAU,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAKrE,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAqE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAMxH,OAAO,EAA6D,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA0FhH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC,cAAc,oBAAoB,EAAE,MAAM,CAAC,CAAC;IACjE,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;CACjC;AAED,uHAAuH;AACvH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,CAAC,EAAE,KAAK,CAAC;IACZ;;;OAGG;IACH,GAAG,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACvC;AAgBD;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,OAAO,UAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBnF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GAAG,eAAe,EACjC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAmQf;AA4KD,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,aAAa,GAAG,UAAU,CAAC,CAAC;AAErF;;;GAGG;AACH,iBAAe,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6G7E;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9F;AA2BD,OAAO,EAAE,GAAG,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -16,6 +16,7 @@ import { formatOutput } from "./format.js";
16
16
  import { loadEcosystemRegistry } from "./ecosystem.js";
17
17
  import { startServerMode, parsePort, parseTransport } from "./server-mode.js";
18
18
  import { connectCliServer } from "./client-mode.js";
19
+ import { autoWarm, autoWarmConfig, autoWarmSteps, parseIdleTimeout } from "./auto-warm.js";
19
20
  import { startRepl } from "./repl.js";
20
21
  import { findChildren, findProcedure, parseCommandLine, parseLeadingGlobals } from "./args.js";
21
22
  import { groupHelp, rootHelp, validationMessage } from "@procedures/cli-kit";
@@ -74,7 +75,7 @@ function showRootHelp(procedures) {
74
75
  footer: [
75
76
  "Hosts: 'mark serve [--procedures pkgs] [--port N] [--expose patterns] [--events topics]' (the procedure server),",
76
77
  " 'mark mcp' (the dev-tools MCP server over stdio).",
77
- "Modes: 'mark --server [--port N] [--host H] [--transport http|websocket|both] [--events topics] [--probe-port N]' and 'mark -i' (the REPL).",
78
+ "Modes: 'mark --server [--port N] [--host H] [--transport http|websocket|both] [--events topics] [--probe-port N] [--idle-timeout ms]' and 'mark -i' (the REPL).",
78
79
  "Root: 'mark --fs-root <folder> <command>' keeps each path of each procedure in the folder, also in each mode and host.",
79
80
  " It refuses the procedures that it cannot confine: git, pnpm, docker run, a program (FORBIDDEN, outside-root).",
80
81
  ],
@@ -83,7 +84,7 @@ function showRootHelp(procedures) {
83
84
  print.info(line);
84
85
  }
85
86
  /** The global flags that only `--server` takes. */
86
- const SERVER_OPTIONS = ["port", "host", "transport", "events", "probe-port"];
87
+ const SERVER_OPTIONS = ["port", "host", "transport", "events", "probe-port", "idle-timeout"];
87
88
  /** True when the global flags give an option of `--server`. */
88
89
  function hasServerOption(globals) {
89
90
  return SERVER_OPTIONS.some((name) => globals[name] !== undefined);
@@ -175,7 +176,7 @@ export async function executeArgs(argv, ctx, options = {}) {
175
176
  return;
176
177
  }
177
178
  if (hasServerOption(globals)) {
178
- fail("--port, --host, --transport, --events and --probe-port are options of --server.");
179
+ fail("--port, --host, --transport, --events, --probe-port and --idle-timeout are options of --server.");
179
180
  return;
180
181
  }
181
182
  // The root applies to the whole run: the procedures load with it, and a warm server must have it
@@ -235,6 +236,7 @@ export async function executeArgs(argv, ctx, options = {}) {
235
236
  fail(`Error: ${messageOf(error)}`);
236
237
  }
237
238
  });
239
+ await warmAfterCold(cold, options, path, globals.local === true, ctx);
238
240
  await endCommand(cold.command, end, failed);
239
241
  return;
240
242
  }
@@ -387,8 +389,48 @@ export async function executeArgs(argv, ctx, options = {}) {
387
389
  failCommand(`Error: ${messageOf(error)}`);
388
390
  }
389
391
  });
392
+ await warmAfterCold(cold, options, path, globals.local === true, ctx);
390
393
  await endCommand(cold.command, end, failed);
391
394
  }
395
+ /**
396
+ * This function does the auto-warm after a cold command (CLI-27), before the end of its service:
397
+ * with the process of Node, `endCommand()` ends the process. The auto-warm does not operate with
398
+ * `--local`, for a `server.*` command, or after a signal started the stop. An error of the
399
+ * auto-warm does not change the command: with `--verbose`, it prints a warning.
400
+ */
401
+ async function warmAfterCold(cold, options, path, local, ctx) {
402
+ const config = options.autoWarm;
403
+ if (config === undefined || config.setting === "never" || local || path[0] === "server")
404
+ return;
405
+ if (cold.command.life.state !== "running")
406
+ return;
407
+ try {
408
+ const { currentBuild } = await import("./lockfile.js");
409
+ const { spawnMarkServer } = await import("@procedures/server");
410
+ const steps = autoWarmSteps(config, { cwd: process.cwd(), build: currentBuild(), fsRoot: ctx.fsRoot }, (spawn) => spawnMarkServer(spawn).child);
411
+ const decision = await autoWarm(config.setting, steps);
412
+ if (ctx.verbose) {
413
+ print.muted(decision.spawn ? `mark: the background host starts on port ${config.port} (${config.setting})` : `mark: no background host: ${decision.reason}`);
414
+ }
415
+ }
416
+ catch (error) {
417
+ if (ctx.verbose)
418
+ print.warning(`mark: the auto-warm failed: ${messageOf(error)}`);
419
+ }
420
+ }
421
+ /**
422
+ * This function reads the auto-warm from the environment. An invalid value gives a message on the
423
+ * standard error and no auto-warm, because the auto-warm does not change the result of a command.
424
+ */
425
+ function readAutoWarm() {
426
+ try {
427
+ return autoWarmConfig();
428
+ }
429
+ catch (error) {
430
+ print.error(`mark: no auto-warm: ${messageOf(error)}`);
431
+ return undefined;
432
+ }
433
+ }
392
434
  /**
393
435
  * This function starts the cold mode of a command: the procedures host of the CLI server, in this
394
436
  * process, under `run()` (CLI-26). After a startup that failed, it prints the errors, waits for the
@@ -568,6 +610,8 @@ async function run(argv, options = {}) {
568
610
  const events = await eventPatterns(globals.events);
569
611
  // --probe-port: the probes answer on a port of their own (CLI-9, decision D5)
570
612
  const probePort = globals["probe-port"] === undefined ? undefined : parsePort(String(globals["probe-port"]));
613
+ // --idle-timeout: the server stops by itself after this time with no call (CLI-27)
614
+ const idleTimeoutMs = globals["idle-timeout"] === undefined ? undefined : parseIdleTimeout(String(globals["idle-timeout"]));
571
615
  await startServerMode({
572
616
  port,
573
617
  host,
@@ -575,13 +619,14 @@ async function run(argv, options = {}) {
575
619
  verbose,
576
620
  events,
577
621
  ...(probePort !== undefined ? { probePort } : {}),
622
+ ...(idleTimeoutMs !== undefined ? { idleTimeoutMs } : {}),
578
623
  ...(fsRoot !== undefined ? { fsRoot } : {}),
579
624
  });
580
625
  return;
581
626
  }
582
627
  // Before, `mark --port 4000 serve` ignored --port without a word
583
628
  if (hasServerOption(globals)) {
584
- fail("--port, --host, --transport, --events and --probe-port are options of --server.", "For mark serve, give them after serve.");
629
+ fail("--port, --host, --transport, --events, --probe-port and --idle-timeout are options of --server.", "For mark serve, give them after serve.");
585
630
  return;
586
631
  }
587
632
  // Handle -i / --interactive flag: start REPL
@@ -612,11 +657,14 @@ async function run(argv, options = {}) {
612
657
  await startMcp(commandArgs, fsRoot);
613
658
  return;
614
659
  }
660
+ // The auto-warm (CLI-27): the bin reads MARK_AUTO_WARM. Another caller gives the option, or has none.
661
+ const warm = options.autoWarm ?? (options.ownsProcess === true ? readAutoWarm() : undefined);
662
+ const execute = { ...options, ...(warm !== undefined ? { autoWarm: warm } : {}) };
615
663
  // The manifest gives the commands without the procedures: the help and a call to the CLI
616
664
  // server do not load them (roadmap 1.3)
617
665
  const manifest = readFreshManifest();
618
666
  if (manifest) {
619
- await executeArgs(argv, { commands: manifest.commands, verbose, fsRoot, load: () => initCli(verbose, fsRoot) }, options);
667
+ await executeArgs(argv, { commands: manifest.commands, verbose, fsRoot, load: () => initCli(verbose, fsRoot) }, execute);
620
668
  return;
621
669
  }
622
670
  // No manifest, or a stale one (a package was built again): load the procedures as before, and
@@ -630,7 +678,7 @@ async function run(argv, options = {}) {
630
678
  print.warning(`mark: the manifest was not written: ${error instanceof Error ? error.message : String(error)}`);
631
679
  }
632
680
  }
633
- await executeArgs(argv, ctx, options);
681
+ await executeArgs(argv, ctx, execute);
634
682
  }
635
683
  /**
636
684
  * Run the CLI and report an error that escapes it as one line, with exit code 1. With