@procedures/mark 0.41.1 → 0.43.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/CHANGELOG.md +50 -0
- package/README.md +7 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/args.d.ts +1 -1
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js.map +1 -1
- package/dist/auto-warm.d.ts +133 -0
- package/dist/auto-warm.d.ts.map +1 -0
- package/dist/auto-warm.js +230 -0
- package/dist/auto-warm.js.map +1 -0
- package/dist/cli.d.ts +7 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +54 -6
- package/dist/cli.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/server-mode.d.ts +5 -0
- package/dist/server-mode.d.ts.map +1 -1
- package/dist/server-mode.js +6 -0
- package/dist/server-mode.js.map +1 -1
- package/package.json +51 -51
- package/src/args.ts +2 -1
- package/src/auto-warm.ts +302 -0
- package/src/cli.ts +64 -7
- package/src/server-mode.ts +11 -0
package/src/cli.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { loadEcosystemRegistry } from "./ecosystem.js";
|
|
|
20
20
|
import { startServerMode, parsePort, parseTransport } from "./server-mode.js";
|
|
21
21
|
import { connectCliServer } from "./client-mode.js";
|
|
22
22
|
import type { ColdCommand, CommandRun } from "./command.js";
|
|
23
|
+
import { autoWarm, autoWarmConfig, autoWarmSteps, parseIdleTimeout, type AutoWarmConfig } from "./auto-warm.js";
|
|
23
24
|
import { startRepl } from "./repl.js";
|
|
24
25
|
import { findChildren, findProcedure, parseCommandLine, parseLeadingGlobals } from "./args.js";
|
|
25
26
|
import { groupHelp, rootHelp, validationMessage } from "@procedures/cli-kit";
|
|
@@ -86,7 +87,7 @@ function showRootHelp(procedures: readonly CommandLike[]): void {
|
|
|
86
87
|
footer: [
|
|
87
88
|
"Hosts: 'mark serve [--procedures pkgs] [--port N] [--expose patterns] [--events topics]' (the procedure server),",
|
|
88
89
|
" 'mark mcp' (the dev-tools MCP server over stdio).",
|
|
89
|
-
"Modes: 'mark --server [--port N] [--host H] [--transport http|websocket|both] [--events topics] [--probe-port N]' and 'mark -i' (the REPL).",
|
|
90
|
+
"Modes: 'mark --server [--port N] [--host H] [--transport http|websocket|both] [--events topics] [--probe-port N] [--idle-timeout ms]' and 'mark -i' (the REPL).",
|
|
90
91
|
"Root: 'mark --fs-root <folder> <command>' keeps each path of each procedure in the folder, also in each mode and host.",
|
|
91
92
|
" It refuses the procedures that it cannot confine: git, pnpm, docker run, a program (FORBIDDEN, outside-root).",
|
|
92
93
|
],
|
|
@@ -95,7 +96,7 @@ function showRootHelp(procedures: readonly CommandLike[]): void {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
/** The global flags that only `--server` takes. */
|
|
98
|
-
const SERVER_OPTIONS = ["port", "host", "transport", "events", "probe-port"] as const;
|
|
99
|
+
const SERVER_OPTIONS = ["port", "host", "transport", "events", "probe-port", "idle-timeout"] as const;
|
|
99
100
|
|
|
100
101
|
/** True when the global flags give an option of `--server`. */
|
|
101
102
|
function hasServerOption(globals: Readonly<Record<string, unknown>>): boolean {
|
|
@@ -157,6 +158,11 @@ export interface ExecuteOptions {
|
|
|
157
158
|
* then installs no signal handler, does not end the process, and records its exit code in `process.exitCode`.
|
|
158
159
|
*/
|
|
159
160
|
ownsProcess?: boolean | undefined;
|
|
161
|
+
/**
|
|
162
|
+
* The auto-warm (CLI-27). With the setting `after-cold`, a cold command starts the background
|
|
163
|
+
* host before it ends. Without it, no command starts a host. The bin reads it from `MARK_AUTO_WARM`.
|
|
164
|
+
*/
|
|
165
|
+
autoWarm?: AutoWarmConfig | undefined;
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
/** The registry of the host of a context: its registry, or a registry of its procedures. */
|
|
@@ -252,7 +258,7 @@ export async function executeArgs(
|
|
|
252
258
|
return;
|
|
253
259
|
}
|
|
254
260
|
if (hasServerOption(globals)) {
|
|
255
|
-
fail("--port, --host, --transport, --events
|
|
261
|
+
fail("--port, --host, --transport, --events, --probe-port and --idle-timeout are options of --server.");
|
|
256
262
|
return;
|
|
257
263
|
}
|
|
258
264
|
// The root applies to the whole run: the procedures load with it, and a warm server must have it
|
|
@@ -315,6 +321,7 @@ export async function executeArgs(
|
|
|
315
321
|
fail(`Error: ${messageOf(error)}`);
|
|
316
322
|
}
|
|
317
323
|
});
|
|
324
|
+
await warmAfterCold(cold, options, path, globals.local === true, ctx);
|
|
318
325
|
await endCommand(cold.command, end, failed);
|
|
319
326
|
return;
|
|
320
327
|
}
|
|
@@ -473,9 +480,52 @@ export async function executeArgs(
|
|
|
473
480
|
failCommand(`Error: ${messageOf(error)}`);
|
|
474
481
|
}
|
|
475
482
|
});
|
|
483
|
+
await warmAfterCold(cold, options, path, globals.local === true, ctx);
|
|
476
484
|
await endCommand(cold.command, end, failed);
|
|
477
485
|
}
|
|
478
486
|
|
|
487
|
+
/**
|
|
488
|
+
* This function does the auto-warm after a cold command (CLI-27), before the end of its service:
|
|
489
|
+
* with the process of Node, `endCommand()` ends the process. The auto-warm does not operate with
|
|
490
|
+
* `--local`, for a `server.*` command, or after a signal started the stop. An error of the
|
|
491
|
+
* auto-warm does not change the command: with `--verbose`, it prints a warning.
|
|
492
|
+
*/
|
|
493
|
+
async function warmAfterCold(
|
|
494
|
+
cold: ColdCommand,
|
|
495
|
+
options: ExecuteOptions,
|
|
496
|
+
path: readonly string[],
|
|
497
|
+
local: boolean,
|
|
498
|
+
ctx: CliContext | ManifestContext
|
|
499
|
+
): Promise<void> {
|
|
500
|
+
const config = options.autoWarm;
|
|
501
|
+
if (config === undefined || config.setting === "never" || local || path[0] === "server") return;
|
|
502
|
+
if (cold.command.life.state !== "running") return;
|
|
503
|
+
try {
|
|
504
|
+
const { currentBuild } = await import("./lockfile.js");
|
|
505
|
+
const { spawnMarkServer } = await import("@procedures/server");
|
|
506
|
+
const steps = autoWarmSteps(config, { cwd: process.cwd(), build: currentBuild(), fsRoot: ctx.fsRoot }, (spawn) => spawnMarkServer(spawn).child);
|
|
507
|
+
const decision = await autoWarm(config.setting, steps);
|
|
508
|
+
if (ctx.verbose) {
|
|
509
|
+
print.muted(decision.spawn ? `mark: the background host starts on port ${config.port} (${config.setting})` : `mark: no background host: ${decision.reason}`);
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (ctx.verbose) print.warning(`mark: the auto-warm failed: ${messageOf(error)}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* This function reads the auto-warm from the environment. An invalid value gives a message on the
|
|
518
|
+
* standard error and no auto-warm, because the auto-warm does not change the result of a command.
|
|
519
|
+
*/
|
|
520
|
+
function readAutoWarm(): AutoWarmConfig | undefined {
|
|
521
|
+
try {
|
|
522
|
+
return autoWarmConfig();
|
|
523
|
+
} catch (error) {
|
|
524
|
+
print.error(`mark: no auto-warm: ${messageOf(error)}`);
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
479
529
|
/**
|
|
480
530
|
* This function starts the cold mode of a command: the procedures host of the CLI server, in this
|
|
481
531
|
* process, under `run()` (CLI-26). After a startup that failed, it prints the errors, waits for the
|
|
@@ -605,7 +655,7 @@ async function startMcp(args: string[], fsRoot?: string): Promise<void> {
|
|
|
605
655
|
}
|
|
606
656
|
|
|
607
657
|
/** The options of `run()` and `main()`: the process of the service of each command (CLI-26). */
|
|
608
|
-
export type RunCliOptions = Pick<ExecuteOptions, "run" | "ownsProcess">;
|
|
658
|
+
export type RunCliOptions = Pick<ExecuteOptions, "run" | "ownsProcess" | "autoWarm">;
|
|
609
659
|
|
|
610
660
|
/**
|
|
611
661
|
* Run the CLI. By default, the caller owns the process: a command records its exit code in
|
|
@@ -647,6 +697,8 @@ async function run(argv: string[], options: RunCliOptions = {}): Promise<void> {
|
|
|
647
697
|
const events = await eventPatterns(globals.events);
|
|
648
698
|
// --probe-port: the probes answer on a port of their own (CLI-9, decision D5)
|
|
649
699
|
const probePort = globals["probe-port"] === undefined ? undefined : parsePort(String(globals["probe-port"]));
|
|
700
|
+
// --idle-timeout: the server stops by itself after this time with no call (CLI-27)
|
|
701
|
+
const idleTimeoutMs = globals["idle-timeout"] === undefined ? undefined : parseIdleTimeout(String(globals["idle-timeout"]));
|
|
650
702
|
await startServerMode({
|
|
651
703
|
port,
|
|
652
704
|
host,
|
|
@@ -654,13 +706,14 @@ async function run(argv: string[], options: RunCliOptions = {}): Promise<void> {
|
|
|
654
706
|
verbose,
|
|
655
707
|
events,
|
|
656
708
|
...(probePort !== undefined ? { probePort } : {}),
|
|
709
|
+
...(idleTimeoutMs !== undefined ? { idleTimeoutMs } : {}),
|
|
657
710
|
...(fsRoot !== undefined ? { fsRoot } : {}),
|
|
658
711
|
});
|
|
659
712
|
return;
|
|
660
713
|
}
|
|
661
714
|
// Before, `mark --port 4000 serve` ignored --port without a word
|
|
662
715
|
if (hasServerOption(globals)) {
|
|
663
|
-
fail("--port, --host, --transport, --events
|
|
716
|
+
fail("--port, --host, --transport, --events, --probe-port and --idle-timeout are options of --server.", "For mark serve, give them after serve.");
|
|
664
717
|
return;
|
|
665
718
|
}
|
|
666
719
|
|
|
@@ -694,11 +747,15 @@ async function run(argv: string[], options: RunCliOptions = {}): Promise<void> {
|
|
|
694
747
|
return;
|
|
695
748
|
}
|
|
696
749
|
|
|
750
|
+
// The auto-warm (CLI-27): the bin reads MARK_AUTO_WARM. Another caller gives the option, or has none.
|
|
751
|
+
const warm = options.autoWarm ?? (options.ownsProcess === true ? readAutoWarm() : undefined);
|
|
752
|
+
const execute: ExecuteOptions = { ...options, ...(warm !== undefined ? { autoWarm: warm } : {}) };
|
|
753
|
+
|
|
697
754
|
// The manifest gives the commands without the procedures: the help and a call to the CLI
|
|
698
755
|
// server do not load them (roadmap 1.3)
|
|
699
756
|
const manifest = readFreshManifest();
|
|
700
757
|
if (manifest) {
|
|
701
|
-
await executeArgs(argv, { commands: manifest.commands, verbose, fsRoot, load: () => initCli(verbose, fsRoot) },
|
|
758
|
+
await executeArgs(argv, { commands: manifest.commands, verbose, fsRoot, load: () => initCli(verbose, fsRoot) }, execute);
|
|
702
759
|
return;
|
|
703
760
|
}
|
|
704
761
|
|
|
@@ -712,7 +769,7 @@ async function run(argv: string[], options: RunCliOptions = {}): Promise<void> {
|
|
|
712
769
|
print.warning(`mark: the manifest was not written: ${error instanceof Error ? error.message : String(error)}`);
|
|
713
770
|
}
|
|
714
771
|
}
|
|
715
|
-
await executeArgs(argv, ctx,
|
|
772
|
+
await executeArgs(argv, ctx, execute);
|
|
716
773
|
}
|
|
717
774
|
|
|
718
775
|
/**
|
package/src/server-mode.ts
CHANGED
|
@@ -36,6 +36,11 @@ export interface ServerModeOptions {
|
|
|
36
36
|
* listener answers the probes under `/_probes`.
|
|
37
37
|
*/
|
|
38
38
|
probePort?: number | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* The idle stop (`--idle-timeout`, CLI-27): the server stops by itself after this number of
|
|
41
|
+
* milliseconds with no call. Without it, the server stops only at a signal or at `mark server stop`.
|
|
42
|
+
*/
|
|
43
|
+
idleTimeoutMs?: number | undefined;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
/**
|
|
@@ -112,6 +117,7 @@ export async function startServerMode(options: ServerModeOptions, run?: Procedur
|
|
|
112
117
|
// The options of the host: the same registry and policy as the cold mode of mark (CLI-26)
|
|
113
118
|
const { markHostOptions } = await import("./command.js");
|
|
114
119
|
const { runProcedureHost } = await import("@procedures/server");
|
|
120
|
+
const { ms } = await import("@procedures/client");
|
|
115
121
|
const id = `peer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
116
122
|
|
|
117
123
|
// The lockfile for client discovery. A client uses this server only from the same folder,
|
|
@@ -137,6 +143,8 @@ export async function startServerMode(options: ServerModeOptions, run?: Procedur
|
|
|
137
143
|
log: (line) => print.info(line),
|
|
138
144
|
// --probe-port: the probes answer on a port of their own, else on each listener under /_probes (CLI-9, D5)
|
|
139
145
|
...(options.probePort !== undefined ? { probes: { port: options.probePort, host } } : {}),
|
|
146
|
+
// --idle-timeout: the stop request of the lifecycle after a time with no call (CLI-27)
|
|
147
|
+
...(options.idleTimeoutMs !== undefined ? { idle: { after: ms(options.idleTimeoutMs) } } : {}),
|
|
140
148
|
...(run ? { run } : {}),
|
|
141
149
|
});
|
|
142
150
|
if (running.life.state !== "running") {
|
|
@@ -164,6 +172,9 @@ export async function startServerMode(options: ServerModeOptions, run?: Procedur
|
|
|
164
172
|
print.info(`File root: ${fsRoot} (each procedure refuses a path outside it)`);
|
|
165
173
|
print.info(` Refused under the root: ${refusedUnderRoot?.length ?? 0} procedures that the root cannot confine`);
|
|
166
174
|
}
|
|
175
|
+
if (options.idleTimeoutMs !== undefined) {
|
|
176
|
+
print.info(`\nIdle stop: after ${options.idleTimeoutMs} ms with no call`);
|
|
177
|
+
}
|
|
167
178
|
print.info("\nPress Ctrl+C to stop.");
|
|
168
179
|
|
|
169
180
|
// A signal starts the stop in stages, and the runner ends the process (CLI-8)
|