@phreshos/cli 0.1.4 → 0.1.6
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 +63 -26
- package/dist/build-template.js +73 -42
- package/dist/cli.js +135 -241
- package/dist/create.js +73 -80
- package/dist/derive.js +4 -4
- package/dist/init.js +113 -122
- package/dist/pack.js +9 -8
- package/dist/program-intake.js +10 -2
- package/dist/project-dependency.js +13 -44
- package/dist/project.js +2 -3
- package/dist/prompts.js +105 -0
- package/dist/style.js +9 -14
- package/dist/system/command.js +70 -0
- package/dist/system/installation.js +241 -0
- package/dist/system/lifecycle.js +174 -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 +94 -0
- package/dist/system/service/macos.js +123 -0
- package/dist/system/types.js +0 -0
- package/dist/template/README.md +1 -1
- package/dist/template/icon.png +0 -0
- package/dist/template/package.json +8 -7
- package/dist/template/phresh.config.ts +65 -25
- package/dist/template/source/build.ts +3 -3
- package/dist/template/source/client/app.tsx +3 -8
- package/dist/template/source/server/main.ts +1 -1
- 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 +36 -4
- package/dist/questions.js +0 -25
- package/dist/relative-value.js +0 -118
- package/dist/template/icons/128x128.png +0 -0
- package/dist/template/vite.config.ts +0 -38
package/dist/init.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {} from "@phreshos/core";
|
|
2
2
|
import { configFile, readManifest } from "./project.js";
|
|
3
|
-
import { dim
|
|
3
|
+
import { dim } from "./style.js";
|
|
4
4
|
import ensureProjectDependency, { projectScript } from "./project-dependency.js";
|
|
5
|
-
import
|
|
5
|
+
import prompts from "./prompts.js";
|
|
6
6
|
import { existsSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
/**
|
|
@@ -16,139 +16,130 @@ import { resolve } from "node:path";
|
|
|
16
16
|
export default async function init(options = {}, directory = process.cwd(), coreRange = "^0.1.0") {
|
|
17
17
|
const manifest = await readManifest(directory);
|
|
18
18
|
const path = resolve(directory, configFile);
|
|
19
|
-
const
|
|
20
|
-
const { interactive, ask, yes } =
|
|
19
|
+
const interaction = prompts();
|
|
20
|
+
const { interactive, ask, yes } = interaction;
|
|
21
21
|
const explicitShape = options.server === true || options.client === true;
|
|
22
22
|
if (existsSync(path) && options.force !== true && !interactive)
|
|
23
23
|
throw new Error(`${configFile} already exists — use --force to replace it`);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
heading("Initialize Program", manifest.name);
|
|
33
|
-
line("identity", manifest.name, "package.json");
|
|
34
|
-
if (manifest.version)
|
|
35
|
-
line("version", manifest.version, "package.json");
|
|
36
|
-
if (manifest.description)
|
|
37
|
-
line("description", manifest.description, "package.json");
|
|
38
|
-
console.log("");
|
|
39
|
-
let serverSelected = options.server === true;
|
|
40
|
-
let clientSelected = options.client === true;
|
|
41
|
-
if (!explicitShape) {
|
|
42
|
-
if (!interactive)
|
|
43
|
-
throw new Error("Choose at least one half with --server, --client, or both");
|
|
44
|
-
serverSelected = await yes("A Server runs on the host machine and can keep working without an open desktop.", "Does this Program have a Server?", true);
|
|
45
|
-
clientSelected = !serverSelected || await yes("A Client runs through a desktop and provides the Program's visual interface.", "Does this Program have a Client?", true);
|
|
24
|
+
interaction.begin("Initialize Program", manifest.name);
|
|
25
|
+
if (existsSync(path) && options.force !== true) {
|
|
26
|
+
interaction.detail(configFile, "already initialized");
|
|
27
|
+
if (!await yes("The existing configuration must be replaced before this project can be initialized again.", "Replace it?", false)) {
|
|
28
|
+
interaction.finish("No changes made");
|
|
29
|
+
return;
|
|
46
30
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
31
|
+
}
|
|
32
|
+
interaction.detail("identity", manifest.name, "package.json");
|
|
33
|
+
if (manifest.version)
|
|
34
|
+
interaction.detail("version", manifest.version, "package.json");
|
|
35
|
+
if (manifest.description)
|
|
36
|
+
interaction.detail("description", manifest.description, "package.json");
|
|
37
|
+
let serverSelected = options.server === true;
|
|
38
|
+
let clientSelected = options.client === true;
|
|
39
|
+
if (!explicitShape) {
|
|
40
|
+
if (!interactive)
|
|
41
|
+
throw new Error("Choose at least one half with --server, --client, or both");
|
|
42
|
+
serverSelected = await yes("A Server runs on the host machine and can keep working without an open desktop.", "Does this Program have a Server?", true);
|
|
43
|
+
clientSelected = !serverSelected || await yes("A Client runs through a desktop and provides the Program's visual interface.", "Does this Program have a Client?", true);
|
|
44
|
+
}
|
|
45
|
+
if (!serverSelected && !clientSelected)
|
|
46
|
+
throw new Error("A Program must have a server half, a client half, or both");
|
|
47
|
+
const name = options.name ?? (interactive ? await ask("This name is shown to people; the package name remains the Program identity.", "What name should people see?", manifest.name) : undefined);
|
|
48
|
+
if (name !== undefined && name.trim().length === 0)
|
|
49
|
+
throw new Error("--name must not be empty");
|
|
50
|
+
let apiDocs = options.apiDocs;
|
|
51
|
+
if (interactive && apiDocs === undefined) {
|
|
52
|
+
const suggested = ["api-docs.md", "README.md"].find(file => existsSync(resolve(directory, file)));
|
|
53
|
+
if (await yes("API documentation explains only the services this Program itself provides.", "Does this Program provide API documentation?", false)) {
|
|
54
|
+
apiDocs = await ask("The path is resolved from the project root and becomes the official API entry point.", "Where is the API documentation file?", suggested);
|
|
67
55
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
56
|
+
}
|
|
57
|
+
if (apiDocs !== undefined && apiDocs.trim().length === 0)
|
|
58
|
+
throw new Error("An API documentation path must not be empty");
|
|
59
|
+
let buildCommand = options.buildCommand;
|
|
60
|
+
if (interactive && buildCommand === undefined) {
|
|
61
|
+
const suggested = manifest.scripts?.build && projectScript(directory, manifest.packageManager, "build");
|
|
62
|
+
const builds = await yes("Production operations need the Program's built files.", "Build before start, install, and pack?", Boolean(suggested));
|
|
63
|
+
if (builds)
|
|
64
|
+
buildCommand = await ask("This command produces the production Server and Client files.", "What command builds the Program?", suggested || undefined);
|
|
65
|
+
}
|
|
66
|
+
if (buildCommand !== undefined && buildCommand.trim().length === 0)
|
|
67
|
+
throw new Error("A build command must not be empty");
|
|
68
|
+
let server;
|
|
69
|
+
if (serverSelected) {
|
|
70
|
+
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") : "");
|
|
71
|
+
const startCommand = options.serverStartCommand ?? (interactive ? await ask("This command runs inside the production Server directory.", "What command starts the production Server?", "node main.js") : "");
|
|
72
|
+
if (!location)
|
|
73
|
+
throw new Error("--server-location is required without a terminal");
|
|
74
|
+
if (!startCommand)
|
|
75
|
+
throw new Error("--server-start-command is required without a terminal");
|
|
76
|
+
let development = options.serverDevelopmentStartCommand;
|
|
77
|
+
if (interactive && development === undefined) {
|
|
78
|
+
const suggested = manifest.scripts?.dev && projectScript(directory, manifest.packageManager, "dev");
|
|
79
|
+
if (await yes("Development mode can run the Server directly from the project source.", "Run the Server from source during development?", Boolean(suggested && !clientSelected))) {
|
|
80
|
+
development = await ask("This command remains attached to the phresh dev session.", "What command starts the development Server?", suggested || undefined);
|
|
84
81
|
}
|
|
85
|
-
if (development !== undefined && development.trim().length === 0)
|
|
86
|
-
throw new Error("A server development command must not be empty");
|
|
87
|
-
server = { location, startCommand, ...development && { development: { startCommand: development } } };
|
|
88
82
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
83
|
+
if (development !== undefined && development.trim().length === 0)
|
|
84
|
+
throw new Error("A server development command must not be empty");
|
|
85
|
+
server = { location, startCommand, ...development && { development: { startCommand: development } } };
|
|
86
|
+
}
|
|
87
|
+
let client;
|
|
88
|
+
if (clientSelected) {
|
|
89
|
+
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") : "");
|
|
90
|
+
if (!location)
|
|
91
|
+
throw new Error("--client-location is required without a terminal");
|
|
92
|
+
let developmentUrl = options.clientDevelopmentUrl;
|
|
93
|
+
let developmentStartCommand = options.clientDevelopmentStartCommand;
|
|
94
|
+
if (interactive && developmentUrl === undefined && developmentStartCommand === undefined) {
|
|
95
|
+
const suggested = manifest.scripts?.dev && projectScript(directory, manifest.packageManager, "dev");
|
|
96
|
+
if (await yes("A development server can provide live updates instead of built Client files.", "Use a Client development server?", Boolean(suggested))) {
|
|
97
|
+
developmentUrl = await ask("The desktop opens this exact HTTP or HTTPS address during phresh dev.", "What URL serves the development Client?", "http://localhost:5173/");
|
|
98
|
+
if (await yes("The CLI can own the development server and stop it when the session ends.", "Should phresh dev start the Client server?", Boolean(suggested))) {
|
|
99
|
+
developmentStartCommand = await ask("This command remains attached to the phresh dev session.", "What command starts the Client development server?", suggested || undefined);
|
|
103
100
|
}
|
|
104
101
|
}
|
|
105
|
-
if (developmentStartCommand !== undefined && developmentUrl === undefined)
|
|
106
|
-
throw new Error("--client-development-url is required with --client-development-start-command");
|
|
107
|
-
if (developmentUrl !== undefined && developmentUrl.trim().length === 0)
|
|
108
|
-
throw new Error("A client development URL must not be empty");
|
|
109
|
-
if (developmentUrl !== undefined && !httpUrl(developmentUrl))
|
|
110
|
-
throw new Error("A client development URL must use HTTP or HTTPS");
|
|
111
|
-
if (developmentStartCommand !== undefined && developmentStartCommand.trim().length === 0)
|
|
112
|
-
throw new Error("A client development command must not be empty");
|
|
113
|
-
client = {
|
|
114
|
-
location,
|
|
115
|
-
...developmentUrl && { development: { url: developmentUrl, ...developmentStartCommand && { startCommand: developmentStartCommand } } }
|
|
116
|
-
};
|
|
117
102
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
103
|
+
if (developmentStartCommand !== undefined && developmentUrl === undefined)
|
|
104
|
+
throw new Error("--client-development-url is required with --client-development-start-command");
|
|
105
|
+
if (developmentUrl !== undefined && developmentUrl.trim().length === 0)
|
|
106
|
+
throw new Error("A client development URL must not be empty");
|
|
107
|
+
if (developmentUrl !== undefined && !httpUrl(developmentUrl))
|
|
108
|
+
throw new Error("A client development URL must use HTTP or HTTPS");
|
|
109
|
+
if (developmentStartCommand !== undefined && developmentStartCommand.trim().length === 0)
|
|
110
|
+
throw new Error("A client development command must not be empty");
|
|
111
|
+
client = {
|
|
112
|
+
location,
|
|
113
|
+
...developmentUrl && { development: { url: developmentUrl, ...developmentStartCommand && { startCommand: developmentStartCommand } } }
|
|
125
114
|
};
|
|
126
|
-
const config = server
|
|
127
|
-
? { ...described, server, ...client && { client } }
|
|
128
|
-
: { ...described, client: client };
|
|
129
|
-
await ensureProjectDependency("@phreshos/core", coreRange, directory);
|
|
130
|
-
writeFileSync(path, compose(config));
|
|
131
|
-
heading(configFile, "created");
|
|
132
|
-
if (config.server)
|
|
133
|
-
line("server", config.server.startCommand, `./${config.server.location}`);
|
|
134
|
-
if (config.client)
|
|
135
|
-
line("client", `./${config.client.location}`);
|
|
136
|
-
console.log("");
|
|
137
|
-
const next = [config.server?.development || config.client?.development ? "phresh dev" : null, "phresh start", "phresh install"].filter(Boolean);
|
|
138
|
-
console.log(` ${dim("Next:")} ${next.join(` ${dim("or")} `)}`);
|
|
139
|
-
if (config.client) {
|
|
140
|
-
heading("Client requirements");
|
|
141
|
-
line("base URL", "./", "required for production assets");
|
|
142
|
-
if (config.client.development)
|
|
143
|
-
line("CORS", "enabled", "required on the development server");
|
|
144
|
-
console.log("");
|
|
145
|
-
}
|
|
146
|
-
else
|
|
147
|
-
console.log("");
|
|
148
115
|
}
|
|
149
|
-
|
|
150
|
-
|
|
116
|
+
const described = {
|
|
117
|
+
identity: manifest.name,
|
|
118
|
+
name,
|
|
119
|
+
version: manifest.version,
|
|
120
|
+
description: manifest.description,
|
|
121
|
+
apiDocs,
|
|
122
|
+
buildCommand
|
|
123
|
+
};
|
|
124
|
+
const config = server
|
|
125
|
+
? { ...described, server, ...client && { client } }
|
|
126
|
+
: { ...described, client: client };
|
|
127
|
+
await ensureProjectDependency("@phreshos/core", coreRange, directory);
|
|
128
|
+
writeFileSync(path, compose(config));
|
|
129
|
+
if (config.server)
|
|
130
|
+
interaction.detail("server", config.server.startCommand, `./${config.server.location}`);
|
|
131
|
+
if (config.client)
|
|
132
|
+
interaction.detail("client", `./${config.client.location}`);
|
|
133
|
+
const next = [config.server?.development || config.client?.development ? "phresh dev" : null, "phresh start", "phresh install"].filter(Boolean);
|
|
134
|
+
interaction.message();
|
|
135
|
+
interaction.message(`${dim("Next:")} ${next.join(` ${dim("or")} `)}`);
|
|
136
|
+
if (config.client) {
|
|
137
|
+
interaction.message();
|
|
138
|
+
interaction.detail("base URL", "./", "required for production assets");
|
|
139
|
+
if (config.client.development)
|
|
140
|
+
interaction.detail("CORS", "enabled", "required on the development server");
|
|
151
141
|
}
|
|
142
|
+
interaction.finish(`${configFile} created`);
|
|
152
143
|
}
|
|
153
144
|
function compose(config) {
|
|
154
145
|
const blocks = [
|
package/dist/pack.js
CHANGED
|
@@ -17,7 +17,7 @@ import build from "./build-command.js";
|
|
|
17
17
|
* a declared half always has a location, even when the system chose it.
|
|
18
18
|
*
|
|
19
19
|
* There is **no wrapping directory**. `program.json`, `server/`,
|
|
20
|
-
* `client/` and `
|
|
20
|
+
* `client/` and optional `icon.png` sit at the package's root, and the directory
|
|
21
21
|
* the system installs into is named from the program's own `identity`.
|
|
22
22
|
* The archive itself therefore needs no second naming layer.
|
|
23
23
|
*
|
|
@@ -41,11 +41,8 @@ export default async function pack(directory = process.cwd()) {
|
|
|
41
41
|
if (!zip.getEntry("client/index.html"))
|
|
42
42
|
throw new Error(`The client files have no index.html — ${config.client.location} is not where a client half is`);
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// half their place is not the program's to choose.
|
|
47
|
-
if (config.icons)
|
|
48
|
-
place(zip, directory, config.icons, "icons");
|
|
44
|
+
if (config.icon)
|
|
45
|
+
file(zip, directory, config.icon, "icon.png", "Program icon");
|
|
49
46
|
if (config.apiDocs)
|
|
50
47
|
document(zip, directory, config.apiDocs);
|
|
51
48
|
zip.addFile("program.json", Buffer.from(JSON.stringify(program(config, version), null, 4) + "\n"));
|
|
@@ -64,6 +61,7 @@ function program(config, version) {
|
|
|
64
61
|
version,
|
|
65
62
|
description: config.description,
|
|
66
63
|
apiDocs: config.apiDocs ? "api-docs.md" : undefined,
|
|
64
|
+
icon: config.icon ? "icon.png" : undefined,
|
|
67
65
|
...config.server && { server: { location: "server", start: config.server.start, installCommand: config.server.installCommand, startCommand: config.server.startCommand } },
|
|
68
66
|
...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 } }
|
|
69
67
|
};
|
|
@@ -77,8 +75,11 @@ function place(zip, directory, location, half) {
|
|
|
77
75
|
zip.addLocalFolder(from, half);
|
|
78
76
|
}
|
|
79
77
|
function document(zip, directory, location) {
|
|
78
|
+
file(zip, directory, location, "api-docs.md", "API documentation file");
|
|
79
|
+
}
|
|
80
|
+
function file(zip, directory, location, target, label) {
|
|
80
81
|
const from = resolve(directory, location);
|
|
81
82
|
if (!existsSync(from) || !statSync(from).isFile())
|
|
82
|
-
throw new Error(`The
|
|
83
|
-
zip.addFile(
|
|
83
|
+
throw new Error(`The ${label} is not at ${location}`);
|
|
84
|
+
zip.addFile(target, readFileSync(from));
|
|
84
85
|
}
|
package/dist/program-intake.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { connect } from "node:net";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
4
|
/**
|
|
5
5
|
* The local Program intake, from the CLI's side.
|
|
6
6
|
*
|
|
@@ -19,7 +19,15 @@ import { join } from "node:path";
|
|
|
19
19
|
* the Program says, then how it ended. None pretends to be a remote method
|
|
20
20
|
* returning through an unrelated transport.
|
|
21
21
|
*/
|
|
22
|
-
export
|
|
22
|
+
export function programIntakePath(environment = process.env, userHome = homedir()) {
|
|
23
|
+
const instanceHome = environment.PHRESHOS_HOME;
|
|
24
|
+
if (instanceHome === undefined)
|
|
25
|
+
return join(userHome, ".phreshos", "intake.sock");
|
|
26
|
+
if (!isAbsolute(instanceHome))
|
|
27
|
+
throw new Error("PHRESHOS_HOME must be an absolute filesystem path");
|
|
28
|
+
return join(instanceHome, "intake.sock");
|
|
29
|
+
}
|
|
30
|
+
export const socketPath = programIntakePath();
|
|
23
31
|
export default function speak(question, heard, path = socketPath, signal) {
|
|
24
32
|
return new Promise(function (settle, refuse) {
|
|
25
33
|
const socket = connect(path);
|
|
@@ -1,46 +1,21 @@
|
|
|
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 CLI running from this repository uses its sibling dev-kit. A distributed
|
|
13
|
-
* CLI has no such sibling and keeps the published range embedded in its
|
|
14
|
-
* generated template.
|
|
15
|
-
*/
|
|
16
|
-
export function projectDependency(name, range, directory) {
|
|
17
|
-
const workspace = manifestAt(resolve(import.meta.dirname, "..", "..", ".."));
|
|
18
|
-
const sourceDirectory = resolve(import.meta.dirname, "..", "..", localDirectories[name]);
|
|
19
|
-
const manifest = manifestAt(sourceDirectory);
|
|
20
|
-
if (workspace?.name === "@phreshos/workspace" && manifest?.name === name && manifest.version && accepts(range, manifest.version)) {
|
|
21
|
-
const local = relative(directory, sourceDirectory).replaceAll("\\", "/") || ".";
|
|
22
|
-
return {
|
|
23
|
-
installSpecifier: sourceDirectory,
|
|
24
|
-
manifestSpecifier: `file:${local}`,
|
|
25
|
-
local: true
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
installSpecifier: `${name}@${range}`,
|
|
30
|
-
manifestSpecifier: range,
|
|
31
|
-
local: false
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
9
|
/** Detects the package manager declared by, present in, or invoking a project. */
|
|
35
10
|
export function projectPackageManager(directory, declared, preferred) {
|
|
36
11
|
const named = preferred ?? packageManagerName(declared) ?? packageManagerFromLocks(directory) ?? packageManagerFromInvocation();
|
|
37
12
|
return managers[named ?? "npm"];
|
|
38
13
|
}
|
|
39
14
|
/** Installs the dependencies already declared by a generated project. */
|
|
40
|
-
export async function installProjectDependencies(directory, preferred) {
|
|
15
|
+
export async function installProjectDependencies(directory, preferred, output = "inherit") {
|
|
41
16
|
const manifest = manifestAt(directory);
|
|
42
17
|
const manager = projectPackageManager(directory, manifest?.packageManager, preferred);
|
|
43
|
-
await run(manager.name, manager.installArgs, directory);
|
|
18
|
+
await run(manager.name, manager.installArgs, directory, output);
|
|
44
19
|
return manager.name;
|
|
45
20
|
}
|
|
46
21
|
/**
|
|
@@ -56,32 +31,33 @@ export default async function ensureProjectDependency(name, range, directory = p
|
|
|
56
31
|
if (manifest.dependencies?.[name] || manifest.devDependencies?.[name])
|
|
57
32
|
return;
|
|
58
33
|
const manager = projectPackageManager(directory, manifest.packageManager);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
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);
|
|
62
36
|
console.log("");
|
|
63
37
|
}
|
|
64
|
-
function run(command, args, directory) {
|
|
38
|
+
function run(command, args, directory, output = "inherit") {
|
|
65
39
|
return new Promise(function (settle, refuse) {
|
|
40
|
+
let diagnostic = "";
|
|
66
41
|
const child = spawn(command, args, {
|
|
67
42
|
cwd: directory,
|
|
68
|
-
stdio: "inherit",
|
|
43
|
+
stdio: output === "capture" ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
69
44
|
shell: process.platform === "win32"
|
|
70
45
|
});
|
|
46
|
+
if (output === "capture") {
|
|
47
|
+
child.stdout?.on("data", chunk => { diagnostic += String(chunk); });
|
|
48
|
+
child.stderr?.on("data", chunk => { diagnostic += String(chunk); });
|
|
49
|
+
}
|
|
71
50
|
child.once("error", error => refuse(new Error(`Could not run ${command}: ${error.message}`)));
|
|
72
51
|
child.once("exit", function (code, signal) {
|
|
73
52
|
if (signal)
|
|
74
53
|
refuse(new Error(`${command} ended on ${signal}`));
|
|
75
54
|
else if (code !== 0)
|
|
76
|
-
refuse(new Error(`${command} exited with ${code ?? 0}`));
|
|
55
|
+
refuse(new Error(`${command} exited with ${code ?? 0}${diagnostic.trim() ? `\n\n${diagnostic.trim()}` : ""}`));
|
|
77
56
|
else
|
|
78
57
|
settle();
|
|
79
58
|
});
|
|
80
59
|
});
|
|
81
60
|
}
|
|
82
|
-
function accepts(range, version) {
|
|
83
|
-
return range === "workspace:*" || range === version || range === `^${version}` || range === `~${version}`;
|
|
84
|
-
}
|
|
85
61
|
function manifestAt(directory) {
|
|
86
62
|
const path = resolve(directory, "package.json");
|
|
87
63
|
if (!existsSync(path))
|
|
@@ -114,13 +90,6 @@ function packageManagerFromInvocation() {
|
|
|
114
90
|
function isPackageManager(value) {
|
|
115
91
|
return value === "bun" || value === "npm" || value === "pnpm" || value === "yarn";
|
|
116
92
|
}
|
|
117
|
-
const localDirectories = {
|
|
118
|
-
"@phreshos/core": "core-sdk",
|
|
119
|
-
"@phreshos/client": "client-sdk",
|
|
120
|
-
"@phreshos/server": "server-sdk",
|
|
121
|
-
"@phreshos/react": "react-sdk",
|
|
122
|
-
"@phreshos/cli": "cli"
|
|
123
|
-
};
|
|
124
93
|
const managers = {
|
|
125
94
|
bun: {
|
|
126
95
|
name: "bun",
|
package/dist/project.js
CHANGED
|
@@ -6,11 +6,10 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { layers } from "@phreshos/core";
|
|
9
|
+
import { isRelativeValue, layers } from "@phreshos/core";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
import { pathToFileURL } from "node:url";
|
|
13
|
-
import { isRelativeValue } from "./relative-value.js";
|
|
14
13
|
export const configFile = "phresh.config.ts";
|
|
15
14
|
export async function readConfig(directory = process.cwd()) {
|
|
16
15
|
const path = resolve(directory, configFile);
|
|
@@ -40,7 +39,7 @@ function coherent(config) {
|
|
|
40
39
|
throw new Error("A program's identity is kebab-case, because it is also the name of its directory");
|
|
41
40
|
if (!config.server && !config.client)
|
|
42
41
|
throw new Error("A program must have a server half, a client half, or both");
|
|
43
|
-
for (const field of ["name", "version", "description", "
|
|
42
|
+
for (const field of ["name", "version", "description", "icon"]) {
|
|
44
43
|
if (config[field] !== undefined && typeof config[field] !== "string")
|
|
45
44
|
throw new Error(`A program's ${field} must be text`);
|
|
46
45
|
}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
|
|
2
|
+
import colors from "picocolors";
|
|
3
|
+
import { column, heading, line } from "./style.js";
|
|
4
|
+
/** Signals an ordinary interactive cancellation rather than an operation failure. */
|
|
5
|
+
export class PromptCancelled extends Error {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* One interaction language shared by commands that can ask questions.
|
|
9
|
+
*
|
|
10
|
+
* Clack owns terminal behavior. This adapter owns only the product's wording
|
|
11
|
+
* and the rule that automation never opens or waits for a prompt.
|
|
12
|
+
*/
|
|
13
|
+
export default function prompts() {
|
|
14
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
15
|
+
function begin(title, context) {
|
|
16
|
+
if (interactive)
|
|
17
|
+
intro(`${colors.bold(title)}${context ? ` ${colors.dim(`· ${context}`)}` : ""}`);
|
|
18
|
+
else
|
|
19
|
+
heading(title, context);
|
|
20
|
+
}
|
|
21
|
+
function finish(message) {
|
|
22
|
+
if (interactive)
|
|
23
|
+
outro(colors.bold(message));
|
|
24
|
+
else
|
|
25
|
+
heading(message);
|
|
26
|
+
}
|
|
27
|
+
function detail(label, value, source) {
|
|
28
|
+
if (interactive)
|
|
29
|
+
log.message(`${colors.dim(column(label))}${value}${source ? ` ${colors.dim(source)}` : ""}`);
|
|
30
|
+
else
|
|
31
|
+
line(label, value, source);
|
|
32
|
+
}
|
|
33
|
+
function message(value = "") {
|
|
34
|
+
if (interactive)
|
|
35
|
+
log.message(value);
|
|
36
|
+
else
|
|
37
|
+
console.log(value ? ` ${value}` : "");
|
|
38
|
+
}
|
|
39
|
+
async function progress(message, completed, work) {
|
|
40
|
+
if (!interactive)
|
|
41
|
+
return await work();
|
|
42
|
+
const indicator = spinner();
|
|
43
|
+
indicator.start(message);
|
|
44
|
+
try {
|
|
45
|
+
const result = await work();
|
|
46
|
+
indicator.clear();
|
|
47
|
+
log.success(completed, { spacing: 0 });
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
indicator.error(`${message} failed`);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function ask(explanation, question, fallback) {
|
|
56
|
+
if (!interactive)
|
|
57
|
+
throw new Error(`${question} Supply the corresponding option when no terminal is attached`);
|
|
58
|
+
log.message(colors.dim(explanation));
|
|
59
|
+
const value = await text({
|
|
60
|
+
message: question,
|
|
61
|
+
placeholder: fallback,
|
|
62
|
+
defaultValue: fallback
|
|
63
|
+
});
|
|
64
|
+
if (isCancel(value))
|
|
65
|
+
stop();
|
|
66
|
+
return value.trim();
|
|
67
|
+
}
|
|
68
|
+
async function yes(explanation, question, fallback) {
|
|
69
|
+
if (!interactive)
|
|
70
|
+
throw new Error(`${question} Supply the corresponding option when no terminal is attached`);
|
|
71
|
+
log.message(colors.dim(explanation));
|
|
72
|
+
const value = await confirm({ message: question, initialValue: fallback });
|
|
73
|
+
if (isCancel(value))
|
|
74
|
+
stop();
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
async function choose(explanation, question, values, fallback) {
|
|
78
|
+
if (!interactive)
|
|
79
|
+
throw new Error(`${question} Supply the corresponding option when no terminal is attached`);
|
|
80
|
+
log.message(colors.dim(explanation));
|
|
81
|
+
const value = await select({
|
|
82
|
+
message: question,
|
|
83
|
+
options: values.map(value => ({ value, label: value })),
|
|
84
|
+
initialValue: fallback
|
|
85
|
+
});
|
|
86
|
+
if (isCancel(value))
|
|
87
|
+
stop();
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function stop() {
|
|
91
|
+
cancel("No changes made");
|
|
92
|
+
throw new PromptCancelled();
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
interactive,
|
|
96
|
+
begin,
|
|
97
|
+
finish,
|
|
98
|
+
detail,
|
|
99
|
+
message,
|
|
100
|
+
progress,
|
|
101
|
+
ask,
|
|
102
|
+
yes,
|
|
103
|
+
choose
|
|
104
|
+
};
|
|
105
|
+
}
|
package/dist/style.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* commands speaking three visual languages is worse than any one of
|
|
7
|
-
* them: a person reading `phresh install` after `phresh init` should not
|
|
8
|
-
* have to work out that it is the same tool.
|
|
9
|
-
*/
|
|
10
|
-
const colour = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
11
|
-
const escape = String.fromCharCode(27);
|
|
12
|
-
export const dim = (text) => colour ? `${escape}[2m${text}${escape}[22m` : text;
|
|
13
|
-
export const bold = (text) => colour ? `${escape}[1m${text}${escape}[22m` : text;
|
|
1
|
+
import colors from "picocolors";
|
|
2
|
+
/** The quiet and emphatic text used by non-interactive command reports. */
|
|
3
|
+
export const dim = colors.dim;
|
|
4
|
+
export const bold = colors.bold;
|
|
5
|
+
export const accent = colors.cyan;
|
|
14
6
|
// A label, what it says, and where that came from. The label is quiet
|
|
15
7
|
// and the value is not, because the value is the thing being reported.
|
|
16
8
|
//
|
|
@@ -18,7 +10,10 @@ export const bold = (text) => colour ? `${escape}[1m${text}${escape}[22m` : text
|
|
|
18
10
|
// even when one overruns: a label that runs into its value is worse than
|
|
19
11
|
// a column that does not line up.
|
|
20
12
|
export function line(label, value, note) {
|
|
21
|
-
console.log(` ${dim(label
|
|
13
|
+
console.log(` ${dim(column(label))}${value}${note ? ` ${dim(note)}` : ""}`);
|
|
14
|
+
}
|
|
15
|
+
export function column(label) {
|
|
16
|
+
return label.length < 12 ? label.padEnd(12) : `${label} `;
|
|
22
17
|
}
|
|
23
18
|
export function heading(title, note) {
|
|
24
19
|
console.log("");
|