@phreshos/cli 0.1.62 → 0.1.64
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 +6 -5
- package/dist/commands/access.js +4 -0
- package/dist/commands/endpoint.js +52 -55
- package/dist/commands/execute.js +30 -0
- package/dist/commands/execution.js +44 -0
- package/dist/commands/input.js +4 -1
- package/dist/commands/operation.js +65 -0
- package/dist/commands/process.js +49 -52
- package/dist/commands/program.js +75 -52
- package/dist/commands/project.js +15 -15
- package/dist/commands/projection.js +3 -2
- package/dist/commands/schemas.js +11 -10
- package/dist/commands/window.js +44 -58
- package/dist/init.js +62 -56
- package/dist/launch.js +9 -3
- package/dist/output/format.js +35 -0
- package/dist/output/write.js +6 -5
- package/dist/template/package.json +6 -6
- package/dist/template/phresh.config.ts +4 -8
- package/dist/template/tests/build.test.ts +2 -2
- package/dist/template/vite.client.ts +1 -2
- package/dist/template.json +2 -2
- package/package.json +4 -6
- package/dist/commands/observation.js +0 -4
- package/dist/output/table.js +0 -88
package/dist/init.js
CHANGED
|
@@ -61,50 +61,48 @@ export default async function init(options = {}, directory = process.cwd(), core
|
|
|
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
62
|
if (!location)
|
|
63
63
|
throw new Error("--server-location is required without a terminal");
|
|
64
|
-
const production = options.
|
|
64
|
+
const production = options.serverCommand === undefined && options.serverWorker === undefined && options.serverSandbox === undefined && interactive
|
|
65
65
|
? await askExecution(interaction, "production", "node main.js")
|
|
66
|
-
: execution(options.
|
|
67
|
-
let
|
|
68
|
-
if (interactive &&
|
|
66
|
+
: execution(options.serverCommand, options.serverWorker, options.serverSandbox, "production Server");
|
|
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
|
-
|
|
71
|
+
devCommand = await ask("This command runs from the project directory with access to development tooling.", "What command starts the development Server?", suggested || undefined);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
|
|
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 } };
|
|
75
77
|
}
|
|
76
78
|
let client;
|
|
77
79
|
if (clientSelected) {
|
|
78
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") : "");
|
|
79
81
|
if (!location)
|
|
80
82
|
throw new Error("--client-location is required without a terminal");
|
|
81
|
-
let
|
|
82
|
-
let
|
|
83
|
-
if (interactive &&
|
|
83
|
+
let devUrl = options.clientDevUrl;
|
|
84
|
+
let devCommand = options.clientDevCommand;
|
|
85
|
+
if (interactive && devUrl === undefined && devCommand === undefined) {
|
|
84
86
|
const suggested = manifest.scripts?.dev && projectScript(directory, manifest.packageManager, "dev");
|
|
85
87
|
if (await yes("A development server can provide live updates instead of built Client files.", "Use a Client development server?", Boolean(suggested))) {
|
|
86
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))) {
|
|
87
|
-
|
|
89
|
+
devCommand = await ask("This command remains attached to the phresh dev session.", "What command starts the Client development server?", suggested || undefined);
|
|
88
90
|
}
|
|
89
91
|
else
|
|
90
|
-
|
|
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/");
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
|
-
if (
|
|
95
|
+
if (devUrl !== undefined && devUrl.trim().length === 0)
|
|
94
96
|
throw new Error("A client development URL must not be empty");
|
|
95
|
-
if (
|
|
97
|
+
if (devUrl !== undefined && !httpUrl(devUrl))
|
|
96
98
|
throw new Error("A client development URL must use HTTP or HTTPS");
|
|
97
|
-
if (
|
|
99
|
+
if (devCommand !== undefined && devCommand.trim().length === 0)
|
|
98
100
|
throw new Error("A client development command must not be empty");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
};
|
|
105
|
-
else if (developmentUrl !== undefined)
|
|
106
|
-
development = { url: developmentUrl };
|
|
107
|
-
client = { location, ...development && { development } };
|
|
101
|
+
client = {
|
|
102
|
+
location,
|
|
103
|
+
...devCommand !== undefined && { devCommand },
|
|
104
|
+
...devUrl !== undefined && { devUrl }
|
|
105
|
+
};
|
|
108
106
|
}
|
|
109
107
|
const described = {
|
|
110
108
|
identity: manifest.name,
|
|
@@ -119,10 +117,10 @@ export default async function init(options = {}, directory = process.cwd(), core
|
|
|
119
117
|
await ensureProjectDependency("@phreshos/core", coreRange, directory);
|
|
120
118
|
writeFileSync(path, compose(config));
|
|
121
119
|
if (config.server)
|
|
122
|
-
interaction.detail(
|
|
120
|
+
interaction.detail(`server ${serverExecution(config.server)[0]}`, serverExecution(config.server)[1], `./${config.server.location}`);
|
|
123
121
|
if (config.client)
|
|
124
122
|
interaction.detail("client", `./${config.client.location}`);
|
|
125
|
-
const next = [config.server?.
|
|
123
|
+
const next = [config.server?.devCommand || config.client?.devCommand || config.client?.devUrl ? "phresh dev" : null, "phresh start", "phresh install"].filter(Boolean);
|
|
126
124
|
interaction.message();
|
|
127
125
|
interaction.message(`${dim("Next:")} ${next.join(` ${dim("or")} `)}`);
|
|
128
126
|
if (config.client) {
|
|
@@ -140,14 +138,13 @@ function compose(config) {
|
|
|
140
138
|
field("buildCommand", config.buildCommand),
|
|
141
139
|
half("server", config.server && {
|
|
142
140
|
location: config.server.location,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
: { entryFile: config.server.entryFile }),
|
|
146
|
-
...config.server.development && { development: config.server.development }
|
|
141
|
+
[serverExecution(config.server)[0]]: serverExecution(config.server)[1],
|
|
142
|
+
devCommand: config.server.devCommand
|
|
147
143
|
}),
|
|
148
144
|
half("client", config.client && {
|
|
149
145
|
location: config.client.location,
|
|
150
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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) {
|
|
@@ -182,35 +176,47 @@ function httpUrl(value) {
|
|
|
182
176
|
return false;
|
|
183
177
|
}
|
|
184
178
|
}
|
|
185
|
-
function execution(
|
|
186
|
-
const selected =
|
|
187
|
-
if (
|
|
188
|
-
throw new Error(`Choose exactly one --server-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
function explicitExecution(startCommand, entryFile, owner) {
|
|
192
|
-
if (startCommand !== undefined && entryFile !== undefined)
|
|
193
|
-
throw new Error(`The ${owner} cannot declare both a start command and an entry file`);
|
|
194
|
-
if (startCommand !== undefined) {
|
|
195
|
-
if (startCommand.trim().length === 0)
|
|
179
|
+
function execution(command, worker, sandbox, owner) {
|
|
180
|
+
const selected = [command, worker, sandbox].filter(value => value !== undefined);
|
|
181
|
+
if (selected.length !== 1)
|
|
182
|
+
throw new Error(`Choose exactly one --server-command, --server-worker, or --server-sandbox for the ${owner}`);
|
|
183
|
+
if (command !== undefined) {
|
|
184
|
+
if (!command.trim())
|
|
196
185
|
throw new Error(`The ${owner} command must not be empty`);
|
|
197
|
-
return {
|
|
186
|
+
return { command };
|
|
198
187
|
}
|
|
199
|
-
if (
|
|
200
|
-
if (
|
|
201
|
-
throw new Error(`The ${owner}
|
|
202
|
-
if (!containedServerEntry(
|
|
203
|
-
throw new Error(`The ${owner} entry
|
|
204
|
-
return {
|
|
188
|
+
if (worker !== undefined) {
|
|
189
|
+
if (!worker.trim())
|
|
190
|
+
throw new Error(`The ${owner} worker must not be empty`);
|
|
191
|
+
if (!containedServerEntry(worker))
|
|
192
|
+
throw new Error(`The ${owner} worker entry must remain inside its Server directory`);
|
|
193
|
+
return { worker };
|
|
205
194
|
}
|
|
195
|
+
if (!sandbox?.trim())
|
|
196
|
+
throw new Error(`The ${owner} sandbox must not be empty`);
|
|
197
|
+
if (!containedServerEntry(sandbox))
|
|
198
|
+
throw new Error(`The ${owner} sandbox entry must remain inside its Server directory`);
|
|
199
|
+
return { sandbox };
|
|
206
200
|
}
|
|
207
201
|
async function askExecution(interaction, mode, suggestedCommand) {
|
|
208
|
-
const
|
|
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);
|
|
203
|
+
if (sandbox)
|
|
204
|
+
return {
|
|
205
|
+
sandbox: await interaction.ask("This bundled JavaScript module remains inside the Server files.", `What is the ${mode} Server Sandbox entry?`, "main.js")
|
|
206
|
+
};
|
|
207
|
+
const worker = await interaction.yes("A Worker uses Node.js host capabilities in an isolated thread.", `Run the ${mode} Server as a Worker?`, false);
|
|
209
208
|
if (worker)
|
|
210
209
|
return {
|
|
211
|
-
|
|
210
|
+
worker: await interaction.ask("This JavaScript module remains inside the Server files.", `What is the ${mode} Server Worker entry?`, "main.js")
|
|
212
211
|
};
|
|
213
212
|
return {
|
|
214
|
-
|
|
213
|
+
command: await interaction.ask("This command runs from the production Server directory.", `What command starts the ${mode} Server?`, suggestedCommand)
|
|
215
214
|
};
|
|
216
215
|
}
|
|
216
|
+
function serverExecution(server) {
|
|
217
|
+
if (server.command !== undefined)
|
|
218
|
+
return ["command", server.command];
|
|
219
|
+
if (server.worker !== undefined)
|
|
220
|
+
return ["worker", server.worker];
|
|
221
|
+
return ["sandbox", server.sandbox];
|
|
222
|
+
}
|
package/dist/launch.js
CHANGED
|
@@ -8,10 +8,16 @@ export default async function launch(mode, directory = process.cwd(), options =
|
|
|
8
8
|
heading(`${definition.name ?? definition.identity}${definition.version ? ` ${definition.version}` : ""}`, mode);
|
|
9
9
|
if (mode === "production" && project.config.buildCommand)
|
|
10
10
|
line("build", project.config.buildCommand);
|
|
11
|
-
if (definition.server)
|
|
12
|
-
|
|
11
|
+
if (definition.server) {
|
|
12
|
+
const [execution, value] = definition.server.command !== undefined
|
|
13
|
+
? ["command", definition.server.command]
|
|
14
|
+
: definition.server.worker !== undefined
|
|
15
|
+
? ["worker", definition.server.worker]
|
|
16
|
+
: ["sandbox", definition.server.sandbox];
|
|
17
|
+
line(`server ${execution}`, value, place(project.directory, definition.server.location));
|
|
18
|
+
}
|
|
13
19
|
if (definition.client)
|
|
14
|
-
line("client", project.config.client?.
|
|
20
|
+
line("client", project.config.client?.devCommand ?? place(project.directory, definition.client.location));
|
|
15
21
|
line("storage", place(project.directory, String(definition.storage)));
|
|
16
22
|
if (Object.keys(options).length)
|
|
17
23
|
line("options", Object.entries(options).map(([name, value]) => `${name}=${value}`).join(" "));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Render records as readable field blocks without terminal-table layout work. */
|
|
2
|
+
export function renderList(fields, rows) {
|
|
3
|
+
return rows.map(row => renderFields(fields, row)).join("\n\n");
|
|
4
|
+
}
|
|
5
|
+
/** Render one value as aligned labels with indented multiline values. */
|
|
6
|
+
export function renderFields(fields, source) {
|
|
7
|
+
const width = Math.max(...fields.map(field => field.label.length), 0);
|
|
8
|
+
return fields.map(field => {
|
|
9
|
+
const value = display(readPath(source, field.path), true);
|
|
10
|
+
const lines = value.split("\n");
|
|
11
|
+
const prefix = `${field.label.padEnd(width)} `;
|
|
12
|
+
const continuation = " ".repeat(prefix.length);
|
|
13
|
+
return [prefix + lines[0], ...lines.slice(1).map(line => continuation + line)].join("\n");
|
|
14
|
+
}).join("\n");
|
|
15
|
+
}
|
|
16
|
+
export function readPath(source, path) {
|
|
17
|
+
if (!path)
|
|
18
|
+
return source;
|
|
19
|
+
return path.split(".").reduce((current, key) => {
|
|
20
|
+
if (typeof current !== "object" || current === null)
|
|
21
|
+
return undefined;
|
|
22
|
+
return current[key];
|
|
23
|
+
}, source);
|
|
24
|
+
}
|
|
25
|
+
export function display(value, expanded = false) {
|
|
26
|
+
if (value === null || value === undefined || value === "")
|
|
27
|
+
return "—";
|
|
28
|
+
if (typeof value === "boolean")
|
|
29
|
+
return value ? "yes" : "no";
|
|
30
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint")
|
|
31
|
+
return String(value);
|
|
32
|
+
if (Array.isArray(value) && !expanded)
|
|
33
|
+
return value.length ? value.map(item => display(item)).join(", ") : "—";
|
|
34
|
+
return JSON.stringify(value, null, expanded ? 2 : undefined);
|
|
35
|
+
}
|
package/dist/output/write.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { inspect } from "node:util";
|
|
2
2
|
import { assertValue } from "../contract/schema.js";
|
|
3
|
-
import { readPath, renderFields,
|
|
3
|
+
import { readPath, renderFields, renderList } from "./format.js";
|
|
4
4
|
/** Validate command data once, then select its machine or human representation. */
|
|
5
5
|
export function writeData(result, contract, machine) {
|
|
6
6
|
const normalized = result ?? null;
|
|
7
7
|
assertValue(normalized, contract.value);
|
|
8
|
-
if (machine) {
|
|
8
|
+
if (machine || contract.presentation.format === "json") {
|
|
9
9
|
console.log(JSON.stringify(normalized));
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
@@ -14,10 +14,11 @@ export function writeData(result, contract, machine) {
|
|
|
14
14
|
}
|
|
15
15
|
function render(value, presentation) {
|
|
16
16
|
switch (presentation.format) {
|
|
17
|
-
case "
|
|
17
|
+
case "list": return renderCollection(value, presentation);
|
|
18
18
|
case "fields": return renderFields(presentation.fields, value);
|
|
19
19
|
case "document": return String(readPath(value, presentation.content) ?? "");
|
|
20
20
|
case "value": return inspect(value, { colors: false, depth: null, compact: false });
|
|
21
|
+
case "json": return JSON.stringify(value);
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
function renderCollection(value, presentation) {
|
|
@@ -25,7 +26,7 @@ function renderCollection(value, presentation) {
|
|
|
25
26
|
const rows = Array.isArray(selected) ? selected : [];
|
|
26
27
|
if (rows.length === 0)
|
|
27
28
|
return presentation.empty;
|
|
28
|
-
const
|
|
29
|
+
const list = renderList(presentation.fields, rows);
|
|
29
30
|
const totalValue = presentation.total ? readPath(value, presentation.total) : rows.length;
|
|
30
31
|
const total = typeof totalValue === "number" ? totalValue : rows.length;
|
|
31
32
|
const noun = total === 1 ? presentation.item : presentation.items;
|
|
@@ -35,5 +36,5 @@ function renderCollection(value, presentation) {
|
|
|
35
36
|
const truncated = presentation.truncated && readPath(value, presentation.truncated) === true
|
|
36
37
|
? " · more available"
|
|
37
38
|
: "";
|
|
38
|
-
return `${
|
|
39
|
+
return `${list}\n\n${summary}${truncated}`;
|
|
39
40
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phresh",
|
|
3
3
|
"private": true,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.40",
|
|
5
5
|
"description": "The official PhreshOS starter Program.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"starter"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@phreshos/client": "^0.1.
|
|
18
|
-
"@phreshos/core": "^0.1.
|
|
19
|
-
"@phreshos/react": "^0.1.
|
|
20
|
-
"@phreshos/server": "^0.1.
|
|
17
|
+
"@phreshos/client": "^0.1.46",
|
|
18
|
+
"@phreshos/core": "^0.1.54",
|
|
19
|
+
"@phreshos/react": "^0.1.28",
|
|
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.
|
|
25
|
+
"@phreshos/cli": "^0.1.64",
|
|
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.
|
|
7
|
+
version: "0.1.40",
|
|
8
8
|
icon: "icon.png",
|
|
9
9
|
categories: ["Development"],
|
|
10
10
|
keywords: ["example", "counter", "client", "server"],
|
|
@@ -12,17 +12,13 @@ export default defineConfig({
|
|
|
12
12
|
buildCommand: "vite-node scripts/build.ts",
|
|
13
13
|
server: {
|
|
14
14
|
location: "dist/server",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
startCommand: "vite-node server/main.ts"
|
|
18
|
-
}
|
|
15
|
+
worker: "main.js",
|
|
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
|
-
|
|
25
|
-
startCommand: "vite --config vite.client.ts"
|
|
26
|
-
}
|
|
22
|
+
devCommand: "vite --config vite.client.ts"
|
|
27
23
|
}
|
|
28
24
|
})
|
|
@@ -9,8 +9,8 @@ test("build contract", async () => {
|
|
|
9
9
|
assert.equal(config.name, "Phresh Program")
|
|
10
10
|
assert.equal(config.version, manifest.version)
|
|
11
11
|
assert.equal(config.server?.location, "dist/server")
|
|
12
|
-
assert.equal(config.server?.
|
|
13
|
-
assert.equal(config.server?.
|
|
12
|
+
assert.equal(config.server?.worker, "main.js")
|
|
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
|
|
package/dist/template.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repository": "PhreshOS/phresh-program",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"sha256": "
|
|
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.
|
|
4
|
+
"version": "0.1.64",
|
|
5
5
|
"description": "The Phresh command-line interface for Program projects and system management.",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.10"
|
|
@@ -47,13 +47,11 @@
|
|
|
47
47
|
"packageManager": "bun@1.3.14",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@clack/prompts": "^1.7.0",
|
|
50
|
-
"@phreshos/core": "^0.1.
|
|
51
|
-
"@phreshos/node": "^0.1.
|
|
50
|
+
"@phreshos/core": "^0.1.54",
|
|
51
|
+
"@phreshos/node": "^0.1.27",
|
|
52
52
|
"adm-zip": "^0.6.0",
|
|
53
|
-
"cli-table3": "^0.6.5",
|
|
54
53
|
"commander": "^15.0.0",
|
|
55
|
-
"picocolors": "^1.1.1"
|
|
56
|
-
"string-width": "^4.2.3"
|
|
54
|
+
"picocolors": "^1.1.1"
|
|
57
55
|
},
|
|
58
56
|
"devDependencies": {
|
|
59
57
|
"@types/adm-zip": "^0.5.8",
|
package/dist/output/table.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import Table from "cli-table3";
|
|
2
|
-
import stringWidth from "string-width";
|
|
3
|
-
const style = Object.freeze({ head: [], border: [] });
|
|
4
|
-
/** Render a table at its natural width, constraining and wrapping it only when necessary. */
|
|
5
|
-
export function renderTable(fields, rows) {
|
|
6
|
-
const width = terminalWidth();
|
|
7
|
-
if (width < minimumTableWidth(fields)) {
|
|
8
|
-
return rows.map(row => renderFields(fields, row)).join("\n");
|
|
9
|
-
}
|
|
10
|
-
const natural = createTable(fields, rows);
|
|
11
|
-
const rendered = natural.toString();
|
|
12
|
-
if (rendered.split("\n").every(line => stringWidth(line) <= width))
|
|
13
|
-
return rendered;
|
|
14
|
-
return createTable(fields, rows, allocateWidths(width, fields)).toString();
|
|
15
|
-
}
|
|
16
|
-
/** Render one value vertically so field names remain readable on narrow terminals. */
|
|
17
|
-
export function renderFields(fields, source) {
|
|
18
|
-
const rows = fields.map(field => [field.label, display(readPath(source, field.path), true)]);
|
|
19
|
-
const width = terminalWidth();
|
|
20
|
-
const desiredLabelWidth = Math.max(6, ...fields.map(field => stringWidth(field.label) + 2));
|
|
21
|
-
const labelWidth = Math.min(24, Math.max(6, width - 11), desiredLabelWidth);
|
|
22
|
-
const valueWidth = Math.max(8, width - labelWidth - 3);
|
|
23
|
-
const table = new Table({
|
|
24
|
-
colWidths: [labelWidth, valueWidth],
|
|
25
|
-
wordWrap: true,
|
|
26
|
-
wrapOnWordBoundary: false,
|
|
27
|
-
style
|
|
28
|
-
});
|
|
29
|
-
table.push(...rows);
|
|
30
|
-
return table.toString();
|
|
31
|
-
}
|
|
32
|
-
export function readPath(source, path) {
|
|
33
|
-
if (!path)
|
|
34
|
-
return source;
|
|
35
|
-
return path.split(".").reduce((current, key) => {
|
|
36
|
-
if (typeof current !== "object" || current === null)
|
|
37
|
-
return undefined;
|
|
38
|
-
return current[key];
|
|
39
|
-
}, source);
|
|
40
|
-
}
|
|
41
|
-
export function display(value, expanded = false) {
|
|
42
|
-
if (value === null || value === undefined || value === "")
|
|
43
|
-
return "—";
|
|
44
|
-
if (typeof value === "boolean")
|
|
45
|
-
return value ? "yes" : "no";
|
|
46
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint")
|
|
47
|
-
return String(value);
|
|
48
|
-
if (Array.isArray(value) && !expanded)
|
|
49
|
-
return value.length ? value.map(item => display(item)).join(", ") : "—";
|
|
50
|
-
return JSON.stringify(value, null, expanded ? 2 : undefined);
|
|
51
|
-
}
|
|
52
|
-
function createTable(fields, rows, colWidths) {
|
|
53
|
-
const table = new Table({
|
|
54
|
-
head: fields.map(field => field.label),
|
|
55
|
-
...(colWidths ? { colWidths } : {}),
|
|
56
|
-
wordWrap: true,
|
|
57
|
-
wrapOnWordBoundary: false,
|
|
58
|
-
style
|
|
59
|
-
});
|
|
60
|
-
table.push(...rows.map(row => fields.map(field => display(readPath(row, field.path)))));
|
|
61
|
-
return table;
|
|
62
|
-
}
|
|
63
|
-
function allocateWidths(width, fields) {
|
|
64
|
-
const minimums = fields.map(field => minimumColumnWidth(field));
|
|
65
|
-
const available = width - fields.length - 1;
|
|
66
|
-
const remaining = available - minimums.reduce((total, current) => total + current, 0);
|
|
67
|
-
const weights = fields.map(field => field.width ?? 1);
|
|
68
|
-
const totalWeight = weights.reduce((total, current) => total + current, 0);
|
|
69
|
-
const widths = minimums.map((minimum, index) => minimum + Math.floor(remaining * weights[index] / totalWeight));
|
|
70
|
-
let remainder = available - widths.reduce((total, current) => total + current, 0);
|
|
71
|
-
for (let index = 0; remainder > 0; index = (index + 1) % widths.length) {
|
|
72
|
-
widths[index] = widths[index] + 1;
|
|
73
|
-
remainder--;
|
|
74
|
-
}
|
|
75
|
-
return widths;
|
|
76
|
-
}
|
|
77
|
-
function minimumTableWidth(fields) {
|
|
78
|
-
return fields.reduce((total, field) => total + minimumColumnWidth(field), fields.length + 1);
|
|
79
|
-
}
|
|
80
|
-
function minimumColumnWidth(field) {
|
|
81
|
-
return Math.max(6, stringWidth(field.label) + 2);
|
|
82
|
-
}
|
|
83
|
-
function terminalWidth() {
|
|
84
|
-
const configured = Number(process.env.COLUMNS);
|
|
85
|
-
if (Number.isFinite(configured) && configured > 0)
|
|
86
|
-
return Math.floor(configured);
|
|
87
|
-
return process.stdout.columns ?? 80;
|
|
88
|
-
}
|