@phreshos/cli 0.1.28 → 0.1.29
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 +16 -7
- package/dist/cli.js +5 -1
- package/dist/derive.js +14 -5
- package/dist/init.js +43 -11
- package/dist/launch.js +1 -1
- package/dist/pack.js +12 -1
- package/dist/project.js +23 -5
- package/dist/template/package.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ rule everything else here follows from.
|
|
|
146
146
|
|
|
147
147
|
It follows that every field which lands in a `program.json` is spelled
|
|
148
148
|
the way the contract spells it and crosses untouched: `size`, not a
|
|
149
|
-
width and a height; `startCommand`, not a command.
|
|
149
|
+
width and a height; `startCommand` or `entryFile`, not a generic command.
|
|
150
150
|
|
|
151
151
|
An optional top-level `buildCommand` is authoring metadata. `phresh start`,
|
|
152
152
|
`phresh install`, and `phresh pack` run it from this project before consuming
|
|
@@ -244,11 +244,19 @@ Say how the server runs or where the client is served:
|
|
|
244
244
|
```
|
|
245
245
|
|
|
246
246
|
Each half may carry a `development` block, but the two shapes are deliberately
|
|
247
|
-
different. A
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
different. A Server and its development block each select exactly one of
|
|
248
|
+
`startCommand` or `entryFile`. A command starts an isolated operating-system
|
|
249
|
+
process tree; an entry module runs as a Worker owned by the System. In
|
|
250
|
+
development, the directory containing `phresh.config.ts` becomes the derived
|
|
251
|
+
Server location. A client block requires an HTTP(S) `url`; development clients
|
|
250
252
|
are never resolved from filesystem paths.
|
|
251
253
|
|
|
254
|
+
An `entryFile` is a path inside its Server location. Packaging copies it with
|
|
255
|
+
the rest of that directory, and the System rejects a path that escapes those
|
|
256
|
+
files. A Worker has no independent process working directory and is not a
|
|
257
|
+
security boundary; resolve module-owned resources with `import.meta.url` and
|
|
258
|
+
use the Server SDK for Program storage.
|
|
259
|
+
|
|
252
260
|
The client development shape may also declare `startCommand`. `phresh dev`
|
|
253
261
|
runs it from the project directory as a foreground development tool; the
|
|
254
262
|
command is never derived into the Program sent to the system. The tool and
|
|
@@ -267,9 +275,10 @@ identity, version, and description from `package.json`, ensures the project has
|
|
|
267
275
|
the matching `@phreshos/core` development dependency, and writes the typed
|
|
268
276
|
`phresh.config.ts` authoring description.
|
|
269
277
|
|
|
270
|
-
In a terminal, `init` asks for the production locations and
|
|
271
|
-
`start` and `install`, including whether a package build should
|
|
272
|
-
locations. It then offers the development
|
|
278
|
+
In a terminal, `init` asks for the production locations and Server execution
|
|
279
|
+
modes needed by `start` and `install`, including whether a package build should
|
|
280
|
+
prepare those locations. It then offers the development Server mode, Client
|
|
281
|
+
command, and URL needed by `dev`.
|
|
273
282
|
Existing `build` and `dev` package scripts become editable suggestions, never
|
|
274
283
|
silent assumptions. A single Endpoint defaults to `dist`; when both Endpoints
|
|
275
284
|
exist, their defaults are `dist/server` and `dist/client`. API documentation is
|
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,9 @@ describe(program.command("init")
|
|
|
54
54
|
.option("--server", "include a Server endpoint")
|
|
55
55
|
.option("--server-location <path>", "production Server directory")
|
|
56
56
|
.option("--server-start-command <command>", "production Server command")
|
|
57
|
+
.option("--server-entry-file <path>", "production Server worker entry")
|
|
57
58
|
.option("--server-development-start-command <command>", "development Server command")
|
|
59
|
+
.option("--server-development-entry-file <path>", "development Server worker entry")
|
|
58
60
|
.option("--client", "include a Client endpoint")
|
|
59
61
|
.option("--client-location <path>", "production Client directory")
|
|
60
62
|
.option("--client-development-url <url>", "development Client URL")
|
|
@@ -64,10 +66,12 @@ describe(program.command("init")
|
|
|
64
66
|
await init({
|
|
65
67
|
name: options.name,
|
|
66
68
|
buildCommand: options.buildCommand,
|
|
67
|
-
server: options.server === true || options.serverLocation !== undefined || options.serverStartCommand !== undefined,
|
|
69
|
+
server: options.server === true || options.serverLocation !== undefined || options.serverStartCommand !== undefined || options.serverEntryFile !== undefined,
|
|
68
70
|
serverLocation: options.serverLocation,
|
|
69
71
|
serverStartCommand: options.serverStartCommand,
|
|
72
|
+
serverEntryFile: options.serverEntryFile,
|
|
70
73
|
serverDevelopmentStartCommand: options.serverDevelopmentStartCommand,
|
|
74
|
+
serverDevelopmentEntryFile: options.serverDevelopmentEntryFile,
|
|
71
75
|
client: options.client === true || options.clientLocation !== undefined,
|
|
72
76
|
clientLocation: options.clientLocation,
|
|
73
77
|
clientDevelopmentUrl: options.clientDevelopmentUrl,
|
package/dist/derive.js
CHANGED
|
@@ -14,6 +14,7 @@ export default function derive(config, directory, which) {
|
|
|
14
14
|
" server: {",
|
|
15
15
|
" …",
|
|
16
16
|
" development: { startCommand: \"tsx source/server/main.ts\" }",
|
|
17
|
+
" // or: development: { entryFile: \"source/server/main.js\" }",
|
|
17
18
|
" }",
|
|
18
19
|
"",
|
|
19
20
|
" client: {",
|
|
@@ -45,7 +46,7 @@ export default function derive(config, directory, which) {
|
|
|
45
46
|
start: server.start,
|
|
46
47
|
installCommand: config.server?.installCommand,
|
|
47
48
|
uninstallCommand: config.server?.uninstallCommand,
|
|
48
|
-
|
|
49
|
+
...serverExecution(server)
|
|
49
50
|
} },
|
|
50
51
|
// A URL stands as written; a directory is made absolute. The
|
|
51
52
|
// contract reads both from the one field, and which it is is
|
|
@@ -67,15 +68,23 @@ export default function derive(config, directory, which) {
|
|
|
67
68
|
function serverHalf(half, which) {
|
|
68
69
|
if (!half)
|
|
69
70
|
return null;
|
|
70
|
-
const { development, ...
|
|
71
|
+
const { development, startCommand, entryFile, ...description } = half;
|
|
71
72
|
if (which === "production" || !development)
|
|
72
|
-
return
|
|
73
|
+
return {
|
|
74
|
+
...description,
|
|
75
|
+
...serverExecution({ startCommand, entryFile })
|
|
76
|
+
};
|
|
73
77
|
return {
|
|
74
|
-
...
|
|
78
|
+
...description,
|
|
75
79
|
location: ".",
|
|
76
|
-
|
|
80
|
+
...serverExecution(development)
|
|
77
81
|
};
|
|
78
82
|
}
|
|
83
|
+
function serverExecution(server) {
|
|
84
|
+
return server.startCommand !== undefined
|
|
85
|
+
? { startCommand: server.startCommand }
|
|
86
|
+
: { entryFile: server.entryFile };
|
|
87
|
+
}
|
|
79
88
|
function clientHalf(half, which) {
|
|
80
89
|
if (!half)
|
|
81
90
|
return null;
|
package/dist/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {} from "@phreshos/core";
|
|
2
|
-
import { configFile, readManifest } from "./project.js";
|
|
2
|
+
import { configFile, containedServerEntry, readManifest } from "./project.js";
|
|
3
3
|
import { dim } from "./style.js";
|
|
4
4
|
import ensureProjectDependency, { projectScript } from "./project-dependency.js";
|
|
5
5
|
import prompts from "./prompts.js";
|
|
@@ -59,21 +59,19 @@ export default async function init(options = {}, directory = process.cwd(), core
|
|
|
59
59
|
let server;
|
|
60
60
|
if (serverSelected) {
|
|
61
61
|
const location = options.serverLocation ?? (interactive ? await ask("The system runs the production Server from this project-relative directory.", "Where are the production Server files?", clientSelected ? "dist/server" : "dist") : "");
|
|
62
|
-
const startCommand = options.serverStartCommand ?? (interactive ? await ask("This command runs inside the production Server directory.", "What command starts the production Server?", "node main.js") : "");
|
|
63
62
|
if (!location)
|
|
64
63
|
throw new Error("--server-location is required without a terminal");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
const production = options.serverStartCommand === undefined && options.serverEntryFile === undefined && interactive
|
|
65
|
+
? await askExecution(interaction, "production", "node main.js")
|
|
66
|
+
: execution(options.serverStartCommand, options.serverEntryFile, "production Server");
|
|
67
|
+
let development = explicitExecution(options.serverDevelopmentStartCommand, options.serverDevelopmentEntryFile, "development Server");
|
|
68
68
|
if (interactive && development === undefined) {
|
|
69
69
|
const suggested = manifest.scripts?.dev && projectScript(directory, manifest.packageManager, "dev");
|
|
70
70
|
if (await yes("Development mode can run the Server directly from the project source.", "Run the Server from source during development?", Boolean(suggested && !clientSelected))) {
|
|
71
|
-
development = await
|
|
71
|
+
development = await askExecution(interaction, "development", suggested || undefined);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
throw new Error("A server development command must not be empty");
|
|
76
|
-
server = { location, startCommand, ...development && { development: { startCommand: development } } };
|
|
74
|
+
server = { location, ...production, ...development && { development } };
|
|
77
75
|
}
|
|
78
76
|
let client;
|
|
79
77
|
if (clientSelected) {
|
|
@@ -117,7 +115,7 @@ export default async function init(options = {}, directory = process.cwd(), core
|
|
|
117
115
|
await ensureProjectDependency("@phreshos/core", coreRange, directory);
|
|
118
116
|
writeFileSync(path, compose(config));
|
|
119
117
|
if (config.server)
|
|
120
|
-
interaction.detail("server", config.server.startCommand, `./${config.server.location}`);
|
|
118
|
+
interaction.detail(config.server.startCommand ? "server" : "server worker", config.server.startCommand ?? config.server.entryFile, `./${config.server.location}`);
|
|
121
119
|
if (config.client)
|
|
122
120
|
interaction.detail("client", `./${config.client.location}`);
|
|
123
121
|
const next = [config.server?.development || config.client?.development ? "phresh dev" : null, "phresh start", "phresh install"].filter(Boolean);
|
|
@@ -140,7 +138,9 @@ function compose(config) {
|
|
|
140
138
|
field("buildCommand", config.buildCommand),
|
|
141
139
|
half("server", config.server && {
|
|
142
140
|
location: config.server.location,
|
|
143
|
-
|
|
141
|
+
...(config.server.startCommand !== undefined
|
|
142
|
+
? { startCommand: config.server.startCommand }
|
|
143
|
+
: { entryFile: config.server.entryFile }),
|
|
144
144
|
...config.server.development && { development: config.server.development }
|
|
145
145
|
}),
|
|
146
146
|
half("client", config.client && {
|
|
@@ -180,3 +180,35 @@ function httpUrl(value) {
|
|
|
180
180
|
return false;
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
+
function execution(startCommand, entryFile, owner) {
|
|
184
|
+
const selected = explicitExecution(startCommand, entryFile, owner);
|
|
185
|
+
if (!selected)
|
|
186
|
+
throw new Error(`Choose exactly one --server-start-command or --server-entry-file for the ${owner}`);
|
|
187
|
+
return selected;
|
|
188
|
+
}
|
|
189
|
+
function explicitExecution(startCommand, entryFile, owner) {
|
|
190
|
+
if (startCommand !== undefined && entryFile !== undefined)
|
|
191
|
+
throw new Error(`The ${owner} cannot declare both a start command and an entry file`);
|
|
192
|
+
if (startCommand !== undefined) {
|
|
193
|
+
if (startCommand.trim().length === 0)
|
|
194
|
+
throw new Error(`The ${owner} command must not be empty`);
|
|
195
|
+
return { startCommand };
|
|
196
|
+
}
|
|
197
|
+
if (entryFile !== undefined) {
|
|
198
|
+
if (entryFile.trim().length === 0)
|
|
199
|
+
throw new Error(`The ${owner} entry file must not be empty`);
|
|
200
|
+
if (!containedServerEntry(entryFile))
|
|
201
|
+
throw new Error(`The ${owner} entry file must remain inside its Server directory`);
|
|
202
|
+
return { entryFile };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function askExecution(interaction, mode, suggestedCommand) {
|
|
206
|
+
const worker = await interaction.yes("A Worker uses fewer resources but shares the System's Node.js process.", `Run the ${mode} Server as a System-owned Worker?`, false);
|
|
207
|
+
if (worker)
|
|
208
|
+
return {
|
|
209
|
+
entryFile: await interaction.ask("This JavaScript module remains inside the Server files.", `What is the ${mode} Server entry file?`, mode === "production" ? "main.js" : "source/server/main.js")
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
startCommand: await interaction.ask(`This command runs from the ${mode === "production" ? "production Server directory" : "project directory"}.`, `What command starts the ${mode} Server?`, suggestedCommand)
|
|
213
|
+
};
|
|
214
|
+
}
|
package/dist/launch.js
CHANGED
|
@@ -50,7 +50,7 @@ export default async function launch(which, directory = process.cwd(), options =
|
|
|
50
50
|
const clientStartCommand = clientDevelopmentConfig?.startCommand;
|
|
51
51
|
heading(`${program.name ?? program.identity}${program.version ? ` ${program.version}` : ""}`, which);
|
|
52
52
|
if (program.server)
|
|
53
|
-
line("server", String(program.server.startCommand), place(directory, program.server.location));
|
|
53
|
+
line(program.server.startCommand ? "server" : "server worker", String(program.server.startCommand ?? program.server.entryFile), place(directory, program.server.location));
|
|
54
54
|
if (program.client)
|
|
55
55
|
line("client", clientStartCommand ?? place(directory, program.client.location), clientStartCommand ? place(directory, program.client.location) : undefined);
|
|
56
56
|
line("storage", place(directory, String(program.storage)));
|
package/dist/pack.js
CHANGED
|
@@ -76,10 +76,21 @@ function program(config, version) {
|
|
|
76
76
|
categories: config.categories,
|
|
77
77
|
keywords: config.keywords,
|
|
78
78
|
website: config.website,
|
|
79
|
-
...config.server && { server: {
|
|
79
|
+
...config.server && { server: {
|
|
80
|
+
location: "server",
|
|
81
|
+
start: config.server.start,
|
|
82
|
+
installCommand: config.server.installCommand,
|
|
83
|
+
uninstallCommand: config.server.uninstallCommand,
|
|
84
|
+
...serverExecution(config.server)
|
|
85
|
+
} },
|
|
80
86
|
...config.client && { client: { location: "client", start: config.client.start, title: config.client.title, size: config.client.size, position: config.client.position, layer: config.client.layer, minimize: config.client.minimize } }
|
|
81
87
|
};
|
|
82
88
|
}
|
|
89
|
+
function serverExecution(server) {
|
|
90
|
+
return server.startCommand !== undefined
|
|
91
|
+
? { startCommand: server.startCommand }
|
|
92
|
+
: { entryFile: server.entryFile };
|
|
93
|
+
}
|
|
83
94
|
// What is at a half's location, where the package keeps it. A half that
|
|
84
95
|
// was declared and is not there is the answer to whether it was built.
|
|
85
96
|
function place(zip, directory, location, half) {
|
package/dist/project.js
CHANGED
|
@@ -8,7 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
8
8
|
};
|
|
9
9
|
import { isRelativeValue, layers } from "@phreshos/core";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
|
-
import { resolve } from "node:path";
|
|
11
|
+
import { isAbsolute, normalize, resolve, sep } from "node:path";
|
|
12
12
|
import { pathToFileURL } from "node:url";
|
|
13
13
|
export const configFile = "phresh.config.ts";
|
|
14
14
|
export async function readConfig(directory = process.cwd()) {
|
|
@@ -76,8 +76,8 @@ function coherent(config) {
|
|
|
76
76
|
}
|
|
77
77
|
if (!(config.server && (config.server.start ?? true)) && !(config.client && (config.client.start ?? true)))
|
|
78
78
|
throw new Error("A Program's default Process must start a server endpoint, a client endpoint, or both");
|
|
79
|
-
if (config.server
|
|
80
|
-
|
|
79
|
+
if (config.server)
|
|
80
|
+
execution(config.server, "A server half");
|
|
81
81
|
if (config.server?.installCommand !== undefined && typeof config.server.installCommand !== "string")
|
|
82
82
|
throw new Error("A server half's install command must be text");
|
|
83
83
|
if (config.server?.uninstallCommand !== undefined && typeof config.server.uninstallCommand !== "string")
|
|
@@ -101,8 +101,8 @@ function coherent(config) {
|
|
|
101
101
|
const serverDevelopment = config.server?.development;
|
|
102
102
|
if (serverDevelopment !== undefined && (typeof serverDevelopment !== "object" || serverDevelopment === null || Array.isArray(serverDevelopment)))
|
|
103
103
|
throw new Error("server.development must be a declaration");
|
|
104
|
-
if (serverDevelopment !== undefined
|
|
105
|
-
|
|
104
|
+
if (serverDevelopment !== undefined)
|
|
105
|
+
execution(serverDevelopment, "server.development");
|
|
106
106
|
const clientDevelopment = config.client?.development;
|
|
107
107
|
if (clientDevelopment !== undefined && (typeof clientDevelopment !== "object" || clientDevelopment === null || Array.isArray(clientDevelopment)))
|
|
108
108
|
throw new Error("client.development must be a declaration");
|
|
@@ -120,6 +120,24 @@ function coherent(config) {
|
|
|
120
120
|
throw new Error(`A window's ${what} is a finite pixel number or a relative expression such as "50% + 10"`);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
function execution(value, owner) {
|
|
124
|
+
const command = typeof value.startCommand === "string" && value.startCommand.trim().length > 0;
|
|
125
|
+
const entry = typeof value.entryFile === "string" && value.entryFile.trim().length > 0;
|
|
126
|
+
if (value.startCommand !== undefined && !command)
|
|
127
|
+
throw new Error(`${owner}'s startCommand must be non-empty text`);
|
|
128
|
+
if (value.entryFile !== undefined && !entry)
|
|
129
|
+
throw new Error(`${owner}'s entryFile must be a non-empty path`);
|
|
130
|
+
if (command === entry)
|
|
131
|
+
throw new Error(`${owner} must declare exactly one non-empty startCommand or entryFile`);
|
|
132
|
+
if (entry && !containedServerEntry(value.entryFile))
|
|
133
|
+
throw new Error(`${owner}'s entryFile must remain inside its Server directory`);
|
|
134
|
+
}
|
|
135
|
+
export function containedServerEntry(entry) {
|
|
136
|
+
if (isAbsolute(entry))
|
|
137
|
+
return false;
|
|
138
|
+
const path = normalize(entry);
|
|
139
|
+
return path !== ".." && !path.startsWith(`..${sep}`);
|
|
140
|
+
}
|
|
123
141
|
function httpUrl(value) {
|
|
124
142
|
if (typeof value !== "string" || value.trim().length === 0)
|
|
125
143
|
return false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
|
|
4
|
+
"version": "0.1.29",
|
|
5
5
|
"description": "The Phresh command-line interface for Program projects and system management.",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.10"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"packageManager": "bun@1.3.14",
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@clack/prompts": "^1.7.0",
|
|
52
|
-
|
|
52
|
+
"@phreshos/core": "^0.1.20",
|
|
53
53
|
"adm-zip": "^0.6.0",
|
|
54
54
|
"commander": "^15.0.0",
|
|
55
55
|
"picocolors": "^1.1.1"
|