@phreshos/cli 0.1.3 → 0.1.5
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 +42 -9
- package/dist/build-template.js +90 -0
- package/dist/cli.js +134 -202
- package/dist/create.js +139 -0
- package/dist/derive.js +4 -4
- package/dist/init.js +115 -137
- package/dist/pack.js +9 -8
- package/dist/program-intake.js +10 -2
- package/dist/project-dependency.js +115 -38
- package/dist/project.js +2 -3
- package/dist/prompts.js +105 -0
- package/dist/style.js +9 -14
- package/dist/template/README.md +28 -0
- package/dist/template/api-docs.md +38 -0
- package/dist/template/gitignore +30 -0
- package/dist/template/icon.png +0 -0
- package/dist/template/package.json +28 -0
- package/dist/template/phresh.config.ts +109 -0
- package/dist/template/source/build.ts +17 -0
- package/dist/template/source/client/app.tsx +36 -0
- package/dist/template/source/client/dec.d.ts +1 -0
- package/dist/template/source/client/index.html +13 -0
- package/dist/template/source/client/main.tsx +8 -0
- package/dist/template/source/client/style.css +81 -0
- package/dist/template/source/server/dec.d.ts +1 -0
- package/dist/template/source/server/main.ts +14 -0
- package/dist/template/tsconfig.json +24 -0
- package/dist/template/vite.client.ts +22 -0
- package/dist/template/vite.server.ts +27 -0
- package/dist/template.json +5 -0
- package/package.json +9 -4
- package/dist/relative-value.js +0 -118
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Its Program commands depend on `@phreshos/core`, and its runtime operations
|
|
|
14
14
|
require a compatible system installation.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
+
phresh create app # create a complete new Program
|
|
17
18
|
phresh init # describe this program, once
|
|
18
19
|
phresh pack # run the optional build, then package its result
|
|
19
20
|
phresh install # lay this program out on this machine
|
|
@@ -33,6 +34,33 @@ and has no word for a process, a window, a store or a setting. The system's
|
|
|
33
34
|
local intake accepts exactly the Program this project declares, and so does
|
|
34
35
|
this tool.
|
|
35
36
|
|
|
37
|
+
## create
|
|
38
|
+
|
|
39
|
+
`phresh create <directory>` creates a complete Server and Client Program from
|
|
40
|
+
the maintained `PhreshOS/phresh-program` repository. The CLI build downloads
|
|
41
|
+
one exact tagged source release, verifies its SHA-256 checksum, removes
|
|
42
|
+
repository-only material, and bundles the resulting authoring project. The
|
|
43
|
+
installed CLI therefore creates projects offline without reading a live branch
|
|
44
|
+
or maintaining a second template by hand.
|
|
45
|
+
|
|
46
|
+
The directory name becomes the stable kebab-case Program identity. In a
|
|
47
|
+
terminal, `create` asks for the directory when it is omitted, the readable
|
|
48
|
+
Program name, and the package manager. With no terminal, the directory is the
|
|
49
|
+
first argument and every optional choice is named:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
phresh create status-board \
|
|
53
|
+
--name "Status Board" \
|
|
54
|
+
--package-manager npm
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Dependencies are installed by default. `--no-install` creates the same valid
|
|
58
|
+
project and leaves installation as the first reported next step. A project
|
|
59
|
+
created inside this repository's workspace uses its sibling dev-kits through
|
|
60
|
+
the workspace protocol. A standalone project, including one created by a
|
|
61
|
+
distributed CLI, uses the published versions embedded in the template. Both
|
|
62
|
+
choices pass through the same dependency resolver used by `init`.
|
|
63
|
+
|
|
36
64
|
## Saying something to a program you start
|
|
37
65
|
|
|
38
66
|
```bash
|
|
@@ -89,7 +117,7 @@ export default defineConfig({
|
|
|
89
117
|
|
|
90
118
|
apiDocs: "api.md",
|
|
91
119
|
|
|
92
|
-
|
|
120
|
+
icon: "icon.png",
|
|
93
121
|
|
|
94
122
|
buildCommand: "bun run build",
|
|
95
123
|
|
|
@@ -184,7 +212,9 @@ In a terminal, `init` asks for the production locations and commands needed by
|
|
|
184
212
|
`start` and `install`, including whether a package build should prepare those
|
|
185
213
|
locations. It then offers the development command and URL needed by `dev`.
|
|
186
214
|
Existing `build` and `dev` package scripts become editable suggestions, never
|
|
187
|
-
silent assumptions.
|
|
215
|
+
silent assumptions. A single Endpoint defaults to `dist`; when both Endpoints
|
|
216
|
+
exist, their defaults are `dist/server` and `dist/client`. API documentation is
|
|
217
|
+
opt-in and defaults to disabled even when a likely document already exists.
|
|
188
218
|
|
|
189
219
|
Outside a terminal it never waits for input; the same values are supplied as
|
|
190
220
|
named options:
|
|
@@ -197,7 +227,7 @@ phresh init --client \
|
|
|
197
227
|
--client-development-start-command "bun run dev"
|
|
198
228
|
|
|
199
229
|
phresh init --server \
|
|
200
|
-
--server-location
|
|
230
|
+
--server-location dist \
|
|
201
231
|
--server-start-command "node main.js"
|
|
202
232
|
```
|
|
203
233
|
|
|
@@ -213,7 +243,7 @@ The final `Next` line is derived from the resulting config. It always shows
|
|
|
213
243
|
one Endpoint received a development declaration.
|
|
214
244
|
|
|
215
245
|
Visual and advanced runtime defaults remain for the author to add deliberately:
|
|
216
|
-
`
|
|
246
|
+
`icon`, `size`, `position`, `installCommand`, `start`, layers, and minimization
|
|
217
247
|
are not guessed. An omitted `start` is `true`; only a default-off Endpoint needs to
|
|
218
248
|
say `start: false`.
|
|
219
249
|
|
|
@@ -221,9 +251,12 @@ say `start: false`.
|
|
|
221
251
|
|
|
222
252
|
Both **run your program without installing it, and stay attached.** They
|
|
223
253
|
print the `program.json` it will be declared as, hand that to the system
|
|
224
|
-
through the socket
|
|
225
|
-
|
|
226
|
-
|
|
254
|
+
through the socket below the selected system home. With no override, that is
|
|
255
|
+
`~/.phreshos/intake.sock`. Set `PHRESHOS_HOME` to an absolute system home to
|
|
256
|
+
address another system instance; the CLI derives
|
|
257
|
+
`<PHRESHOS_HOME>/intake.sock` from it. The socket is not selected
|
|
258
|
+
separately from its instance. Only your account can open it, so nothing is
|
|
259
|
+
sent to prove anything, and then the command holds.
|
|
227
260
|
|
|
228
261
|
**The connection is the tether, in both directions.** Ctrl-C and your
|
|
229
262
|
program stops. Close the window it opened and the command returns, with
|
|
@@ -339,8 +372,8 @@ register a subscription. Those are system contracts documented once by the
|
|
|
339
372
|
SDKs; `api-docs.md` supplies only the Program-specific meaning carried through
|
|
340
373
|
them.
|
|
341
374
|
|
|
342
|
-
There is **no wrapping directory**: `program.json`, `server/`, `client
|
|
343
|
-
`
|
|
375
|
+
There is **no wrapping directory**: `program.json`, `server/`, `client/`,
|
|
376
|
+
optional `icon.png`, and optional `api-docs.md` sit at the package's root, and the system names the
|
|
344
377
|
directory it installs into from your program's `identity`.
|
|
345
378
|
|
|
346
379
|
Nothing about the system moves because this exists. `program.json` is
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import metadata from "../package.json" with { type: "json" };
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, resolve, sep } from "node:path";
|
|
5
|
+
import AdmZip from "adm-zip";
|
|
6
|
+
import { readConfig } from "./project.js";
|
|
7
|
+
const repository = "PhreshOS/phresh-program";
|
|
8
|
+
const release = {
|
|
9
|
+
version: "0.1.0",
|
|
10
|
+
sha256: "6e1a87a037a6ac5f82ff4c7d420757f972098387d73a8b4165aab2ef331cec36"
|
|
11
|
+
};
|
|
12
|
+
const archiveUrl = `https://github.com/${repository}/archive/refs/tags/v${release.version}.zip`;
|
|
13
|
+
const output = resolve(import.meta.dirname, "template");
|
|
14
|
+
const descriptionOutput = resolve(import.meta.dirname, "template.json");
|
|
15
|
+
const excludedDirectories = new Set([".github", "dist", "node_modules", "scripts", "storage"]);
|
|
16
|
+
const excludedRootFiles = new Set(["CONTRIBUTING.md", "LICENSE", "SECURITY.md"]);
|
|
17
|
+
const excludedFiles = new Set([
|
|
18
|
+
".DS_Store",
|
|
19
|
+
"bun.lock",
|
|
20
|
+
"bun.lockb",
|
|
21
|
+
"package-lock.json",
|
|
22
|
+
"pnpm-lock.yaml",
|
|
23
|
+
"yarn.lock"
|
|
24
|
+
]);
|
|
25
|
+
const response = await fetch(archiveUrl);
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
throw new Error(`Could not download ${repository} v${release.version}: ${response.status} ${response.statusText}`);
|
|
28
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
29
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
30
|
+
if (digest !== release.sha256)
|
|
31
|
+
throw new Error(`The ${repository} v${release.version} source archive failed integrity verification`);
|
|
32
|
+
const archive = new AdmZip(bytes);
|
|
33
|
+
const entries = archive.getEntries();
|
|
34
|
+
const roots = new Set(entries.map(entry => entry.entryName.split("/")[0]).filter(Boolean));
|
|
35
|
+
if (roots.size !== 1)
|
|
36
|
+
throw new Error(`The ${repository} v${release.version} source archive has an invalid root`);
|
|
37
|
+
const root = [...roots][0];
|
|
38
|
+
rmSync(output, { recursive: true, force: true });
|
|
39
|
+
rmSync(descriptionOutput, { force: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (entry.isDirectory || !entry.entryName.startsWith(`${root}/`))
|
|
42
|
+
continue;
|
|
43
|
+
const local = entry.entryName.slice(root.length + 1);
|
|
44
|
+
if (!included(local))
|
|
45
|
+
continue;
|
|
46
|
+
const target = local === ".gitignore" ? "gitignore" : local;
|
|
47
|
+
const path = resolve(output, target);
|
|
48
|
+
if (!path.startsWith(`${output}${sep}`))
|
|
49
|
+
throw new Error(`The ${repository} source archive contains an unsafe path: ${local}`);
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
writeFileSync(path, entry.getData());
|
|
52
|
+
}
|
|
53
|
+
const manifestPath = resolve(output, "package.json");
|
|
54
|
+
const manifestSource = archive.readFile(`${root}/package.json`);
|
|
55
|
+
if (!manifestSource)
|
|
56
|
+
throw new Error(`The ${repository} v${release.version} source archive has no package.json`);
|
|
57
|
+
const manifest = JSON.parse(manifestSource.toString("utf8"));
|
|
58
|
+
manifest.scripts = select(manifest.scripts, ["dev", "start"]);
|
|
59
|
+
manifest.devDependencies = {
|
|
60
|
+
...manifest.devDependencies,
|
|
61
|
+
"@phreshos/cli": `^${metadata.version}`
|
|
62
|
+
};
|
|
63
|
+
delete manifest.author;
|
|
64
|
+
delete manifest.license;
|
|
65
|
+
delete manifest.repository;
|
|
66
|
+
delete manifest.bugs;
|
|
67
|
+
delete manifest.homepage;
|
|
68
|
+
delete manifest.packageManager;
|
|
69
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 4) + "\n");
|
|
70
|
+
const config = await readConfig(output);
|
|
71
|
+
if (config.identity !== "phresh-program" || config.name !== "Phresh Program")
|
|
72
|
+
throw new Error(`The ${repository} release is not Phresh Program`);
|
|
73
|
+
writeFileSync(descriptionOutput, JSON.stringify({
|
|
74
|
+
repository,
|
|
75
|
+
version: release.version,
|
|
76
|
+
development: Boolean(config.server?.development || config.client?.development)
|
|
77
|
+
}, null, 4) + "\n");
|
|
78
|
+
function included(local) {
|
|
79
|
+
const parts = local.split("/");
|
|
80
|
+
if (parts.some(part => excludedDirectories.has(part)))
|
|
81
|
+
return false;
|
|
82
|
+
if (parts.length === 1 && excludedRootFiles.has(local))
|
|
83
|
+
return false;
|
|
84
|
+
if (excludedFiles.has(parts.at(-1)))
|
|
85
|
+
return false;
|
|
86
|
+
return !local.endsWith(".zip");
|
|
87
|
+
}
|
|
88
|
+
function select(source, names) {
|
|
89
|
+
return Object.fromEntries(names.flatMap(name => source?.[name] === undefined ? [] : [[name, source[name]]]));
|
|
90
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { Command, Option } from "commander";
|
|
3
3
|
import metadata from "../package.json" with { type: "json" };
|
|
4
|
+
import { PromptCancelled } from "./prompts.js";
|
|
5
|
+
import create from "./create.js";
|
|
4
6
|
import install from "./install.js";
|
|
5
7
|
import launch from "./launch.js";
|
|
6
8
|
import init from "./init.js";
|
|
@@ -8,214 +10,144 @@ import pack from "./pack.js";
|
|
|
8
10
|
import uninstall from "./uninstall.js";
|
|
9
11
|
const { version } = metadata;
|
|
10
12
|
const coreRange = metadata.dependencies["@phreshos/core"];
|
|
11
|
-
/**
|
|
12
|
-
* One command system.
|
|
13
|
-
*
|
|
14
|
-
* Every command declares its options beside its help and all of them pass
|
|
15
|
-
* through the same parser, error presentation, and exit rules. Interactive
|
|
16
|
-
* behavior belongs to the command that needs it; parsing never guesses.
|
|
17
|
-
*/
|
|
18
|
-
const commands = [
|
|
19
|
-
{
|
|
20
|
-
name: "init",
|
|
21
|
-
summary: "initialize an existing Program project",
|
|
22
|
-
detail: [
|
|
23
|
-
"Reads identity, version, and description from package.json, then",
|
|
24
|
-
"writes phresh.config.ts. In a terminal it asks only for the Program",
|
|
25
|
-
"shape and values package.json cannot provide.",
|
|
26
|
-
"",
|
|
27
|
-
"Without a terminal, declare at least one half with named options.",
|
|
28
|
-
"No prompt is opened and no input is awaited."
|
|
29
|
-
],
|
|
30
|
-
options: [
|
|
31
|
-
{ name: "name", value: "name", summary: "human-readable Program name" },
|
|
32
|
-
{ name: "api-docs", value: "path", summary: "official Program API documentation" },
|
|
33
|
-
{ name: "build-command", value: "command", summary: "prepare production files before use" },
|
|
34
|
-
{ name: "server", summary: "include a server half" },
|
|
35
|
-
{ name: "server-location", value: "path", summary: "production server directory" },
|
|
36
|
-
{ name: "server-start-command", value: "command", summary: "production server command" },
|
|
37
|
-
{ name: "server-development-start-command", value: "command", summary: "development server command" },
|
|
38
|
-
{ name: "client", summary: "include a client half" },
|
|
39
|
-
{ name: "client-location", value: "path", summary: "production client directory" },
|
|
40
|
-
{ name: "client-development-url", value: "url", summary: "development client URL" },
|
|
41
|
-
{ name: "client-development-start-command", value: "command", summary: "client development command" },
|
|
42
|
-
{ name: "force", summary: "replace an existing phresh.config.ts" }
|
|
43
|
-
],
|
|
44
|
-
run: options => init({
|
|
45
|
-
name: text(options, "name"),
|
|
46
|
-
apiDocs: text(options, "api-docs"),
|
|
47
|
-
buildCommand: text(options, "build-command"),
|
|
48
|
-
server: options.server === true || text(options, "server-location") !== undefined || text(options, "server-start-command") !== undefined,
|
|
49
|
-
serverLocation: text(options, "server-location"),
|
|
50
|
-
serverStartCommand: text(options, "server-start-command"),
|
|
51
|
-
serverDevelopmentStartCommand: text(options, "server-development-start-command"),
|
|
52
|
-
client: options.client === true || text(options, "client-location") !== undefined,
|
|
53
|
-
clientLocation: text(options, "client-location"),
|
|
54
|
-
clientDevelopmentUrl: text(options, "client-development-url"),
|
|
55
|
-
clientDevelopmentStartCommand: text(options, "client-development-start-command"),
|
|
56
|
-
force: options.force === true
|
|
57
|
-
}, process.cwd(), coreRange)
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: "pack",
|
|
61
|
-
summary: "build and package this Program",
|
|
62
|
-
detail: [
|
|
63
|
-
"Runs an optional buildCommand, then assembles each half from where",
|
|
64
|
-
"the config says the production files are.",
|
|
65
|
-
"",
|
|
66
|
-
"A package carries a Program to another machine. Installing this",
|
|
67
|
-
"project takes no package because its description already names the",
|
|
68
|
-
"files on this machine."
|
|
69
|
-
],
|
|
70
|
-
run: () => pack()
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: "install",
|
|
74
|
-
summary: "install this Program",
|
|
75
|
-
detail: [
|
|
76
|
-
"Builds and lays out the Program declared by this project.",
|
|
77
|
-
"",
|
|
78
|
-
"Running processes end before installed paths change. Program data is",
|
|
79
|
-
"preserved."
|
|
80
|
-
],
|
|
81
|
-
run: () => install()
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: "uninstall",
|
|
85
|
-
summary: "uninstall this Program",
|
|
86
|
-
detail: [
|
|
87
|
-
"Removes the installed Program files. Its running processes, stored",
|
|
88
|
-
"data, and runtime Program remain available.",
|
|
89
|
-
"",
|
|
90
|
-
"--everything ends its processes, removes everything the system owns",
|
|
91
|
-
"for it, and forgets the runtime Program."
|
|
92
|
-
],
|
|
93
|
-
options: [
|
|
94
|
-
{ name: "everything", summary: "also remove processes, data, and runtime state" }
|
|
95
|
-
],
|
|
96
|
-
run: options => uninstall(options.everything === true)
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: "start",
|
|
100
|
-
summary: "run the production Program without installing",
|
|
101
|
-
detail: [
|
|
102
|
-
"Runs this Program attached to the terminal. Its output arrives here,",
|
|
103
|
-
"and when this command ends the system stops the Program.",
|
|
104
|
-
"",
|
|
105
|
-
"An optional buildCommand runs before the production Program starts.",
|
|
106
|
-
"The Program's exit status becomes this command's exit status."
|
|
107
|
-
],
|
|
108
|
-
runOptions: true,
|
|
109
|
-
run: options => launch("production", process.cwd(), options.run)
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: "dev",
|
|
113
|
-
summary: "run the development Program without installing",
|
|
114
|
-
detail: [
|
|
115
|
-
"Runs the same attached lifecycle as start, using each half's",
|
|
116
|
-
"development declaration.",
|
|
117
|
-
"",
|
|
118
|
-
"A declared client development command belongs to this session. Its",
|
|
119
|
-
"URL must respond within 15 seconds before the Program launches."
|
|
120
|
-
],
|
|
121
|
-
runOptions: true,
|
|
122
|
-
run: options => launch("development", process.cwd(), options.run)
|
|
123
|
-
}
|
|
124
|
-
];
|
|
125
13
|
const runOptionPrefix = "--run-option-";
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
14
|
+
const program = new Command()
|
|
15
|
+
.name("phresh")
|
|
16
|
+
.description("Create and manage Programs")
|
|
17
|
+
.version(version, "-v, --version")
|
|
18
|
+
.showHelpAfterError()
|
|
19
|
+
.showSuggestionAfterError();
|
|
20
|
+
program.addHelpText("after", "\nRun phresh <command> --help for detailed command guidance.\n");
|
|
21
|
+
describe(program.command("create")
|
|
22
|
+
.description("create a new Program project")
|
|
23
|
+
.argument("[directory]", "directory for the new Program")
|
|
24
|
+
.option("--name <name>", "human-readable Program name")
|
|
25
|
+
.addOption(new Option("--package-manager <manager>", "package manager used to install dependencies").choices(["bun", "npm", "pnpm", "yarn"]))
|
|
26
|
+
.option("--no-install", "create files without installing dependencies")
|
|
27
|
+
.action(async function (directory, options) {
|
|
28
|
+
await create({
|
|
29
|
+
directory,
|
|
30
|
+
name: options.name,
|
|
31
|
+
packageManager: options.packageManager,
|
|
32
|
+
install: options.install
|
|
33
|
+
});
|
|
34
|
+
}), [
|
|
35
|
+
"Creates a complete Server and Client project from the maintained",
|
|
36
|
+
"Phresh Program template bundled with this CLI.",
|
|
37
|
+
"",
|
|
38
|
+
"In a terminal, omitted values are collected interactively. Automation",
|
|
39
|
+
"supplies the directory and optional choices through named options."
|
|
40
|
+
]);
|
|
41
|
+
describe(program.command("init")
|
|
42
|
+
.description("initialize an existing Program project")
|
|
43
|
+
.option("--name <name>", "human-readable Program name")
|
|
44
|
+
.option("--api-docs <path>", "official Program API documentation")
|
|
45
|
+
.option("--build-command <command>", "prepare production files before use")
|
|
46
|
+
.option("--server", "include a Server endpoint")
|
|
47
|
+
.option("--server-location <path>", "production Server directory")
|
|
48
|
+
.option("--server-start-command <command>", "production Server command")
|
|
49
|
+
.option("--server-development-start-command <command>", "development Server command")
|
|
50
|
+
.option("--client", "include a Client endpoint")
|
|
51
|
+
.option("--client-location <path>", "production Client directory")
|
|
52
|
+
.option("--client-development-url <url>", "development Client URL")
|
|
53
|
+
.option("--client-development-start-command <command>", "development Client command")
|
|
54
|
+
.option("--force", "replace an existing phresh.config.ts")
|
|
55
|
+
.action(async function (options) {
|
|
56
|
+
await init({
|
|
57
|
+
name: options.name,
|
|
58
|
+
apiDocs: options.apiDocs,
|
|
59
|
+
buildCommand: options.buildCommand,
|
|
60
|
+
server: options.server === true || options.serverLocation !== undefined || options.serverStartCommand !== undefined,
|
|
61
|
+
serverLocation: options.serverLocation,
|
|
62
|
+
serverStartCommand: options.serverStartCommand,
|
|
63
|
+
serverDevelopmentStartCommand: options.serverDevelopmentStartCommand,
|
|
64
|
+
client: options.client === true || options.clientLocation !== undefined,
|
|
65
|
+
clientLocation: options.clientLocation,
|
|
66
|
+
clientDevelopmentUrl: options.clientDevelopmentUrl,
|
|
67
|
+
clientDevelopmentStartCommand: options.clientDevelopmentStartCommand,
|
|
68
|
+
force: options.force === true
|
|
69
|
+
}, process.cwd(), coreRange);
|
|
70
|
+
}), [
|
|
71
|
+
"Reads identity, version, and description from package.json, then writes",
|
|
72
|
+
"phresh.config.ts. A terminal asks only for values the project cannot",
|
|
73
|
+
"provide. Without a terminal, declare at least one endpoint with options."
|
|
74
|
+
]);
|
|
75
|
+
describe(program.command("pack")
|
|
76
|
+
.description("build and package this Program")
|
|
77
|
+
.action(async function () {
|
|
78
|
+
await pack();
|
|
79
|
+
}), [
|
|
80
|
+
"Runs an optional buildCommand, then packages the production files",
|
|
81
|
+
"declared for each endpoint."
|
|
82
|
+
]);
|
|
83
|
+
describe(program.command("install")
|
|
84
|
+
.description("install this Program")
|
|
85
|
+
.action(async function () {
|
|
86
|
+
await install();
|
|
87
|
+
}), [
|
|
88
|
+
"Builds and installs the Program declared by this project. Running",
|
|
89
|
+
"Processes end before installed paths change; Program data is preserved."
|
|
90
|
+
]);
|
|
91
|
+
describe(program.command("uninstall")
|
|
92
|
+
.description("uninstall this Program")
|
|
93
|
+
.option("--everything", "also remove Processes, data, and runtime state")
|
|
94
|
+
.action(async function (options) {
|
|
95
|
+
await uninstall(options.everything === true);
|
|
96
|
+
}), [
|
|
97
|
+
"Removes the installed Program files while preserving its Processes, data,",
|
|
98
|
+
"and runtime Program. --everything removes all system-owned state."
|
|
99
|
+
]);
|
|
100
|
+
attached("start", "run the production Program without installing", [
|
|
101
|
+
"Runs this Program attached to the terminal. Output arrives here, and",
|
|
102
|
+
"ending this command stops the Program. An optional buildCommand runs",
|
|
103
|
+
"before launch, and the Program's exit status becomes this command's status."
|
|
104
|
+
], "production");
|
|
105
|
+
attached("dev", "run the development Program without installing", [
|
|
106
|
+
"Runs the same attached lifecycle as start using the development",
|
|
107
|
+
"declarations. A Client URL must respond within 15 seconds before launch."
|
|
108
|
+
], "development");
|
|
109
|
+
// Every command begins with the same breathing room. Keep this at the entry
|
|
110
|
+
// point so individual commands never need to manufacture their own opening.
|
|
111
|
+
console.log("");
|
|
112
|
+
if (process.argv.length === 2)
|
|
113
|
+
program.help();
|
|
145
114
|
try {
|
|
146
|
-
await
|
|
115
|
+
await program.parseAsync();
|
|
147
116
|
}
|
|
148
117
|
catch (error) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
155
|
-
const argument = args[index];
|
|
156
|
-
if (command.runOptions && argument.startsWith(runOptionPrefix)) {
|
|
157
|
-
const said = argument.slice(runOptionPrefix.length);
|
|
158
|
-
const at = said.indexOf("=");
|
|
159
|
-
if (at < 1)
|
|
160
|
-
throw new Error(`"${argument}" says no value — write ${runOptionPrefix}<name>=<value>, and end with = for an empty value`);
|
|
161
|
-
const name = said.slice(0, at);
|
|
162
|
-
if (Object.hasOwn(options.run, name))
|
|
163
|
-
throw new Error(`The run option "${name}" was given more than once`);
|
|
164
|
-
options.run[name] = said.slice(at + 1);
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
if (!argument.startsWith("--"))
|
|
168
|
-
throw new Error(`${command.name} takes no positional arguments, and I was given "${argument}"`);
|
|
169
|
-
const at = argument.indexOf("=");
|
|
170
|
-
const name = argument.slice(2, at < 0 ? undefined : at);
|
|
171
|
-
const declared = command.options?.find(option => option.name === name);
|
|
172
|
-
if (!declared)
|
|
173
|
-
throw new Error(`${command.name} does not know the option "--${name}"`);
|
|
174
|
-
if (Object.hasOwn(options, name))
|
|
175
|
-
throw new Error(`The option "--${name}" was given more than once`);
|
|
176
|
-
if (!declared.value) {
|
|
177
|
-
if (at >= 0)
|
|
178
|
-
throw new Error(`--${name} does not take a value`);
|
|
179
|
-
options[name] = true;
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
const value = at >= 0 ? argument.slice(at + 1) : args[++index];
|
|
183
|
-
if (value === undefined || at < 0 && value.startsWith("--"))
|
|
184
|
-
throw new Error(`--${name} needs <${declared.value}>`);
|
|
185
|
-
options[name] = value;
|
|
118
|
+
if (error instanceof PromptCancelled)
|
|
119
|
+
process.exitCode = 0;
|
|
120
|
+
else {
|
|
121
|
+
console.error(`\n phresh: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
122
|
+
process.exitCode = 1;
|
|
186
123
|
}
|
|
187
|
-
return options;
|
|
188
124
|
}
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
125
|
+
function attached(name, summary, detail, mode) {
|
|
126
|
+
describe(program.command(name)
|
|
127
|
+
.description(summary)
|
|
128
|
+
.argument("[run-options...]", "--run-option-<name>=<value> values passed to the Program")
|
|
129
|
+
.allowUnknownOption()
|
|
130
|
+
.action(async function (args) {
|
|
131
|
+
await launch(mode, process.cwd(), runOptions(name, args));
|
|
132
|
+
}), detail);
|
|
192
133
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
console.log(` ${bold(command.name.padEnd(12))}${command.summary}`);
|
|
197
|
-
console.log("");
|
|
198
|
-
console.log(` ${dim("phresh <command> --help".padEnd(32))}${dim("show one command in detail")}`);
|
|
199
|
-
console.log("");
|
|
134
|
+
function describe(command, paragraphs) {
|
|
135
|
+
command.addHelpText("after", `\n${paragraphs.map(line => line ? ` ${line}` : "").join("\n")}\n`);
|
|
136
|
+
return command;
|
|
200
137
|
}
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
for (const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const signature = `--${option.name}${option.value ? ` <${option.value}>` : ""}`;
|
|
215
|
-
console.log(` ${signature.padEnd(width)}${dim(option.summary)}`);
|
|
216
|
-
}
|
|
217
|
-
if (command.runOptions)
|
|
218
|
-
console.log(` ${(runOptionPrefix + "<name>=<value>").padEnd(width)}${dim("pass text to the launched Program")}`);
|
|
138
|
+
function runOptions(command, args) {
|
|
139
|
+
const options = {};
|
|
140
|
+
for (const argument of args) {
|
|
141
|
+
if (!argument.startsWith(runOptionPrefix))
|
|
142
|
+
throw new Error(`${command} accepts Program options only as ${runOptionPrefix}<name>=<value>; received "${argument}"`);
|
|
143
|
+
const said = argument.slice(runOptionPrefix.length);
|
|
144
|
+
const at = said.indexOf("=");
|
|
145
|
+
if (at < 1)
|
|
146
|
+
throw new Error(`"${argument}" says no value — write ${runOptionPrefix}<name>=<value>, and end with = for an empty value`);
|
|
147
|
+
const name = said.slice(0, at);
|
|
148
|
+
if (Object.hasOwn(options, name))
|
|
149
|
+
throw new Error(`The run option "${name}" was given more than once`);
|
|
150
|
+
options[name] = said.slice(at + 1);
|
|
219
151
|
}
|
|
220
|
-
|
|
152
|
+
return options;
|
|
221
153
|
}
|