@phreshos/cli 0.1.5 → 0.1.7
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/LICENSE +21 -0
- package/README.md +45 -12
- package/dist/cli.js +16 -3
- package/dist/create.js +4 -19
- package/dist/project-dependency.js +3 -52
- package/dist/prompts.js +18 -8
- package/dist/style.js +10 -0
- package/dist/system/command.js +96 -0
- package/dist/system/installation.js +241 -0
- package/dist/system/lifecycle.js +176 -0
- package/dist/system/node.js +13 -0
- package/dist/system/paths.js +28 -0
- package/dist/system/process.js +23 -0
- package/dist/system/readiness.js +29 -0
- package/dist/system/release.js +86 -0
- package/dist/system/service/index.js +11 -0
- package/dist/system/service/linux.js +99 -0
- package/dist/system/service/macos.js +123 -0
- package/dist/system/types.js +0 -0
- package/dist/template/package.json +1 -1
- package/package.json +30 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zohayr SLILEH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @phreshos/cli
|
|
2
2
|
|
|
3
|
-
The `phresh` command for creating
|
|
4
|
-
|
|
3
|
+
The `phresh` command for creating and operating Programs, and for installing
|
|
4
|
+
and managing the PhreshOS System on the current machine.
|
|
5
5
|
|
|
6
6
|
## Package status
|
|
7
7
|
|
|
@@ -21,6 +21,7 @@ phresh install # lay this program out on this machine
|
|
|
21
21
|
phresh uninstall # remove its installed form
|
|
22
22
|
phresh start # run what your build left, and stay with it
|
|
23
23
|
phresh dev # run from source, and stay with it
|
|
24
|
+
phresh system status # inspect the local System and its background service
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
`phresh --help` lists them, `phresh <command> --help` explains one, and
|
|
@@ -28,11 +29,45 @@ phresh dev # run from source, and stay with it
|
|
|
28
29
|
unknown command, an unknown flag and a malformed option are each refused
|
|
29
30
|
and named.
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
Every top-level Program command acts on the current project, and that list ends
|
|
33
|
+
there. It accepts no arbitrary Program identity and has no word for a Process,
|
|
34
|
+
Window, store, or setting. Machine lifecycle is isolated under `phresh system`;
|
|
35
|
+
it manages the System installation and native service, not the state inside the
|
|
36
|
+
System.
|
|
37
|
+
|
|
38
|
+
## System lifecycle
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
phresh system install
|
|
42
|
+
phresh system uninstall
|
|
43
|
+
phresh system status
|
|
44
|
+
phresh system version
|
|
45
|
+
phresh system start
|
|
46
|
+
phresh system stop
|
|
47
|
+
phresh system enable
|
|
48
|
+
phresh system disable
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`install` resolves the newest compatible stable release from the official
|
|
52
|
+
[`PhreshOS/system`](https://github.com/PhreshOS/system) GitHub Releases. It
|
|
53
|
+
downloads the production archive and adjacent checksum, verifies every byte,
|
|
54
|
+
installs production dependencies into a staged version directory, atomically
|
|
55
|
+
points the stable `current` path at it, then registers, enables, and starts the
|
|
56
|
+
native per-user service. The selected release and the service entry therefore
|
|
57
|
+
cannot become two competing sources of truth if installation is interrupted.
|
|
58
|
+
It never reads a source checkout and never requires Bun or TypeScript.
|
|
59
|
+
|
|
60
|
+
The System runs under `launchd` on macOS and `systemd --user` on Linux. The
|
|
61
|
+
native manager owns it after the CLI exits and restarts a failed active
|
|
62
|
+
service. `start` and `stop` change current execution only; `enable` and
|
|
63
|
+
`disable` change automatic startup only. `status` reports the installed version,
|
|
64
|
+
service readiness, and automatic startup without changing them; `version`
|
|
65
|
+
reports only the installed System release.
|
|
66
|
+
|
|
67
|
+
Installation files and persistent System state have separate homes. Removing
|
|
68
|
+
the System unregisters its service and removes its release files while keeping
|
|
69
|
+
`~/.phreshos`, including Programs and owner data. Windows remains unsupported
|
|
70
|
+
until the System has an equally strong local-intake authorization model there.
|
|
36
71
|
|
|
37
72
|
## create
|
|
38
73
|
|
|
@@ -55,11 +90,9 @@ phresh create status-board \
|
|
|
55
90
|
```
|
|
56
91
|
|
|
57
92
|
Dependencies are installed by default. `--no-install` creates the same valid
|
|
58
|
-
project and leaves installation as the first reported next step.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
distributed CLI, uses the published versions embedded in the template. Both
|
|
62
|
-
choices pass through the same dependency resolver used by `init`.
|
|
93
|
+
project and leaves installation as the first reported next step. Generated and
|
|
94
|
+
initialized Programs always use the published package ranges embedded in the
|
|
95
|
+
CLI; repository layout never changes dependency meaning.
|
|
63
96
|
|
|
64
97
|
## Saying something to a program you start
|
|
65
98
|
|
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, Option } from "commander";
|
|
3
3
|
import metadata from "../package.json" with { type: "json" };
|
|
4
|
-
import { PromptCancelled } from "./prompts.js";
|
|
4
|
+
import { PromptCancelled, ReportedFailure } from "./prompts.js";
|
|
5
5
|
import create from "./create.js";
|
|
6
6
|
import install from "./install.js";
|
|
7
7
|
import launch from "./launch.js";
|
|
8
8
|
import init from "./init.js";
|
|
9
9
|
import pack from "./pack.js";
|
|
10
10
|
import uninstall from "./uninstall.js";
|
|
11
|
+
import systemCommands from "./system/command.js";
|
|
11
12
|
const { version } = metadata;
|
|
12
13
|
const coreRange = metadata.dependencies["@phreshos/core"];
|
|
13
14
|
const runOptionPrefix = "--run-option-";
|
|
14
15
|
const program = new Command()
|
|
15
16
|
.name("phresh")
|
|
16
|
-
.description("Create and manage
|
|
17
|
+
.description("Create Programs and manage PhreshOS")
|
|
17
18
|
.version(version, "-v, --version")
|
|
18
19
|
.showHelpAfterError()
|
|
19
|
-
.showSuggestionAfterError()
|
|
20
|
+
.showSuggestionAfterError()
|
|
21
|
+
.configureOutput({
|
|
22
|
+
writeOut: value => process.stdout.write(spaced(value)),
|
|
23
|
+
writeErr: value => process.stderr.write(spaced(value))
|
|
24
|
+
});
|
|
20
25
|
program.addHelpText("after", "\nRun phresh <command> --help for detailed command guidance.\n");
|
|
21
26
|
describe(program.command("create")
|
|
22
27
|
.description("create a new Program project")
|
|
@@ -106,6 +111,7 @@ attached("dev", "run the development Program without installing", [
|
|
|
106
111
|
"Runs the same attached lifecycle as start using the development",
|
|
107
112
|
"declarations. A Client URL must respond within 15 seconds before launch."
|
|
108
113
|
], "development");
|
|
114
|
+
systemCommands(program);
|
|
109
115
|
// Every command begins with the same breathing room. Keep this at the entry
|
|
110
116
|
// point so individual commands never need to manufacture their own opening.
|
|
111
117
|
console.log("");
|
|
@@ -117,11 +123,18 @@ try {
|
|
|
117
123
|
catch (error) {
|
|
118
124
|
if (error instanceof PromptCancelled)
|
|
119
125
|
process.exitCode = 0;
|
|
126
|
+
else if (error instanceof ReportedFailure)
|
|
127
|
+
process.exitCode = 1;
|
|
120
128
|
else {
|
|
121
129
|
console.error(`\n phresh: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
122
130
|
process.exitCode = 1;
|
|
123
131
|
}
|
|
124
132
|
}
|
|
133
|
+
function spaced(value) {
|
|
134
|
+
if (value.endsWith("\n\n"))
|
|
135
|
+
return value;
|
|
136
|
+
return value.endsWith("\n") ? `${value}\n` : `${value}\n\n`;
|
|
137
|
+
}
|
|
125
138
|
function attached(name, summary, detail, mode) {
|
|
126
139
|
describe(program.command(name)
|
|
127
140
|
.description(summary)
|
package/dist/create.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { installProjectDependencies, projectPackageManager, projectScript } from "./project-dependency.js";
|
|
2
2
|
import prompts from "./prompts.js";
|
|
3
3
|
import { accent, bold } from "./style.js";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -36,7 +36,7 @@ export default async function create(options = {}, directory = process.cwd()) {
|
|
|
36
36
|
try {
|
|
37
37
|
cpSync(bundled.directory, staging, { recursive: true });
|
|
38
38
|
renameSync(resolve(staging, "gitignore"), resolve(staging, ".gitignore"));
|
|
39
|
-
customize(staging,
|
|
39
|
+
customize(staging, identity, name, manager);
|
|
40
40
|
renameSync(staging, target);
|
|
41
41
|
placed = true;
|
|
42
42
|
// A project inside this repository becomes a real workspace member
|
|
@@ -60,6 +60,7 @@ export default async function create(options = {}, directory = process.cwd()) {
|
|
|
60
60
|
console.log(bold(`\n${bundled.development ? "Run Development Program" : "Run Program"}`));
|
|
61
61
|
console.log(accent(script));
|
|
62
62
|
console.log(bold("\nYou can now open the project and start building your Program"));
|
|
63
|
+
console.log("");
|
|
63
64
|
}
|
|
64
65
|
function template() {
|
|
65
66
|
const candidates = [
|
|
@@ -79,7 +80,7 @@ function template() {
|
|
|
79
80
|
}
|
|
80
81
|
throw new Error("The CLI template has not been built — run its build command and try again");
|
|
81
82
|
}
|
|
82
|
-
function customize(directory,
|
|
83
|
+
function customize(directory, identity, name, manager) {
|
|
83
84
|
for (const path of textFiles(directory)) {
|
|
84
85
|
let content = readFileSync(path, "utf-8");
|
|
85
86
|
content = content.replaceAll("phresh-program", identity).replaceAll("Phresh Program", name);
|
|
@@ -93,15 +94,6 @@ function customize(directory, finalDirectory, identity, name, manager) {
|
|
|
93
94
|
const manifestPath = resolve(directory, "package.json");
|
|
94
95
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
95
96
|
manifest.name = identity;
|
|
96
|
-
for (const dependencies of [manifest.dependencies, manifest.devDependencies]) {
|
|
97
|
-
if (!dependencies)
|
|
98
|
-
continue;
|
|
99
|
-
for (const [dependency, range] of Object.entries(dependencies)) {
|
|
100
|
-
if (!isProjectPackage(dependency))
|
|
101
|
-
continue;
|
|
102
|
-
dependencies[dependency] = projectDependency(dependency, range, finalDirectory).manifestSpecifier;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
97
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 4) + "\n");
|
|
106
98
|
}
|
|
107
99
|
function textFiles(directory) {
|
|
@@ -120,13 +112,6 @@ function packageManager(value) {
|
|
|
120
112
|
return value;
|
|
121
113
|
throw new Error(`The package manager must be bun, npm, pnpm, or yarn; received "${value}"`);
|
|
122
114
|
}
|
|
123
|
-
function isProjectPackage(value) {
|
|
124
|
-
return value === "@phreshos/core"
|
|
125
|
-
|| value === "@phreshos/client"
|
|
126
|
-
|| value === "@phreshos/server"
|
|
127
|
-
|| value === "@phreshos/react"
|
|
128
|
-
|| value === "@phreshos/cli";
|
|
129
|
-
}
|
|
130
115
|
function title(identity) {
|
|
131
116
|
return identity.split("-").map(word => word[0].toUpperCase() + word.slice(1)).join(" ");
|
|
132
117
|
}
|
|
@@ -1,37 +1,11 @@
|
|
|
1
1
|
import { line } from "./style.js";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import {
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
5
|
/** Returns the package-manager command that runs one project script. */
|
|
6
6
|
export function projectScript(directory, declared, script) {
|
|
7
7
|
return `${projectPackageManager(directory, declared).name} run ${script}`;
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
* Resolves an SDK through the one source policy shared by `init` and `create`.
|
|
11
|
-
*
|
|
12
|
-
* A Program inside this repository's workspace uses its sibling dev-kit. A
|
|
13
|
-
* standalone project or distributed CLI keeps the published range embedded
|
|
14
|
-
* in the generated template. Local packages therefore never leak their own
|
|
15
|
-
* workspace-only development graph into an unrelated project.
|
|
16
|
-
*/
|
|
17
|
-
export function projectDependency(name, range, directory) {
|
|
18
|
-
const workspaceDirectory = resolve(import.meta.dirname, "..", "..", "..");
|
|
19
|
-
const workspace = manifestAt(workspaceDirectory);
|
|
20
|
-
const sourceDirectory = resolve(import.meta.dirname, "..", "..", localDirectories[name]);
|
|
21
|
-
const manifest = manifestAt(sourceDirectory);
|
|
22
|
-
if (workspace?.name === "@phreshos/workspace" && workspaceIncludes(workspaceDirectory, directory, workspace.workspaces) && manifest?.name === name && manifest.version && accepts(range, manifest.version)) {
|
|
23
|
-
return {
|
|
24
|
-
installSpecifier: `${name}@workspace:*`,
|
|
25
|
-
manifestSpecifier: "workspace:*",
|
|
26
|
-
local: true
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
installSpecifier: `${name}@${range}`,
|
|
31
|
-
manifestSpecifier: range,
|
|
32
|
-
local: false
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
9
|
/** Detects the package manager declared by, present in, or invoking a project. */
|
|
36
10
|
export function projectPackageManager(directory, declared, preferred) {
|
|
37
11
|
const named = preferred ?? packageManagerName(declared) ?? packageManagerFromLocks(directory) ?? packageManagerFromInvocation();
|
|
@@ -57,9 +31,8 @@ export default async function ensureProjectDependency(name, range, directory = p
|
|
|
57
31
|
if (manifest.dependencies?.[name] || manifest.devDependencies?.[name])
|
|
58
32
|
return;
|
|
59
33
|
const manager = projectPackageManager(directory, manifest.packageManager);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await run(manager.name, [...manager.addArgs(section), source.installSpecifier], directory);
|
|
34
|
+
line("dependency", name, `${manager.name}, ${range}`);
|
|
35
|
+
await run(manager.name, [...manager.addArgs(section), `${name}@${range}`], directory);
|
|
63
36
|
console.log("");
|
|
64
37
|
}
|
|
65
38
|
function run(command, args, directory, output = "inherit") {
|
|
@@ -85,21 +58,6 @@ function run(command, args, directory, output = "inherit") {
|
|
|
85
58
|
});
|
|
86
59
|
});
|
|
87
60
|
}
|
|
88
|
-
function accepts(range, version) {
|
|
89
|
-
return range === "workspace:*" || range === version || range === `^${version}` || range === `~${version}`;
|
|
90
|
-
}
|
|
91
|
-
function workspaceIncludes(root, directory, patterns) {
|
|
92
|
-
const local = relative(root, resolve(directory)).replaceAll("\\", "/");
|
|
93
|
-
if (!local || local === ".." || local.startsWith("../") || isAbsolute(local))
|
|
94
|
-
return false;
|
|
95
|
-
return patterns?.some(function (pattern) {
|
|
96
|
-
if (!pattern.endsWith("/*"))
|
|
97
|
-
return local === pattern;
|
|
98
|
-
const prefix = pattern.slice(0, -1);
|
|
99
|
-
const child = local.startsWith(prefix) ? local.slice(prefix.length) : "";
|
|
100
|
-
return Boolean(child) && !child.includes("/");
|
|
101
|
-
}) ?? false;
|
|
102
|
-
}
|
|
103
61
|
function manifestAt(directory) {
|
|
104
62
|
const path = resolve(directory, "package.json");
|
|
105
63
|
if (!existsSync(path))
|
|
@@ -132,13 +90,6 @@ function packageManagerFromInvocation() {
|
|
|
132
90
|
function isPackageManager(value) {
|
|
133
91
|
return value === "bun" || value === "npm" || value === "pnpm" || value === "yarn";
|
|
134
92
|
}
|
|
135
|
-
const localDirectories = {
|
|
136
|
-
"@phreshos/core": "core-sdk",
|
|
137
|
-
"@phreshos/client": "client-sdk",
|
|
138
|
-
"@phreshos/server": "server-sdk",
|
|
139
|
-
"@phreshos/react": "react-sdk",
|
|
140
|
-
"@phreshos/cli": "cli"
|
|
141
|
-
};
|
|
142
93
|
const managers = {
|
|
143
94
|
bun: {
|
|
144
95
|
name: "bun",
|
package/dist/prompts.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
|
|
2
2
|
import colors from "picocolors";
|
|
3
|
-
import { column,
|
|
3
|
+
import { caution, column, ending, line, section } from "./style.js";
|
|
4
4
|
/** Signals an ordinary interactive cancellation rather than an operation failure. */
|
|
5
5
|
export class PromptCancelled extends Error {
|
|
6
6
|
}
|
|
7
|
+
/** An unsuccessful command whose complete explanation has already been shown. */
|
|
8
|
+
export class ReportedFailure extends Error {
|
|
9
|
+
}
|
|
7
10
|
/**
|
|
8
11
|
* One interaction language shared by commands that can ask questions.
|
|
9
12
|
*
|
|
@@ -16,26 +19,32 @@ export default function prompts() {
|
|
|
16
19
|
if (interactive)
|
|
17
20
|
intro(`${colors.bold(title)}${context ? ` ${colors.dim(`· ${context}`)}` : ""}`);
|
|
18
21
|
else
|
|
19
|
-
|
|
22
|
+
section(title, context);
|
|
20
23
|
}
|
|
21
24
|
function finish(message) {
|
|
22
25
|
if (interactive)
|
|
23
26
|
outro(colors.bold(message));
|
|
24
27
|
else
|
|
25
|
-
|
|
28
|
+
ending(message);
|
|
26
29
|
}
|
|
27
30
|
function detail(label, value, source) {
|
|
28
31
|
if (interactive)
|
|
29
|
-
log.message(`${colors.dim(column(label))}${value}${source ? ` ${colors.dim(source)}` : ""}
|
|
32
|
+
log.message(`${colors.dim(column(label))}${value}${source ? ` ${colors.dim(source)}` : ""}`, { spacing: 0 });
|
|
30
33
|
else
|
|
31
34
|
line(label, value, source);
|
|
32
35
|
}
|
|
33
36
|
function message(value = "") {
|
|
34
37
|
if (interactive)
|
|
35
|
-
log.message(value);
|
|
38
|
+
log.message(value, { spacing: 0 });
|
|
36
39
|
else
|
|
37
40
|
console.log(value ? ` ${value}` : "");
|
|
38
41
|
}
|
|
42
|
+
function warning(value) {
|
|
43
|
+
if (interactive)
|
|
44
|
+
log.warning(value, { spacing: 0 });
|
|
45
|
+
else
|
|
46
|
+
line("warning", caution(value));
|
|
47
|
+
}
|
|
39
48
|
async function progress(message, completed, work) {
|
|
40
49
|
if (!interactive)
|
|
41
50
|
return await work();
|
|
@@ -55,7 +64,7 @@ export default function prompts() {
|
|
|
55
64
|
async function ask(explanation, question, fallback) {
|
|
56
65
|
if (!interactive)
|
|
57
66
|
throw new Error(`${question} Supply the corresponding option when no terminal is attached`);
|
|
58
|
-
log.message(colors.dim(explanation));
|
|
67
|
+
log.message(colors.dim(explanation), { spacing: 0 });
|
|
59
68
|
const value = await text({
|
|
60
69
|
message: question,
|
|
61
70
|
placeholder: fallback,
|
|
@@ -68,7 +77,7 @@ export default function prompts() {
|
|
|
68
77
|
async function yes(explanation, question, fallback) {
|
|
69
78
|
if (!interactive)
|
|
70
79
|
throw new Error(`${question} Supply the corresponding option when no terminal is attached`);
|
|
71
|
-
log.message(colors.dim(explanation));
|
|
80
|
+
log.message(colors.dim(explanation), { spacing: 0 });
|
|
72
81
|
const value = await confirm({ message: question, initialValue: fallback });
|
|
73
82
|
if (isCancel(value))
|
|
74
83
|
stop();
|
|
@@ -77,7 +86,7 @@ export default function prompts() {
|
|
|
77
86
|
async function choose(explanation, question, values, fallback) {
|
|
78
87
|
if (!interactive)
|
|
79
88
|
throw new Error(`${question} Supply the corresponding option when no terminal is attached`);
|
|
80
|
-
log.message(colors.dim(explanation));
|
|
89
|
+
log.message(colors.dim(explanation), { spacing: 0 });
|
|
81
90
|
const value = await select({
|
|
82
91
|
message: question,
|
|
83
92
|
options: values.map(value => ({ value, label: value })),
|
|
@@ -97,6 +106,7 @@ export default function prompts() {
|
|
|
97
106
|
finish,
|
|
98
107
|
detail,
|
|
99
108
|
message,
|
|
109
|
+
warning,
|
|
100
110
|
progress,
|
|
101
111
|
ask,
|
|
102
112
|
yes,
|
package/dist/style.js
CHANGED
|
@@ -3,6 +3,9 @@ import colors from "picocolors";
|
|
|
3
3
|
export const dim = colors.dim;
|
|
4
4
|
export const bold = colors.bold;
|
|
5
5
|
export const accent = colors.cyan;
|
|
6
|
+
export const positive = colors.green;
|
|
7
|
+
export const caution = colors.yellow;
|
|
8
|
+
export const negative = colors.red;
|
|
6
9
|
// A label, what it says, and where that came from. The label is quiet
|
|
7
10
|
// and the value is not, because the value is the thing being reported.
|
|
8
11
|
//
|
|
@@ -17,6 +20,13 @@ export function column(label) {
|
|
|
17
20
|
}
|
|
18
21
|
export function heading(title, note) {
|
|
19
22
|
console.log("");
|
|
23
|
+
section(title, note);
|
|
24
|
+
console.log("");
|
|
25
|
+
}
|
|
26
|
+
export function section(title, note) {
|
|
20
27
|
console.log(` ${bold(title)}${note ? ` ${dim("·")} ${dim(note)}` : ""}`);
|
|
28
|
+
}
|
|
29
|
+
export function ending(message) {
|
|
30
|
+
console.log(` ${bold(message)}`);
|
|
21
31
|
console.log("");
|
|
22
32
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import SystemLifecycle from "./lifecycle.js";
|
|
2
|
+
import prompts, { ReportedFailure } from "../prompts.js";
|
|
3
|
+
import { accent, caution, dim, negative, positive } from "../style.js";
|
|
4
|
+
/** Attach the System lifecycle without mixing it with Program commands. */
|
|
5
|
+
export default function systemCommands(program, provided) {
|
|
6
|
+
let lifecycle = provided;
|
|
7
|
+
const current = () => lifecycle ?? (lifecycle = new SystemLifecycle());
|
|
8
|
+
const system = program.command("system").description("install and manage the PhreshOS System");
|
|
9
|
+
system.command("install")
|
|
10
|
+
.description("install or update the System and start its service")
|
|
11
|
+
.action(async function () {
|
|
12
|
+
const interaction = prompts();
|
|
13
|
+
interaction.begin("Install System", "official stable release");
|
|
14
|
+
const status = await interaction.progress("Installing PhreshOS", "PhreshOS installed", () => current().install());
|
|
15
|
+
interaction.finish(`PhreshOS ${accent(status.installed?.version ?? "")} installed`);
|
|
16
|
+
});
|
|
17
|
+
system.command("uninstall")
|
|
18
|
+
.description("remove the System installation and service")
|
|
19
|
+
.action(async function () {
|
|
20
|
+
const interaction = prompts();
|
|
21
|
+
interaction.begin("Uninstall System");
|
|
22
|
+
await interaction.progress("Removing PhreshOS", "PhreshOS removed", () => current().uninstall());
|
|
23
|
+
interaction.message("Persistent System data was kept.");
|
|
24
|
+
interaction.finish("System uninstalled");
|
|
25
|
+
});
|
|
26
|
+
system.command("status")
|
|
27
|
+
.description("show the System version and operating state")
|
|
28
|
+
.action(async function () {
|
|
29
|
+
const status = await installed(current());
|
|
30
|
+
const interaction = prompts();
|
|
31
|
+
interaction.begin("System Status");
|
|
32
|
+
report(interaction, status);
|
|
33
|
+
interaction.finish(status.ready ? positive("System ready") : caution("System not ready"));
|
|
34
|
+
});
|
|
35
|
+
system.command("version")
|
|
36
|
+
.description("show the installed System version")
|
|
37
|
+
.action(async function () {
|
|
38
|
+
const status = await installed(current());
|
|
39
|
+
const interaction = prompts();
|
|
40
|
+
interaction.begin("System Version");
|
|
41
|
+
interaction.finish(`PhreshOS ${accent(status.installed.version)}`);
|
|
42
|
+
});
|
|
43
|
+
action(system, "start", "start the background service", current, lifecycle => lifecycle.start());
|
|
44
|
+
action(system, "stop", "stop the background service", current, lifecycle => lifecycle.stop());
|
|
45
|
+
action(system, "enable", "enable automatic startup", current, lifecycle => lifecycle.enable());
|
|
46
|
+
action(system, "disable", "disable automatic startup", current, lifecycle => lifecycle.disable());
|
|
47
|
+
return system;
|
|
48
|
+
}
|
|
49
|
+
function action(system, name, description, current, work) {
|
|
50
|
+
system.command(name)
|
|
51
|
+
.description(description)
|
|
52
|
+
.action(async function () {
|
|
53
|
+
const lifecycle = current();
|
|
54
|
+
await installed(lifecycle);
|
|
55
|
+
const interaction = prompts();
|
|
56
|
+
interaction.begin(`System ${title(name)}`);
|
|
57
|
+
await interaction.progress(`${title(name)}ing PhreshOS`, `PhreshOS ${past(name)}`, () => work(lifecycle));
|
|
58
|
+
interaction.finish(`System ${past(name)}`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function report(interaction, status) {
|
|
62
|
+
interaction.detail("version", accent(status.installed?.version ?? "unknown"));
|
|
63
|
+
interaction.detail("service", service(status));
|
|
64
|
+
interaction.detail("startup", status.enabled ? positive("enabled") : dim("disabled"));
|
|
65
|
+
}
|
|
66
|
+
async function installed(lifecycle) {
|
|
67
|
+
const status = await lifecycle.status();
|
|
68
|
+
if (status.installed)
|
|
69
|
+
return { ...status, installed: status.installed };
|
|
70
|
+
const interaction = prompts();
|
|
71
|
+
interaction.begin("PhreshOS System");
|
|
72
|
+
interaction.warning("PhreshOS System is not installed");
|
|
73
|
+
interaction.finish(`${dim("Install it with")} ${accent("phresh system install")}`);
|
|
74
|
+
throw new ReportedFailure();
|
|
75
|
+
}
|
|
76
|
+
function service(status) {
|
|
77
|
+
if (status.ready)
|
|
78
|
+
return positive("ready");
|
|
79
|
+
if (status.running)
|
|
80
|
+
return caution("starting");
|
|
81
|
+
if (status.registered)
|
|
82
|
+
return caution("stopped");
|
|
83
|
+
return negative("not registered");
|
|
84
|
+
}
|
|
85
|
+
function title(value) {
|
|
86
|
+
return `${value[0]?.toUpperCase()}${value.slice(1)}`;
|
|
87
|
+
}
|
|
88
|
+
function past(value) {
|
|
89
|
+
if (value === "stop")
|
|
90
|
+
return "stopped";
|
|
91
|
+
if (value === "enable")
|
|
92
|
+
return "enabled";
|
|
93
|
+
if (value === "disable")
|
|
94
|
+
return "disabled";
|
|
95
|
+
return "started";
|
|
96
|
+
}
|