@phreshos/cli 0.1.63 → 0.1.65

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.
@@ -39,11 +39,11 @@ export default function projectCommands(program, coreRange) {
39
39
  option("--server-command <command>", "production Server host command"),
40
40
  option("--server-worker <path>", "production Server Node Worker entry"),
41
41
  option("--server-sandbox <path>", "production Server Sandbox entry"),
42
- option("--server-development-command <command>", "development Server command"),
42
+ option("--server-dev-command <command>", "development Server command"),
43
43
  option("--client", "include a Client endpoint"),
44
44
  option("--client-location <path>", "production Client directory"),
45
- option("--client-development-url <url>", "fixed or external development Client URL"),
46
- option("--client-development-start-command <command>", "development Client command"),
45
+ option("--client-dev-url <url>", "fixed or external development Client URL"),
46
+ option("--client-dev-command <command>", "development Client command"),
47
47
  option("--force", "replace an existing phresh.config.ts")
48
48
  ],
49
49
  guidance: [
@@ -61,14 +61,14 @@ export default function projectCommands(program, coreRange) {
61
61
  serverCommand: options.serverCommand,
62
62
  serverWorker: options.serverWorker,
63
63
  serverSandbox: options.serverSandbox,
64
- serverDevelopmentCommand: options.serverDevelopmentCommand,
64
+ serverDevCommand: options.serverDevCommand,
65
65
  client: options.client === true
66
66
  || options.clientLocation !== undefined
67
- || options.clientDevelopmentUrl !== undefined
68
- || options.clientDevelopmentStartCommand !== undefined,
67
+ || options.clientDevUrl !== undefined
68
+ || options.clientDevCommand !== undefined,
69
69
  clientLocation: options.clientLocation,
70
- clientDevelopmentUrl: options.clientDevelopmentUrl,
71
- clientDevelopmentStartCommand: options.clientDevelopmentStartCommand,
70
+ clientDevUrl: options.clientDevUrl,
71
+ clientDevCommand: options.clientDevCommand,
72
72
  force: options.force === true
73
73
  }, process.cwd(), coreRange);
74
74
  });
package/dist/init.js CHANGED
@@ -64,49 +64,45 @@ export default async function init(options = {}, directory = process.cwd(), core
64
64
  const production = options.serverCommand === undefined && options.serverWorker === undefined && options.serverSandbox === undefined && interactive
65
65
  ? await askExecution(interaction, "production", "node main.js")
66
66
  : execution(options.serverCommand, options.serverWorker, options.serverSandbox, "production Server");
67
- let development = developmentExecution(options.serverDevelopmentCommand, "development Server");
68
- if (interactive && development === undefined) {
67
+ let devCommand = options.serverDevCommand;
68
+ if (interactive && devCommand === 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 = {
72
- command: await ask("This command runs from the project directory with access to development tooling.", "What command starts the development Server?", suggested || undefined)
73
- };
71
+ devCommand = await ask("This command runs from the project directory with access to development tooling.", "What command starts the development Server?", suggested || undefined);
74
72
  }
75
73
  }
76
- server = { location, ...production, ...development && { development } };
74
+ if (devCommand !== undefined && !devCommand.trim())
75
+ throw new Error("The development Server command must not be empty");
76
+ server = { location, ...production, ...devCommand !== undefined && { devCommand } };
77
77
  }
78
78
  let client;
79
79
  if (clientSelected) {
80
80
  const location = options.clientLocation ?? (interactive ? await ask("The system serves the production Client from this project-relative directory.", "Where are the production Client files?", serverSelected ? "dist/client" : "dist") : "");
81
81
  if (!location)
82
82
  throw new Error("--client-location is required without a terminal");
83
- let developmentUrl = options.clientDevelopmentUrl;
84
- let developmentStartCommand = options.clientDevelopmentStartCommand;
85
- if (interactive && developmentUrl === undefined && developmentStartCommand === undefined) {
83
+ let devUrl = options.clientDevUrl;
84
+ let devCommand = options.clientDevCommand;
85
+ if (interactive && devUrl === undefined && devCommand === undefined) {
86
86
  const suggested = manifest.scripts?.dev && projectScript(directory, manifest.packageManager, "dev");
87
87
  if (await yes("A development server can provide live updates instead of built Client files.", "Use a Client development server?", Boolean(suggested))) {
88
88
  if (await yes("The Project can own the development server and stop it when the session ends.", "Should phresh dev start the Client server?", Boolean(suggested))) {
89
- developmentStartCommand = await ask("This command remains attached to the phresh dev session.", "What command starts the Client development server?", suggested || undefined);
89
+ devCommand = await ask("This command remains attached to the phresh dev session.", "What command starts the Client development server?", suggested || undefined);
90
90
  }
91
91
  else
92
- developmentUrl = await ask("The System routes the Program asset path to this HTTP or HTTPS address during phresh dev.", "What URL serves the external development Client?", "http://localhost:5173/");
92
+ devUrl = await ask("The System routes the Program asset path to this HTTP or HTTPS address during phresh dev.", "What URL serves the external development Client?", "http://localhost:5173/");
93
93
  }
94
94
  }
95
- if (developmentUrl !== undefined && developmentUrl.trim().length === 0)
95
+ if (devUrl !== undefined && devUrl.trim().length === 0)
96
96
  throw new Error("A client development URL must not be empty");
97
- if (developmentUrl !== undefined && !httpUrl(developmentUrl))
97
+ if (devUrl !== undefined && !httpUrl(devUrl))
98
98
  throw new Error("A client development URL must use HTTP or HTTPS");
99
- if (developmentStartCommand !== undefined && developmentStartCommand.trim().length === 0)
99
+ if (devCommand !== undefined && devCommand.trim().length === 0)
100
100
  throw new Error("A client development command must not be empty");
101
- let development;
102
- if (developmentStartCommand !== undefined)
103
- development = {
104
- startCommand: developmentStartCommand,
105
- ...developmentUrl !== undefined && { url: developmentUrl }
106
- };
107
- else if (developmentUrl !== undefined)
108
- development = { url: developmentUrl };
109
- client = { location, ...development && { development } };
101
+ client = {
102
+ location,
103
+ ...devCommand !== undefined && { devCommand },
104
+ ...devUrl !== undefined && { devUrl }
105
+ };
110
106
  }
111
107
  const described = {
112
108
  identity: manifest.name,
@@ -124,7 +120,7 @@ export default async function init(options = {}, directory = process.cwd(), core
124
120
  interaction.detail(`server ${serverExecution(config.server)[0]}`, serverExecution(config.server)[1], `./${config.server.location}`);
125
121
  if (config.client)
126
122
  interaction.detail("client", `./${config.client.location}`);
127
- const next = [config.server?.development || config.client?.development ? "phresh dev" : null, "phresh start", "phresh install"].filter(Boolean);
123
+ const next = [config.server?.devCommand || config.client?.devCommand || config.client?.devUrl ? "phresh dev" : null, "phresh start", "phresh install"].filter(Boolean);
128
124
  interaction.message();
129
125
  interaction.message(`${dim("Next:")} ${next.join(` ${dim("or")} `)}`);
130
126
  if (config.client) {
@@ -143,11 +139,12 @@ function compose(config) {
143
139
  half("server", config.server && {
144
140
  location: config.server.location,
145
141
  [serverExecution(config.server)[0]]: serverExecution(config.server)[1],
146
- ...config.server.development && { development: config.server.development }
142
+ devCommand: config.server.devCommand
147
143
  }),
148
144
  half("client", config.client && {
149
145
  location: config.client.location,
150
- ...config.client.development && { development: config.client.development }
146
+ devCommand: config.client.devCommand,
147
+ devUrl: config.client.devUrl
151
148
  })
152
149
  ];
153
150
  return [
@@ -166,11 +163,8 @@ function half(name, values) {
166
163
  if (!values)
167
164
  return "";
168
165
  const inside = Object.entries(values).map(function ([key, value]) {
169
- if (typeof value === "string")
170
- return ` ${key}: ${JSON.stringify(value)}`;
171
- const nested = Object.entries(value).map(([nestedKey, nestedValue]) => ` ${nestedKey}: ${JSON.stringify(nestedValue)}`);
172
- return ` ${key}: {\n${nested.join(",\n")}\n }`;
173
- });
166
+ return value === undefined ? null : ` ${key}: ${JSON.stringify(value)}`;
167
+ }).filter(Boolean);
174
168
  return ` ${name}: {\n${inside.join(",\n")}\n }`;
175
169
  }
176
170
  function httpUrl(value) {
@@ -204,13 +198,6 @@ function execution(command, worker, sandbox, owner) {
204
198
  throw new Error(`The ${owner} sandbox entry must remain inside its Server directory`);
205
199
  return { sandbox };
206
200
  }
207
- function developmentExecution(command, owner) {
208
- if (command === undefined)
209
- return undefined;
210
- if (!command.trim())
211
- throw new Error(`The ${owner} command must not be empty`);
212
- return { command };
213
- }
214
201
  async function askExecution(interaction, mode, suggestedCommand) {
215
202
  const sandbox = await interaction.yes("A Sandbox provides only JavaScript and the permission-constrained System API.", `Run the ${mode} Server in a Sandbox?`, false);
216
203
  if (sandbox)
package/dist/launch.js CHANGED
@@ -17,7 +17,7 @@ export default async function launch(mode, directory = process.cwd(), options =
17
17
  line(`server ${execution}`, value, place(project.directory, definition.server.location));
18
18
  }
19
19
  if (definition.client)
20
- line("client", project.config.client?.development?.startCommand ?? place(project.directory, definition.client.location));
20
+ line("client", project.config.client?.devCommand ?? place(project.directory, definition.client.location));
21
21
  line("storage", place(project.directory, String(definition.storage)));
22
22
  if (Object.keys(options).length)
23
23
  line("options", Object.entries(options).map(([name, value]) => `${name}=${value}`).join(" "));
@@ -30,3 +30,12 @@ export async function waitForGateway(path, running, timeout = 15_000) {
30
30
  }
31
31
  throw new Error(`The PhreshOS System did not become ready within ${Math.ceil(timeout / 1000)} seconds`);
32
32
  }
33
+ export async function waitForGatewayClose(path, timeout = 15_000) {
34
+ const until = Date.now() + timeout;
35
+ while (Date.now() < until) {
36
+ if (!await gatewayReady(path))
37
+ return;
38
+ await new Promise(resolve => setTimeout(resolve, 100));
39
+ }
40
+ throw new Error(`The PhreshOS System gateway did not close within ${Math.ceil(timeout / 1000)} seconds`);
41
+ }
@@ -1,6 +1,6 @@
1
1
  import SystemInstallation from "./installation.js";
2
2
  import { downloadSystemRelease, resolveSystemRelease } from "./release.js";
3
- import { gatewayReady, waitForGateway } from "./gateway-readiness.js";
3
+ import { gatewayReady, waitForGateway, waitForGatewayClose } from "./gateway-readiness.js";
4
4
  import systemPaths from "./paths.js";
5
5
  import systemService from "./service/index.js";
6
6
  import nodeExecutable from "./node.js";
@@ -21,6 +21,7 @@ export default class SystemLifecycle {
21
21
  downloadRelease: dependencies?.downloadRelease ?? downloadSystemRelease,
22
22
  ready: dependencies?.ready ?? gatewayReady,
23
23
  wait: dependencies?.wait ?? waitForGateway,
24
+ waitForStop: dependencies?.waitForStop ?? waitForGatewayClose,
24
25
  provisionSetup: dependencies?.provisionSetup ?? provisionSetup
25
26
  };
26
27
  }
@@ -45,7 +46,7 @@ export default class SystemLifecycle {
45
46
  await activation.commit();
46
47
  }
47
48
  catch (error) {
48
- await service.stop().catch(() => undefined);
49
+ await this.stopService().catch(() => undefined);
49
50
  await activation.rollback();
50
51
  return await this.restoredFailure(error, previous, previousService);
51
52
  }
@@ -69,7 +70,7 @@ export default class SystemLifecycle {
69
70
  const { installation, service } = this.dependencies;
70
71
  const state = await service.inspect();
71
72
  if (state.running)
72
- await service.stop();
73
+ await this.stopService();
73
74
  if (state.enabled)
74
75
  await service.disable();
75
76
  if (state.registered)
@@ -92,7 +93,7 @@ export default class SystemLifecycle {
92
93
  }
93
94
  async stopExclusive() {
94
95
  await this.requireInstalledService();
95
- await this.dependencies.service.stop();
96
+ await this.stopService();
96
97
  return await this.status();
97
98
  }
98
99
  async enable() {
@@ -148,10 +149,10 @@ export default class SystemLifecycle {
148
149
  }
149
150
  }
150
151
  async activate(prepared, previous, state, purge) {
151
- const { installation, service } = this.dependencies;
152
+ const { installation } = this.dependencies;
152
153
  try {
153
154
  if (state.running)
154
- await service.stop();
155
+ await this.stopService();
155
156
  if (purge)
156
157
  await installation.purgeStorage();
157
158
  return await installation.activate(prepared, previous);
@@ -205,6 +206,11 @@ export default class SystemLifecycle {
205
206
  throw error;
206
207
  }
207
208
  }
209
+ async stopService() {
210
+ const { installation, service } = this.dependencies;
211
+ await service.stop();
212
+ await this.dependencies.waitForStop(installation.paths.gateway);
213
+ }
208
214
  }
209
215
  async function provisionSetup() {
210
216
  await installProgram({ name: "setup", announce: false });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phresh",
3
3
  "private": true,
4
- "version": "0.1.39",
4
+ "version": "0.1.40",
5
5
  "description": "The official PhreshOS starter Program.",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -15,14 +15,14 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@phreshos/client": "^0.1.46",
18
- "@phreshos/core": "^0.1.53",
18
+ "@phreshos/core": "^0.1.54",
19
19
  "@phreshos/react": "^0.1.28",
20
20
  "@phreshos/server": "^0.1.50",
21
21
  "react": "^19.2.8",
22
22
  "react-dom": "^19.2.8"
23
23
  },
24
24
  "devDependencies": {
25
- "@phreshos/cli": "^0.1.63",
25
+ "@phreshos/cli": "^0.1.65",
26
26
  "@types/node": "^26.4.0",
27
27
  "@types/react": "^19.2.18",
28
28
  "@types/react-dom": "^19.2.5",
@@ -4,7 +4,7 @@ export default defineConfig({
4
4
  identity: "phresh",
5
5
  name: "Phresh Program",
6
6
  description: "A minimal counter with direct Client and Server endpoints.",
7
- version: "0.1.39",
7
+ version: "0.1.40",
8
8
  icon: "icon.png",
9
9
  categories: ["Development"],
10
10
  keywords: ["example", "counter", "client", "server"],
@@ -13,16 +13,12 @@ export default defineConfig({
13
13
  server: {
14
14
  location: "dist/server",
15
15
  worker: "main.js",
16
- development: {
17
- command: "vite-node server/main.ts"
18
- }
16
+ devCommand: "vite-node server/main.ts"
19
17
  },
20
18
  client: {
21
19
  location: "dist/client",
22
20
  title: "Phresh Program",
23
21
  size: { width: 600, height: 500 },
24
- development: {
25
- startCommand: "vite --config vite.client.ts"
26
- }
22
+ devCommand: "vite --config vite.client.ts"
27
23
  }
28
24
  })
@@ -10,7 +10,7 @@ test("build contract", async () => {
10
10
  assert.equal(config.version, manifest.version)
11
11
  assert.equal(config.server?.location, "dist/server")
12
12
  assert.equal(config.server?.worker, "main.js")
13
- assert.equal(config.server?.development?.command, "vite-node server/main.ts")
13
+ assert.equal(config.server?.devCommand, "vite-node server/main.ts")
14
14
  assert.equal(config.client?.location, "dist/client")
15
15
  assert.deepEqual(config.client?.size, { width: 600, height: 500 })
16
16
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "PhreshOS/phresh-program",
3
- "version": "0.1.39",
4
- "sha256": "faf95c8b089928f9dca8ef0a0ff8063a54d0f90b602b4f237d009860ea884c23",
3
+ "version": "0.1.40",
4
+ "sha256": "7f2bb99438c7224e543147519a75d76a1db04313ff6ec6b024bb7532feb6a017",
5
5
  "development": true
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phreshos/cli",
3
3
  "type": "module",
4
- "version": "0.1.63",
4
+ "version": "0.1.65",
5
5
  "description": "The Phresh command-line interface for Program projects and system management.",
6
6
  "engines": {
7
7
  "node": ">=20.10"
@@ -47,8 +47,8 @@
47
47
  "packageManager": "bun@1.3.14",
48
48
  "dependencies": {
49
49
  "@clack/prompts": "^1.7.0",
50
- "@phreshos/core": "^0.1.53",
51
- "@phreshos/node": "^0.1.26",
50
+ "@phreshos/core": "^0.1.54",
51
+ "@phreshos/node": "^0.1.27",
52
52
  "adm-zip": "^0.6.0",
53
53
  "commander": "^15.0.0",
54
54
  "picocolors": "^1.1.1"