@phreshos/cli 0.1.48 → 0.1.50
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 +31 -59
- package/dist/cli.js +23 -149
- package/dist/{control/command.js → commands/access.js} +3 -3
- package/dist/commands/connection.js +26 -0
- package/dist/commands/describe.js +75 -0
- package/dist/commands/endpoint.js +134 -0
- package/dist/{control/shared.js → commands/input.js} +0 -92
- package/dist/commands/observation.js +4 -0
- package/dist/commands/options.js +52 -0
- package/dist/commands/process.js +111 -0
- package/dist/commands/program.js +90 -0
- package/dist/commands/project.js +152 -0
- package/dist/commands/projection.js +59 -0
- package/dist/commands/schemas.js +89 -0
- package/dist/commands/window.js +102 -0
- package/dist/contract/command.js +92 -0
- package/dist/contract/schema.js +80 -0
- package/dist/system/command.js +17 -18
- package/dist/system/lifecycle.js +0 -4
- package/dist/template/package.json +1 -1
- package/package.json +3 -3
- package/dist/command-contract.js +0 -12
- package/dist/control/endpoint.js +0 -99
- package/dist/control/options.js +0 -44
- package/dist/control/process.js +0 -86
- package/dist/control/program.js +0 -68
- package/dist/control/window.js +0 -84
- package/dist/describe-command.js +0 -56
package/README.md
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
# `@phreshos/cli`
|
|
2
2
|
|
|
3
|
-
The `phresh` command for
|
|
4
|
-
|
|
3
|
+
The `phresh` command for Program projects, runtime operations, and PhreshOS
|
|
4
|
+
System management.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
[Documentation](https://docs.phreshos.com/sdks/cli) ·
|
|
7
|
+
[Installation](https://docs.phreshos.com/installation) ·
|
|
8
|
+
[Source](https://github.com/PhreshOS/cli)
|
|
9
|
+
|
|
10
|
+
## Role
|
|
11
|
+
|
|
12
|
+
The CLI presents public Project and System operations as human-facing commands.
|
|
13
|
+
It owns command parsing, terminal output, packaging presentation, System release
|
|
14
|
+
acquisition, and native service management.
|
|
15
|
+
|
|
16
|
+
The CLI owns its command contract. Runtime command executors use
|
|
17
|
+
`@phreshos/node` and its shared System domain handles; the CLI does not define a
|
|
18
|
+
second System interface or transport request language.
|
|
9
19
|
|
|
10
20
|
## Installation
|
|
11
21
|
|
|
@@ -18,61 +28,18 @@ native service management.
|
|
|
18
28
|
|
|
19
29
|
Node.js 20.10 or newer is required.
|
|
20
30
|
|
|
21
|
-
## Program projects
|
|
22
|
-
|
|
23
31
|
```sh
|
|
24
32
|
phresh create
|
|
25
|
-
phresh init
|
|
26
33
|
phresh dev
|
|
27
|
-
phresh start
|
|
28
34
|
phresh install
|
|
29
|
-
phresh uninstall
|
|
30
|
-
phresh pack
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`create` produces the official starter Program. The remaining commands operate
|
|
34
|
-
on the current Program project through the Node Project contract and present
|
|
35
|
-
its build, development, installation, and runtime lifecycles.
|
|
36
|
-
|
|
37
|
-
## System
|
|
38
|
-
|
|
39
|
-
```sh
|
|
40
|
-
phresh system install
|
|
41
35
|
phresh system status
|
|
42
|
-
phresh system start
|
|
43
|
-
phresh system stop
|
|
44
|
-
phresh system enable
|
|
45
|
-
phresh system disable
|
|
46
|
-
phresh system uninstall
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
System installation acquires and verifies the official release archive and
|
|
50
|
-
configures the native per-user service. Starting and stopping control current
|
|
51
|
-
execution; enabling and disabling control automatic startup.
|
|
52
|
-
|
|
53
|
-
## Runtime
|
|
54
|
-
|
|
55
|
-
```sh
|
|
56
36
|
phresh program list
|
|
57
|
-
phresh process list --program my-program
|
|
58
|
-
phresh endpoint inspect \
|
|
59
|
-
--program my-program \
|
|
60
|
-
--process main \
|
|
61
|
-
--endpoint server
|
|
62
|
-
phresh window inspect --program my-program --process main
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
The command hierarchy follows the runtime ownership hierarchy. Unknown
|
|
66
|
-
commands, flags, and malformed values reject rather than being guessed.
|
|
67
|
-
|
|
68
|
-
```sh
|
|
69
|
-
phresh describe
|
|
70
|
-
phresh describe program
|
|
71
|
-
phresh describe process list
|
|
72
37
|
```
|
|
73
38
|
|
|
74
|
-
`describe`
|
|
75
|
-
|
|
39
|
+
Use `phresh describe` to inspect a command, or `phresh describe --all --json`
|
|
40
|
+
to read the complete machine-discoverable contract. See the
|
|
41
|
+
[CLI documentation](https://docs.phreshos.com/sdks/cli) for Program project,
|
|
42
|
+
System, and runtime commands.
|
|
76
43
|
|
|
77
44
|
## Development
|
|
78
45
|
|
|
@@ -84,15 +51,20 @@ bun run verify
|
|
|
84
51
|
`verify` checks the scripts, builds the CLI and bundled starter, runs the
|
|
85
52
|
command tests, and validates the package artifact.
|
|
86
53
|
|
|
87
|
-
|
|
88
|
-
for the command model.
|
|
54
|
+
## Related repositories
|
|
89
55
|
|
|
90
|
-
|
|
56
|
+
- [`@phreshos/node`](https://github.com/PhreshOS/node) owns the Project and
|
|
57
|
+
external System APIs composed by the CLI.
|
|
58
|
+
- [`@phreshos/core`](https://github.com/PhreshOS/core) owns the shared System
|
|
59
|
+
domain contracts used by the Node SDK.
|
|
60
|
+
- [PhreshOS System](https://github.com/PhreshOS/system) provides the managed
|
|
61
|
+
runtime and official release artifact.
|
|
62
|
+
- [Phresh Program](https://github.com/PhreshOS/phresh-program) is the starter
|
|
63
|
+
bundled by `phresh create`.
|
|
64
|
+
- [PhreshOS Install](https://github.com/PhreshOS/install) bootstraps this CLI on
|
|
65
|
+
clean machines.
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
and host service integration. Node owns the external JavaScript interface and
|
|
94
|
-
Project workflows, Core owns shared contracts, and the System owns authoritative
|
|
95
|
-
state.
|
|
67
|
+
## Contributing
|
|
96
68
|
|
|
97
69
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository workflow and
|
|
98
70
|
[SECURITY.md](SECURITY.md) for private vulnerability reporting.
|
package/dist/cli.js
CHANGED
|
@@ -1,138 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command
|
|
2
|
+
import { Command } from "commander";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import accessCommands from "./commands/access.js";
|
|
5
|
+
import describeCommands from "./commands/describe.js";
|
|
6
|
+
import projectCommands from "./commands/project.js";
|
|
7
|
+
import { assertCommandContracts, attachCommandContract } from "./contract/command.js";
|
|
4
8
|
import { PromptCancelled, ReportedFailure } from "./prompts.js";
|
|
5
|
-
import create from "./create.js";
|
|
6
|
-
import install from "./install.js";
|
|
7
|
-
import launch from "./launch.js";
|
|
8
|
-
import init from "./init.js";
|
|
9
|
-
import pack from "./pack.js";
|
|
10
|
-
import uninstall from "./uninstall.js";
|
|
11
|
-
import systemCommands from "./system/command.js";
|
|
12
|
-
import controlCommands from "./control/command.js";
|
|
13
|
-
import describeCommands from "./describe-command.js";
|
|
14
|
-
import { commandContract } from "./command-contract.js";
|
|
15
9
|
import { blank, failure } from "./style.js";
|
|
10
|
+
import systemCommands from "./system/command.js";
|
|
16
11
|
const metadata = createRequire(import.meta.url)("../package.json");
|
|
17
|
-
const { version } = metadata;
|
|
18
|
-
const coreRange = metadata.dependencies["@phreshos/core"];
|
|
19
|
-
const runOptionPrefix = "--run-option-";
|
|
20
12
|
const program = new Command()
|
|
21
13
|
.name("phresh")
|
|
22
|
-
.
|
|
23
|
-
.version(version, "-v, --version")
|
|
14
|
+
.version(metadata.version, "-v, --version")
|
|
24
15
|
.showHelpAfterError()
|
|
25
16
|
.showSuggestionAfterError()
|
|
26
17
|
.configureOutput({
|
|
27
18
|
writeOut: value => process.stdout.write(spaced(value)),
|
|
28
19
|
writeErr: value => process.stderr.write(spaced(value))
|
|
29
20
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
name: options.name,
|
|
42
|
-
packageManager: options.packageManager,
|
|
43
|
-
install: options.install
|
|
44
|
-
});
|
|
45
|
-
}), [
|
|
46
|
-
"Creates a complete Server and Client project from the maintained",
|
|
47
|
-
"Phresh Program template bundled with this CLI.",
|
|
48
|
-
"",
|
|
49
|
-
"In a terminal, omitted values are collected interactively. Automation",
|
|
50
|
-
"supplies the directory and optional choices through named options."
|
|
51
|
-
]);
|
|
52
|
-
describe(program.command("init")
|
|
53
|
-
.description("initialize an existing Program project")
|
|
54
|
-
.option("--name <name>", "human-readable Program name")
|
|
55
|
-
.option("--build-command <command>", "prepare production files before use")
|
|
56
|
-
.option("--server", "include a Server endpoint")
|
|
57
|
-
.option("--server-location <path>", "production Server directory")
|
|
58
|
-
.option("--server-start-command <command>", "production Server command")
|
|
59
|
-
.option("--server-entry-file <path>", "production Server worker entry")
|
|
60
|
-
.option("--server-development-start-command <command>", "development Server command")
|
|
61
|
-
.option("--server-development-entry-file <path>", "development Server worker entry")
|
|
62
|
-
.option("--client", "include a Client endpoint")
|
|
63
|
-
.option("--client-location <path>", "production Client directory")
|
|
64
|
-
.option("--client-development-url <url>", "fixed or external development Client URL")
|
|
65
|
-
.option("--client-development-start-command <command>", "development Client command")
|
|
66
|
-
.option("--force", "replace an existing phresh.config.ts")
|
|
67
|
-
.action(async function (options) {
|
|
68
|
-
await init({
|
|
69
|
-
name: options.name,
|
|
70
|
-
buildCommand: options.buildCommand,
|
|
71
|
-
server: options.server === true || options.serverLocation !== undefined || options.serverStartCommand !== undefined || options.serverEntryFile !== undefined,
|
|
72
|
-
serverLocation: options.serverLocation,
|
|
73
|
-
serverStartCommand: options.serverStartCommand,
|
|
74
|
-
serverEntryFile: options.serverEntryFile,
|
|
75
|
-
serverDevelopmentStartCommand: options.serverDevelopmentStartCommand,
|
|
76
|
-
serverDevelopmentEntryFile: options.serverDevelopmentEntryFile,
|
|
77
|
-
client: options.client === true
|
|
78
|
-
|| options.clientLocation !== undefined
|
|
79
|
-
|| options.clientDevelopmentUrl !== undefined
|
|
80
|
-
|| options.clientDevelopmentStartCommand !== undefined,
|
|
81
|
-
clientLocation: options.clientLocation,
|
|
82
|
-
clientDevelopmentUrl: options.clientDevelopmentUrl,
|
|
83
|
-
clientDevelopmentStartCommand: options.clientDevelopmentStartCommand,
|
|
84
|
-
force: options.force === true
|
|
85
|
-
}, process.cwd(), coreRange);
|
|
86
|
-
}), [
|
|
87
|
-
"Reads identity, version, and description from package.json, then writes",
|
|
88
|
-
"phresh.config.ts. A terminal asks only for values the project cannot",
|
|
89
|
-
"provide. Without a terminal, declare at least one endpoint with options."
|
|
90
|
-
]);
|
|
91
|
-
describe(program.command("pack")
|
|
92
|
-
.description("build and package this Program")
|
|
93
|
-
.action(async function () {
|
|
94
|
-
await pack();
|
|
95
|
-
}), [
|
|
96
|
-
"Runs an optional buildCommand, then packages the production files",
|
|
97
|
-
"declared for each endpoint."
|
|
98
|
-
]);
|
|
99
|
-
describe(program.command("install")
|
|
100
|
-
.description("install a local or official Program")
|
|
101
|
-
.argument("[name]", "name of an official Program")
|
|
102
|
-
.option("--run", "run the installed Program now")
|
|
103
|
-
.action(async function (name, options) {
|
|
104
|
-
await install({ name, run: options.run === true });
|
|
105
|
-
}), [
|
|
106
|
-
"Without a name, builds and installs the Program declared by this project.",
|
|
107
|
-
"A name installs its verified official production release. --run launches",
|
|
108
|
-
"the installed Program now."
|
|
109
|
-
]);
|
|
110
|
-
describe(program.command("uninstall")
|
|
111
|
-
.description("uninstall a local or installed Program")
|
|
112
|
-
.argument("[name]", "name of an installed Program")
|
|
113
|
-
.option("--everything", "also remove Processes, data, and runtime state")
|
|
114
|
-
.action(async function (name, options) {
|
|
115
|
-
await uninstall({ name, everything: options.everything === true });
|
|
116
|
-
}), [
|
|
117
|
-
"Without a name, uses the Program declared by this project. Removes its",
|
|
118
|
-
"installed files while preserving its Processes, data,",
|
|
119
|
-
"and runtime Program. --everything removes all system-owned state."
|
|
120
|
-
]);
|
|
121
|
-
attached("start", "run the production Program without installing", [
|
|
122
|
-
"Runs this Program attached to the terminal. Output arrives here, and",
|
|
123
|
-
"ending this command stops the Program. An optional buildCommand runs",
|
|
124
|
-
"before launch, and the Program's exit status becomes this command's status."
|
|
125
|
-
], "production");
|
|
126
|
-
attached("dev", "run the development Program without installing", [
|
|
127
|
-
"Runs the same attached lifecycle as start using the development",
|
|
128
|
-
"declarations. A Client URL must respond within 15 seconds before launch."
|
|
129
|
-
], "development");
|
|
21
|
+
attachCommandContract(program, {
|
|
22
|
+
name: "phresh",
|
|
23
|
+
description: "create Programs and manage PhreshOS",
|
|
24
|
+
options: [{ flags: "-v, --version", description: "display the CLI version" }],
|
|
25
|
+
guidance: ["Run phresh <command> --help for human guidance, or phresh describe for the machine-readable command contract."],
|
|
26
|
+
examples: ["phresh create", "phresh describe --all --json"]
|
|
27
|
+
});
|
|
28
|
+
const coreRange = metadata.dependencies["@phreshos/core"];
|
|
29
|
+
if (!coreRange)
|
|
30
|
+
throw new Error("The CLI package does not declare @phreshos/core");
|
|
31
|
+
projectCommands(program, coreRange);
|
|
130
32
|
systemCommands(program);
|
|
131
|
-
|
|
33
|
+
accessCommands(program);
|
|
132
34
|
describeCommands(program);
|
|
35
|
+
assertCommandContracts(program);
|
|
133
36
|
// Every command begins with the same breathing room. Keep this at the entry
|
|
134
|
-
// point so individual commands never
|
|
135
|
-
|
|
37
|
+
// point so individual commands never manufacture their own opening.
|
|
38
|
+
if (!process.argv.includes("--json"))
|
|
39
|
+
blank();
|
|
136
40
|
if (process.argv.length === 2)
|
|
137
41
|
program.help();
|
|
138
42
|
try {
|
|
@@ -153,33 +57,3 @@ function spaced(value) {
|
|
|
153
57
|
return value;
|
|
154
58
|
return value.endsWith("\n") ? `${value}\n` : `${value}\n\n`;
|
|
155
59
|
}
|
|
156
|
-
function attached(name, summary, detail, mode) {
|
|
157
|
-
describe(program.command(name)
|
|
158
|
-
.description(summary)
|
|
159
|
-
.argument("[run-options...]", "--run-option-<name>=<value> values passed to the Program")
|
|
160
|
-
.allowUnknownOption()
|
|
161
|
-
.action(async function (args) {
|
|
162
|
-
await launch(mode, process.cwd(), runOptions(name, args));
|
|
163
|
-
}), detail);
|
|
164
|
-
}
|
|
165
|
-
function describe(command, paragraphs) {
|
|
166
|
-
command.addHelpText("after", `\n${paragraphs.map(line => line ? ` ${line}` : "").join("\n")}\n`);
|
|
167
|
-
commandContract(command, { guidance: paragraphs.filter(Boolean) });
|
|
168
|
-
return command;
|
|
169
|
-
}
|
|
170
|
-
function runOptions(command, args) {
|
|
171
|
-
const options = {};
|
|
172
|
-
for (const argument of args) {
|
|
173
|
-
if (!argument.startsWith(runOptionPrefix))
|
|
174
|
-
throw new Error(`${command} accepts Program options only as ${runOptionPrefix}<name>=<value>; received "${argument}"`);
|
|
175
|
-
const said = argument.slice(runOptionPrefix.length);
|
|
176
|
-
const at = said.indexOf("=");
|
|
177
|
-
if (at < 1)
|
|
178
|
-
throw new Error(`"${argument}" says no value — write ${runOptionPrefix}<name>=<value>, and end with = for an empty value`);
|
|
179
|
-
const name = said.slice(0, at);
|
|
180
|
-
if (Object.hasOwn(options, name))
|
|
181
|
-
throw new Error(`The run option "${name}" was given more than once`);
|
|
182
|
-
options[name] = said.slice(at + 1);
|
|
183
|
-
}
|
|
184
|
-
return options;
|
|
185
|
-
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import endpointCommands from "./endpoint.js";
|
|
2
2
|
import processCommands from "./process.js";
|
|
3
3
|
import programCommands from "./program.js";
|
|
4
|
-
import { connectSystem } from "./
|
|
4
|
+
import { connectSystem } from "./connection.js";
|
|
5
5
|
import windowCommands from "./window.js";
|
|
6
|
-
/** Expose the shared System
|
|
7
|
-
export default function
|
|
6
|
+
/** Expose the shared System domains through explicit Node SDK executors. */
|
|
7
|
+
export default function accessCommands(program, connect = connectSystem) {
|
|
8
8
|
programCommands(program, connect);
|
|
9
9
|
processCommands(program, connect);
|
|
10
10
|
endpointCommands(program, connect);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { System } from "@phreshos/node";
|
|
2
|
+
export const connectSystem = () => System.connect();
|
|
3
|
+
/** Give one command exclusive ownership of one Node SDK System connection. */
|
|
4
|
+
export async function connected(connect, action) {
|
|
5
|
+
const system = await connect();
|
|
6
|
+
try {
|
|
7
|
+
return await action(system);
|
|
8
|
+
}
|
|
9
|
+
finally {
|
|
10
|
+
await system.disconnect();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function requireProgram(system, identity) {
|
|
14
|
+
const program = await system.program.find(identity);
|
|
15
|
+
if (!program)
|
|
16
|
+
throw new Error(`Unknown Program "${identity}"`);
|
|
17
|
+
return program;
|
|
18
|
+
}
|
|
19
|
+
export async function requireProcess(system, identity, programIdentity) {
|
|
20
|
+
const process = programIdentity
|
|
21
|
+
? await (await requireProgram(system, programIdentity)).process.find(identity)
|
|
22
|
+
: await system.process.find(identity);
|
|
23
|
+
if (!process)
|
|
24
|
+
throw new Error(`Unknown Process "${identity}"`);
|
|
25
|
+
return process;
|
|
26
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineCommand, readCommandContract } from "../contract/command.js";
|
|
2
|
+
import { value } from "../contract/schema.js";
|
|
3
|
+
import { jsonOption, option } from "./options.js";
|
|
4
|
+
import { jsonOutput } from "./schemas.js";
|
|
5
|
+
/** Expose the authoritative CLI contract without contacting the System. */
|
|
6
|
+
export default function describeCommands(root) {
|
|
7
|
+
defineCommand(root, {
|
|
8
|
+
name: "describe",
|
|
9
|
+
description: "describe Phresh commands as machine-readable data",
|
|
10
|
+
arguments: [{ syntax: "[path...]", description: "command path to describe" }],
|
|
11
|
+
options: [
|
|
12
|
+
option("--all", "include every descendant beneath the selected path"),
|
|
13
|
+
jsonOption
|
|
14
|
+
],
|
|
15
|
+
guidance: ["Omit the path to start at the CLI root. Add --all to retrieve the complete selected contract tree."],
|
|
16
|
+
examples: ["phresh describe", "phresh describe process create", "phresh describe --all --json"],
|
|
17
|
+
output: jsonOutput(value.any("one command contract or a complete contract tree"), "CLI command contract")
|
|
18
|
+
}, ({ arguments: [path], options }) => {
|
|
19
|
+
const command = resolveCommand(root, path);
|
|
20
|
+
return options.all ? describeTree(command, path) : describe(command, path);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function resolveCommand(root, path) {
|
|
24
|
+
let command = root;
|
|
25
|
+
for (const name of path) {
|
|
26
|
+
const child = command.commands.find(candidate => {
|
|
27
|
+
const contract = readCommandContract(candidate);
|
|
28
|
+
return candidate.name() === name || contract?.aliases?.includes(name);
|
|
29
|
+
});
|
|
30
|
+
if (!child)
|
|
31
|
+
throw new Error(`Unknown command path "${path.join(" ")}"`);
|
|
32
|
+
command = child;
|
|
33
|
+
}
|
|
34
|
+
return command;
|
|
35
|
+
}
|
|
36
|
+
function describeTree(command, path) {
|
|
37
|
+
return {
|
|
38
|
+
...describe(command, path),
|
|
39
|
+
commands: command.commands.map(child => describeTree(child, [...path, child.name()]))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function describe(command, path) {
|
|
43
|
+
const contract = readCommandContract(command);
|
|
44
|
+
if (!contract)
|
|
45
|
+
throw new Error(`Command "${path.join(" ") || command.name()}" has no CLI contract`);
|
|
46
|
+
return {
|
|
47
|
+
path,
|
|
48
|
+
name: contract.name,
|
|
49
|
+
aliases: contract.aliases ?? [],
|
|
50
|
+
description: contract.description,
|
|
51
|
+
arguments: contract.arguments ?? [],
|
|
52
|
+
options: (contract.options ?? []).map(publicOption),
|
|
53
|
+
guidance: contract.guidance ?? [],
|
|
54
|
+
examples: contract.examples ?? [],
|
|
55
|
+
requiresSystem: contract.requiresSystem === true,
|
|
56
|
+
output: contract.output ?? null,
|
|
57
|
+
commands: command.commands.map(child => {
|
|
58
|
+
const childContract = readCommandContract(child);
|
|
59
|
+
if (!childContract)
|
|
60
|
+
throw new Error(`Command "${[...path, child.name()].join(" ")}" has no CLI contract`);
|
|
61
|
+
return { name: childContract.name, aliases: childContract.aliases ?? [], description: childContract.description };
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function publicOption(option) {
|
|
66
|
+
return {
|
|
67
|
+
flags: option.flags,
|
|
68
|
+
description: option.description,
|
|
69
|
+
mandatory: option.mandatory === true,
|
|
70
|
+
value: option.flags.includes("<") ? "required" : option.flags.includes("[") ? "optional" : "none",
|
|
71
|
+
repeatable: option.repeatable === true,
|
|
72
|
+
choices: option.choices ?? [],
|
|
73
|
+
...(Object.hasOwn(option, "default") ? { default: option.default } : {})
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { defineCommand } from "../contract/command.js";
|
|
2
|
+
import { value } from "../contract/schema.js";
|
|
3
|
+
import { clientOverrideOptions, endpointOptions, option, serverOverrideOptions, timeoutOption, withJson } from "./options.js";
|
|
4
|
+
import { endpointOutput, eventOutput, jsonOutput } from "./schemas.js";
|
|
5
|
+
import { connected, requireProcess } from "./connection.js";
|
|
6
|
+
import { bounded, clientLaunch, payload, serverLaunch } from "./input.js";
|
|
7
|
+
import { wait } from "./observation.js";
|
|
8
|
+
import { endpoint, endpointView } from "./projection.js";
|
|
9
|
+
export default function endpointCommands(root, connect) {
|
|
10
|
+
const endpoints = defineCommand(root, {
|
|
11
|
+
name: "endpoint",
|
|
12
|
+
description: "inspect, control, and communicate with Process Endpoints",
|
|
13
|
+
guidance: ["Select the exact Process and either its server or client Endpoint."]
|
|
14
|
+
});
|
|
15
|
+
defineCommand(endpoints, {
|
|
16
|
+
name: "inspect",
|
|
17
|
+
description: "read whether one Endpoint is declared and running",
|
|
18
|
+
requiresSystem: true,
|
|
19
|
+
options: withJson(...endpointOptions),
|
|
20
|
+
output: jsonOutput(endpointOutput, "The selected Endpoint"),
|
|
21
|
+
examples: ["phresh endpoint inspect --process main --program terminal --endpoint server"]
|
|
22
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
23
|
+
return await endpointView(process, name);
|
|
24
|
+
}));
|
|
25
|
+
defineCommand(endpoints, {
|
|
26
|
+
name: "start",
|
|
27
|
+
description: "start a fresh Endpoint incarnation",
|
|
28
|
+
requiresSystem: true,
|
|
29
|
+
options: withJson(...endpointOptions, ...clientOverrideOptions, ...serverOverrideOptions),
|
|
30
|
+
output: jsonOutput(endpointOutput, "The started Endpoint"),
|
|
31
|
+
examples: ["phresh endpoint start --process main --program terminal --endpoint server --server-service"]
|
|
32
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
33
|
+
const client = clientLaunch(options);
|
|
34
|
+
const server = serverLaunch(options);
|
|
35
|
+
if (name === "server" && client !== undefined)
|
|
36
|
+
throw new Error("Client overrides require --endpoint client");
|
|
37
|
+
if (name === "client" && server !== undefined)
|
|
38
|
+
throw new Error("Server overrides require --endpoint server");
|
|
39
|
+
if (name === "client")
|
|
40
|
+
await process.client.start(typeof client === "object" ? client : undefined);
|
|
41
|
+
else
|
|
42
|
+
await process.server.start(typeof server === "object" ? server : undefined);
|
|
43
|
+
return await endpointView(process, name);
|
|
44
|
+
}));
|
|
45
|
+
defineCommand(endpoints, {
|
|
46
|
+
name: "stop",
|
|
47
|
+
description: "stop one Endpoint",
|
|
48
|
+
requiresSystem: true,
|
|
49
|
+
options: withJson(...endpointOptions),
|
|
50
|
+
output: jsonOutput(endpointOutput, "The stopped Endpoint"),
|
|
51
|
+
examples: ["phresh endpoint stop --process main --program terminal --endpoint client"]
|
|
52
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
53
|
+
await endpoint(process, name).stop();
|
|
54
|
+
return await endpointView(process, name);
|
|
55
|
+
}));
|
|
56
|
+
defineCommand(endpoints, {
|
|
57
|
+
name: "waitReady",
|
|
58
|
+
aliases: ["wait-ready"],
|
|
59
|
+
description: "wait until the Server Endpoint reports readiness",
|
|
60
|
+
requiresSystem: true,
|
|
61
|
+
options: withJson(...endpointOptions, timeoutOption),
|
|
62
|
+
output: jsonOutput(endpointOutput, "The ready Server Endpoint"),
|
|
63
|
+
examples: ["phresh endpoint wait-ready --process main --program terminal --endpoint server --timeout 30000"]
|
|
64
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
65
|
+
if (name !== "server")
|
|
66
|
+
throw new Error("waitReady requires --endpoint server");
|
|
67
|
+
await process.server.waitReady(timeout(options.timeout));
|
|
68
|
+
return await endpointView(process, "server");
|
|
69
|
+
}));
|
|
70
|
+
defineCommand(endpoints, {
|
|
71
|
+
name: "waitLifecycle",
|
|
72
|
+
aliases: ["wait-lifecycle"],
|
|
73
|
+
description: "wait for one lifecycle transition of an exact Endpoint",
|
|
74
|
+
requiresSystem: true,
|
|
75
|
+
options: withJson(...endpointOptions, option("--event <event>", "Endpoint lifecycle event", { mandatory: true, choices: ["start", "stop"] }), timeoutOption),
|
|
76
|
+
output: jsonOutput(value.object({
|
|
77
|
+
scope: value.string("Endpoint lifecycle subscription scope"),
|
|
78
|
+
event: value.enumeration(["start", "stop"], "observed lifecycle event")
|
|
79
|
+
}, ["scope", "event"], "Endpoint lifecycle event"), "One Endpoint lifecycle event"),
|
|
80
|
+
examples: ["phresh endpoint wait-lifecycle --process main --program terminal --endpoint server --event start"]
|
|
81
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
82
|
+
await wait(endpoint(process, name).lifecycle, options.event, timeout(options.timeout));
|
|
83
|
+
return { scope: `endpoint:${process.identity}:${name}:lifecycle`, event: options.event };
|
|
84
|
+
}));
|
|
85
|
+
defineCommand(endpoints, {
|
|
86
|
+
name: "ask",
|
|
87
|
+
description: "ask a Server event and return its answer",
|
|
88
|
+
requiresSystem: true,
|
|
89
|
+
options: withJson(...endpointOptions, option("--event <event>", "event name", { mandatory: true }), option("--payload <json>", "arbitrary event payload as JSON"), timeoutOption),
|
|
90
|
+
output: jsonOutput(value.any("answer returned by the Server event contract"), "The Server answer"),
|
|
91
|
+
examples: ["phresh endpoint ask --process main --program terminal --endpoint server --event status --json"]
|
|
92
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
93
|
+
if (name !== "server")
|
|
94
|
+
throw new Error("ask requires --endpoint server");
|
|
95
|
+
const input = payload(options.payload);
|
|
96
|
+
const answer = options.timeout === undefined
|
|
97
|
+
? await process.server.ask(options.event, input)
|
|
98
|
+
: await process.server.timeout(timeout(options.timeout)).ask(options.event, input);
|
|
99
|
+
return answer;
|
|
100
|
+
}));
|
|
101
|
+
defineCommand(endpoints, {
|
|
102
|
+
name: "publish",
|
|
103
|
+
description: "publish one event without waiting for an answer",
|
|
104
|
+
requiresSystem: true,
|
|
105
|
+
options: withJson(...endpointOptions, option("--event <event>", "event name", { mandatory: true }), option("--payload <json>", "arbitrary event payload as JSON")),
|
|
106
|
+
output: jsonOutput(endpointOutput, "The Endpoint after publishing"),
|
|
107
|
+
examples: ["phresh endpoint publish --process main --program terminal --endpoint client --event changed --payload '{\"value\":1}'"]
|
|
108
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
109
|
+
endpoint(process, name).publish(options.event, payload(options.payload));
|
|
110
|
+
return await endpointView(process, name);
|
|
111
|
+
}));
|
|
112
|
+
defineCommand(endpoints, {
|
|
113
|
+
name: "wait",
|
|
114
|
+
description: "wait for the next event emitted by one live Endpoint",
|
|
115
|
+
requiresSystem: true,
|
|
116
|
+
options: withJson(...endpointOptions, option("--event <event>", "event name", { mandatory: true }), timeoutOption),
|
|
117
|
+
output: jsonOutput(eventOutput("The observed Endpoint event"), "One Endpoint event"),
|
|
118
|
+
examples: ["phresh endpoint wait --process main --program terminal --endpoint client --event changed --json"]
|
|
119
|
+
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
120
|
+
return {
|
|
121
|
+
scope: `endpoint:${process.identity}:${name}`,
|
|
122
|
+
event: options.event,
|
|
123
|
+
payload: await wait(endpoint(process, name), options.event, timeout(options.timeout))
|
|
124
|
+
};
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
async function withEndpoint(connect, options, action) {
|
|
128
|
+
return await connected(connect, async (system) => {
|
|
129
|
+
return await action(await requireProcess(system, options.process, options.program), options.endpoint);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function timeout(value) {
|
|
133
|
+
return value === undefined ? undefined : bounded(value, "--timeout", 1);
|
|
134
|
+
}
|
|
@@ -1,89 +1,3 @@
|
|
|
1
|
-
import { System } from "@phreshos/node";
|
|
2
|
-
import { blank } from "../style.js";
|
|
3
|
-
export const connectSystem = () => System.connect();
|
|
4
|
-
export async function connected(connect, action) {
|
|
5
|
-
const system = await connect();
|
|
6
|
-
try {
|
|
7
|
-
return await action(system);
|
|
8
|
-
}
|
|
9
|
-
finally {
|
|
10
|
-
await system.disconnect();
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
export async function requireProgram(system, identity) {
|
|
14
|
-
const program = await system.program.find(identity);
|
|
15
|
-
if (!program)
|
|
16
|
-
throw new Error(`Unknown Program "${identity}"`);
|
|
17
|
-
return program;
|
|
18
|
-
}
|
|
19
|
-
export async function requireProcess(system, identity, programIdentity) {
|
|
20
|
-
const process = programIdentity
|
|
21
|
-
? await (await requireProgram(system, programIdentity)).process.find(identity)
|
|
22
|
-
: await system.process.find(identity);
|
|
23
|
-
if (!process)
|
|
24
|
-
throw new Error(`Unknown Process "${identity}"`);
|
|
25
|
-
return process;
|
|
26
|
-
}
|
|
27
|
-
export function endpoint(process, name) {
|
|
28
|
-
return name === "server" ? process.server : process.client;
|
|
29
|
-
}
|
|
30
|
-
export async function programView(program) {
|
|
31
|
-
return {
|
|
32
|
-
identity: program.identity,
|
|
33
|
-
assetId: program.assetId,
|
|
34
|
-
name: program.name,
|
|
35
|
-
version: program.version,
|
|
36
|
-
description: program.description,
|
|
37
|
-
installed: await program.installed(),
|
|
38
|
-
hasAgent: program.hasAgent,
|
|
39
|
-
server: program.server,
|
|
40
|
-
client: program.client
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export async function processView(process) {
|
|
44
|
-
const [server, client, serverService, clientService] = await Promise.all([
|
|
45
|
-
process.server.exists(),
|
|
46
|
-
process.client.exists(),
|
|
47
|
-
process.server.isService(),
|
|
48
|
-
process.client.isService()
|
|
49
|
-
]);
|
|
50
|
-
return {
|
|
51
|
-
identity: process.identity,
|
|
52
|
-
name: process.name,
|
|
53
|
-
program: process.program().identity,
|
|
54
|
-
startedAt: process.startedAt.toISOString(),
|
|
55
|
-
server: { declared: process.program().server !== null, running: server, service: serverService },
|
|
56
|
-
client: { declared: process.program().client !== null, running: client, service: clientService }
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
export async function endpointView(process, name) {
|
|
60
|
-
const program = process.program();
|
|
61
|
-
return {
|
|
62
|
-
process: process.identity,
|
|
63
|
-
program: program.identity,
|
|
64
|
-
endpoint: name,
|
|
65
|
-
declared: name === "server" ? program.server !== null : program.client !== null,
|
|
66
|
-
running: await endpoint(process, name).exists(),
|
|
67
|
-
service: await endpoint(process, name).isService()
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
export async function windowView(process) {
|
|
71
|
-
const window = process.client.window;
|
|
72
|
-
const [title, position, size, minimized, front, layer, location] = await Promise.all([
|
|
73
|
-
window.title(),
|
|
74
|
-
window.position(),
|
|
75
|
-
window.size(),
|
|
76
|
-
window.minimized(),
|
|
77
|
-
window.front(),
|
|
78
|
-
window.layer(),
|
|
79
|
-
window.location()
|
|
80
|
-
]);
|
|
81
|
-
return { process: process.identity, title, position, size, minimized, front, layer, location };
|
|
82
|
-
}
|
|
83
|
-
export function output(value, compact) {
|
|
84
|
-
console.log(JSON.stringify(value ?? null, null, compact ? undefined : 2));
|
|
85
|
-
blank();
|
|
86
|
-
}
|
|
87
1
|
export function payload(value) {
|
|
88
2
|
if (value === undefined)
|
|
89
3
|
return undefined;
|
|
@@ -194,9 +108,3 @@ function entries(values = []) {
|
|
|
194
108
|
}
|
|
195
109
|
return result;
|
|
196
110
|
}
|
|
197
|
-
export async function wait(target, event, timeout) {
|
|
198
|
-
return target.waitFor(event, timeout);
|
|
199
|
-
}
|
|
200
|
-
export function windowOf(process) {
|
|
201
|
-
return process.client.window;
|
|
202
|
-
}
|