@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/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.serverStartCommand === undefined && options.serverEntryFile === undefined && interactive
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.serverStartCommand, options.serverEntryFile, "production Server");
67
- let development = explicitExecution(options.serverDevelopmentStartCommand, options.serverDevelopmentEntryFile, "development Server");
68
- if (interactive && development === undefined) {
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
- development = await askExecution(interaction, "development", suggested || undefined);
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
- 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 } };
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 developmentUrl = options.clientDevelopmentUrl;
82
- let developmentStartCommand = options.clientDevelopmentStartCommand;
83
- if (interactive && developmentUrl === undefined && developmentStartCommand === undefined) {
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
- 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);
88
90
  }
89
91
  else
90
- 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/");
91
93
  }
92
94
  }
93
- if (developmentUrl !== undefined && developmentUrl.trim().length === 0)
95
+ if (devUrl !== undefined && devUrl.trim().length === 0)
94
96
  throw new Error("A client development URL must not be empty");
95
- if (developmentUrl !== undefined && !httpUrl(developmentUrl))
97
+ if (devUrl !== undefined && !httpUrl(devUrl))
96
98
  throw new Error("A client development URL must use HTTP or HTTPS");
97
- if (developmentStartCommand !== undefined && developmentStartCommand.trim().length === 0)
99
+ if (devCommand !== undefined && devCommand.trim().length === 0)
98
100
  throw new Error("A client development command must not be empty");
99
- let development;
100
- if (developmentStartCommand !== undefined)
101
- development = {
102
- startCommand: developmentStartCommand,
103
- ...developmentUrl !== undefined && { url: developmentUrl }
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(config.server.startCommand ? "server" : "server worker", config.server.startCommand ?? config.server.entryFile, `./${config.server.location}`);
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?.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);
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
- ...(config.server.startCommand !== undefined
144
- ? { startCommand: config.server.startCommand }
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
- ...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) {
@@ -182,35 +176,47 @@ function httpUrl(value) {
182
176
  return false;
183
177
  }
184
178
  }
185
- function execution(startCommand, entryFile, owner) {
186
- const selected = explicitExecution(startCommand, entryFile, owner);
187
- if (!selected)
188
- throw new Error(`Choose exactly one --server-start-command or --server-entry-file for the ${owner}`);
189
- return selected;
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 { startCommand };
186
+ return { command };
198
187
  }
199
- if (entryFile !== undefined) {
200
- if (entryFile.trim().length === 0)
201
- throw new Error(`The ${owner} entry file must not be empty`);
202
- if (!containedServerEntry(entryFile))
203
- throw new Error(`The ${owner} entry file must remain inside its Server directory`);
204
- return { entryFile };
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 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);
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
- 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
+ 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
- startCommand: await interaction.ask(`This command runs from the ${mode === "production" ? "production Server directory" : "project directory"}.`, `What command starts the ${mode} Server?`, suggestedCommand)
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
- line(definition.server.startCommand ? "server" : "server worker", String(definition.server.startCommand ?? definition.server.entryFile), place(project.directory, definition.server.location));
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?.development?.startCommand ?? place(project.directory, definition.client.location));
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
+ }
@@ -1,11 +1,11 @@
1
1
  import { inspect } from "node:util";
2
2
  import { assertValue } from "../contract/schema.js";
3
- import { readPath, renderFields, renderTable } from "./table.js";
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 "table": return renderCollection(value, presentation);
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 table = renderTable(presentation.columns, rows);
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 `${table}\n${summary}${truncated}`;
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.37",
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.43",
18
- "@phreshos/core": "^0.1.49",
19
- "@phreshos/react": "^0.1.25",
20
- "@phreshos/server": "^0.1.47",
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.62",
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.37",
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
- entryFile: "main.js",
16
- development: {
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
- development: {
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?.entryFile, "main.js")
13
- assert.equal(config.server?.development?.startCommand, "vite-node server/main.ts")
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
 
@@ -7,8 +7,7 @@ export default defineConfig({
7
7
  plugins: [react()],
8
8
  base: process.env.PHRESHOS_CLIENT_BASE ?? "./",
9
9
  resolve: {
10
- tsconfigPaths: true,
11
- dedupe: ["react"]
10
+ tsconfigPaths: true
12
11
  },
13
12
  server: {
14
13
  port: Number(process.env.PHRESHOS_CLIENT_PORT ?? "5200"),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "PhreshOS/phresh-program",
3
- "version": "0.1.37",
4
- "sha256": "2cf505d4ee6be9a7659b5969d59f7ed68fe9a2e44e79bd7fe827e9e42423b003",
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.62",
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.52",
51
- "@phreshos/node": "^0.1.25",
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",
@@ -1,4 +0,0 @@
1
- /** Wait for one named event while preserving the target's own subscription contract. */
2
- export async function wait(target, event, timeout) {
3
- return target.wait(event, timeout);
4
- }
@@ -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
- }