bun-workspaces 1.1.0 → 1.1.2
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 +24 -12
- package/package.json +3 -2
- package/src/cli/commands/commandHandlerUtils.d.ts +1 -0
- package/src/cli/commands/commandsConfig.d.ts +2 -3
- package/src/cli/commands/commandsConfig.mjs +1 -3
- package/src/cli/commands/runScript/handleRunScript.mjs +15 -6
- package/src/cli/commands/runScript/output/renderGroupedOutput.d.ts +2 -1
- package/src/cli/commands/runScript/output/renderGroupedOutput.mjs +50 -41
- package/src/cli/createCli.d.ts +1 -0
- package/src/cli/createCli.mjs +3 -0
- package/src/cli/globalOptions/globalOptions.mjs +4 -3
- package/src/internal/core/runtime/os.d.ts +2 -0
- package/src/internal/core/runtime/os.mjs +13 -1
- package/src/project/implementations/fileSystemProject.d.ts +4 -4
- package/src/project/implementations/fileSystemProject.mjs +78 -21
package/README.md
CHANGED
|
@@ -65,9 +65,6 @@ bw run lint my-workspace # Run for a single workspace
|
|
|
65
65
|
bw run lint my-workspace-a my-workspace-b # Run for multiple workspaces
|
|
66
66
|
bw run lint my-alias-a my-alias-b # Run by alias (set by optional config)
|
|
67
67
|
|
|
68
|
-
bw run lint "my-workspace-*" # Run for matching workspace names
|
|
69
|
-
bw run lint "alias:my-alias-pattern-*" "path:my-glob/**/*" # Use matching specifiers
|
|
70
|
-
|
|
71
68
|
# A workspace's script will wait until any workspaces it depends on have completed
|
|
72
69
|
# Similar to Bun's --filter behavior
|
|
73
70
|
bw run lint --dep-order
|
|
@@ -75,6 +72,9 @@ bw run lint --dep-order
|
|
|
75
72
|
# Continue running scripts even if a dependency fails
|
|
76
73
|
bw run lint --dep-order --ignore-dep-failure
|
|
77
74
|
|
|
75
|
+
bw run lint "my-workspace-*" # Run for matching workspace names
|
|
76
|
+
bw run lint "alias:my-alias-pattern-*" "path:my-glob/**/*" # Use matching specifiers
|
|
77
|
+
|
|
78
78
|
bw run lint --args="--my-appended-args" # Add args to each script call
|
|
79
79
|
bw run lint --args="--my-arg=<workspaceName>" # Use the workspace name in args
|
|
80
80
|
|
|
@@ -90,7 +90,7 @@ bw run lint --parallel=50% # Run in parallel with a max of 50% of the "auto" lim
|
|
|
90
90
|
bw run my-script --output-style=grouped
|
|
91
91
|
|
|
92
92
|
# Set the max preview lines for script output in grouped output style
|
|
93
|
-
bw run my-script --output-style=grouped --grouped-lines=
|
|
93
|
+
bw run my-script --output-style=grouped --grouped-lines=auto
|
|
94
94
|
bw run my-script --output-style=grouped --grouped-lines=10
|
|
95
95
|
|
|
96
96
|
# Use simple script output with workspace prefixes (default when not on a TTY)
|
|
@@ -140,14 +140,22 @@ const runSingleScript = async () => {
|
|
|
140
140
|
const { output, exit } = project.runWorkspaceScript({
|
|
141
141
|
workspaceNameOrAlias: "my-workspace",
|
|
142
142
|
script: "my-script",
|
|
143
|
-
|
|
143
|
+
|
|
144
|
+
// Optional. Arguments to add to the command
|
|
145
|
+
// Can be a string or an array of strings
|
|
146
|
+
// If string, the argv will be parsed POSIX-style
|
|
147
|
+
args: ["--my", "--appended", "--args"],
|
|
148
|
+
|
|
149
|
+
// Optional. Whether to ignore all output from the script.
|
|
150
|
+
// This saves memory when you don't need script output.
|
|
151
|
+
ignoreOutput: false,
|
|
144
152
|
});
|
|
145
153
|
|
|
146
154
|
// Get a stream of the script subprocess's output
|
|
147
155
|
for await (const { chunk, metadata } of output.text()) {
|
|
148
|
-
// console.log(chunk); //
|
|
149
|
-
// console.log(metadata.streamName); // "stdout" or "stderr"
|
|
150
|
-
// console.log(metadata.workspace); //
|
|
156
|
+
// console.log(chunk); // The output chunk's content (string)
|
|
157
|
+
// console.log(metadata.streamName); // The output stream, "stdout" or "stderr"
|
|
158
|
+
// console.log(metadata.workspace); // The target Workspace
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
// Get data about the script execution after it exits
|
|
@@ -173,8 +181,8 @@ const runManyScripts = async () => {
|
|
|
173
181
|
// Required. The package.json "scripts" field name to run
|
|
174
182
|
script: "my-script",
|
|
175
183
|
|
|
176
|
-
// Optional. Arguments to add to the command
|
|
177
|
-
args: "--my --appended --args",
|
|
184
|
+
// Optional. Arguments to add to the command (same as for runWorkspaceScript)
|
|
185
|
+
args: ["--my", "--appended", "--args"],
|
|
178
186
|
|
|
179
187
|
// Optional. Whether to run the scripts in parallel (default: true)
|
|
180
188
|
parallel: true,
|
|
@@ -187,6 +195,10 @@ const runManyScripts = async () => {
|
|
|
187
195
|
// continue running scripts even if a dependency fails
|
|
188
196
|
ignoreDependencyFailure: false,
|
|
189
197
|
|
|
198
|
+
// Optional. Whether to ignore all output from the scripts.
|
|
199
|
+
// This saves memory when you don't need script output.
|
|
200
|
+
ignoreOutput: false,
|
|
201
|
+
|
|
190
202
|
// Optional, callback when script starts, skips, or exits
|
|
191
203
|
onScriptEvent: (event, { workspace, exitResult }) => {
|
|
192
204
|
// event: "start", "skip", "exit"
|
|
@@ -195,9 +207,9 @@ const runManyScripts = async () => {
|
|
|
195
207
|
|
|
196
208
|
// Get a stream of script output
|
|
197
209
|
for await (const { chunk, metadata } of output.text()) {
|
|
198
|
-
// console.log(chunk); // the content (string)
|
|
210
|
+
// console.log(chunk); // the output chunk's content (string)
|
|
199
211
|
// console.log(metadata.streamName); // "stdout" or "stderr"
|
|
200
|
-
// console.log(metadata.workspace); // the
|
|
212
|
+
// console.log(metadata.workspace); // the Workspace that the output came from
|
|
201
213
|
}
|
|
202
214
|
|
|
203
215
|
// Get final summary data and script exit details after all scripts have completed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-workspaces",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A monorepo management tool for Bun, with a CLI and API to enhance Bun's native workspaces.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"commander": "^12.1.0"
|
|
35
|
+
"commander": "^12.1.0",
|
|
36
|
+
"shell-quote": "^1.8.3"
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -14,6 +14,7 @@ export type GlobalCommandContext = {
|
|
|
14
14
|
middleware: CliMiddleware;
|
|
15
15
|
outputWriters: Required<WriteOutputOptions>;
|
|
16
16
|
terminalWidth: number;
|
|
17
|
+
terminalHeight: number;
|
|
17
18
|
};
|
|
18
19
|
export type ProjectCommandContext = GlobalCommandContext & {
|
|
19
20
|
project: FileSystemProject;
|
|
@@ -26,7 +26,6 @@ export type CliProjectCommandName = Exclude<
|
|
|
26
26
|
CliGlobalCommandName
|
|
27
27
|
>;
|
|
28
28
|
export declare const JSON_FLAGS: readonly ["-j", "--json"];
|
|
29
|
-
export declare const DEFAULT_GROUPED_LINES = 20;
|
|
30
29
|
export declare const CLI_COMMANDS_CONFIG: {
|
|
31
30
|
readonly doctor: {
|
|
32
31
|
readonly command: "doctor";
|
|
@@ -153,7 +152,7 @@ export declare const CLI_COMMANDS_CONFIG: {
|
|
|
153
152
|
};
|
|
154
153
|
readonly groupedLines: {
|
|
155
154
|
readonly flags: ["-L", "--grouped-lines <count>"];
|
|
156
|
-
readonly description: 'With
|
|
155
|
+
readonly description: 'With grouped output, the max preview lines (number or "auto", default "auto")';
|
|
157
156
|
};
|
|
158
157
|
readonly noPrefix: {
|
|
159
158
|
readonly flags: ["-N", "--no-prefix"];
|
|
@@ -314,7 +313,7 @@ export declare const getCliCommandConfig: (commandName: CliCommandName) =>
|
|
|
314
313
|
};
|
|
315
314
|
readonly groupedLines: {
|
|
316
315
|
readonly flags: ["-L", "--grouped-lines <count>"];
|
|
317
|
-
readonly description: 'With
|
|
316
|
+
readonly description: 'With grouped output, the max preview lines (number or "auto", default "auto")';
|
|
318
317
|
};
|
|
319
318
|
readonly noPrefix: {
|
|
320
319
|
readonly flags: ["-N", "--no-prefix"];
|
|
@@ -4,7 +4,6 @@ import { OUTPUT_STYLE_VALUES } from "./runScript/output/outputStyle.mjs"; // CON
|
|
|
4
4
|
// CONCATENATED MODULE: ./src/cli/commands/commandsConfig.ts
|
|
5
5
|
|
|
6
6
|
const JSON_FLAGS = ["-j", "--json"];
|
|
7
|
-
const DEFAULT_GROUPED_LINES = 20;
|
|
8
7
|
const CLI_COMMANDS_CONFIG = {
|
|
9
8
|
doctor: {
|
|
10
9
|
command: "doctor",
|
|
@@ -133,7 +132,7 @@ const CLI_COMMANDS_CONFIG = {
|
|
|
133
132
|
},
|
|
134
133
|
groupedLines: {
|
|
135
134
|
flags: ["-L", "--grouped-lines <count>"],
|
|
136
|
-
description: `With
|
|
135
|
+
description: `With grouped output, the max preview lines (number or "auto", default "auto")`,
|
|
137
136
|
},
|
|
138
137
|
noPrefix: {
|
|
139
138
|
flags: ["-N", "--no-prefix"],
|
|
@@ -176,7 +175,6 @@ const getCliCommandNames = () => Object.keys(CLI_COMMANDS_CONFIG);
|
|
|
176
175
|
|
|
177
176
|
export {
|
|
178
177
|
CLI_COMMANDS_CONFIG,
|
|
179
|
-
DEFAULT_GROUPED_LINES,
|
|
180
178
|
JSON_FLAGS,
|
|
181
179
|
getCliCommandConfig,
|
|
182
180
|
getCliCommandNames,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { expandHomePath } from "../../../internal/core/index.mjs";
|
|
3
4
|
import { logger } from "../../../internal/logger/index.mjs";
|
|
4
5
|
import {
|
|
5
6
|
handleProjectCommand,
|
|
6
7
|
splitWorkspacePatterns,
|
|
7
8
|
} from "../commandHandlerUtils.mjs";
|
|
8
|
-
import { DEFAULT_GROUPED_LINES } from "../commandsConfig.mjs";
|
|
9
9
|
import {
|
|
10
10
|
getDefaultOutputStyle,
|
|
11
11
|
validateOutputStyle,
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
} from "./output/renderGroupedOutput.mjs";
|
|
18
18
|
import { renderPlainOutput } from "./output/renderPlainOutput.mjs"; // CONCATENATED MODULE: external "fs"
|
|
19
19
|
// CONCATENATED MODULE: external "path"
|
|
20
|
+
// CONCATENATED MODULE: external "../../../internal/core/index.mjs"
|
|
20
21
|
// CONCATENATED MODULE: external "../../../internal/logger/index.mjs"
|
|
21
22
|
// CONCATENATED MODULE: external "../commandHandlerUtils.mjs"
|
|
22
|
-
// CONCATENATED MODULE: external "../commandsConfig.mjs"
|
|
23
23
|
// CONCATENATED MODULE: external "./output/outputStyle.mjs"
|
|
24
24
|
// CONCATENATED MODULE: external "./output/renderGroupedOutput.mjs"
|
|
25
25
|
// CONCATENATED MODULE: external "./output/renderPlainOutput.mjs"
|
|
@@ -28,7 +28,13 @@ import { renderPlainOutput } from "./output/renderPlainOutput.mjs"; // CONCATENA
|
|
|
28
28
|
const runScript = handleProjectCommand(
|
|
29
29
|
"runScript",
|
|
30
30
|
async (
|
|
31
|
-
{
|
|
31
|
+
{
|
|
32
|
+
project,
|
|
33
|
+
postTerminatorArgs,
|
|
34
|
+
outputWriters,
|
|
35
|
+
terminalWidth,
|
|
36
|
+
terminalHeight,
|
|
37
|
+
},
|
|
32
38
|
positionalScript,
|
|
33
39
|
positionalWorkspacePatterns,
|
|
34
40
|
options,
|
|
@@ -52,7 +58,7 @@ const runScript = handleProjectCommand(
|
|
|
52
58
|
process.exit(1);
|
|
53
59
|
}
|
|
54
60
|
const scriptArgs = postTerminatorArgs.length
|
|
55
|
-
? postTerminatorArgs
|
|
61
|
+
? postTerminatorArgs
|
|
56
62
|
: options.args;
|
|
57
63
|
if (positionalWorkspacePatterns.length && options.workspacePatterns) {
|
|
58
64
|
logger.error(
|
|
@@ -114,10 +120,12 @@ const runScript = handleProjectCommand(
|
|
|
114
120
|
logger.debug(`Script name: ${scriptName}`);
|
|
115
121
|
const stripDisruptiveControls = workspaces.length > 1 || !!options.parallel;
|
|
116
122
|
logger.debug(`Strip disruptive controls: ${stripDisruptiveControls}`);
|
|
117
|
-
let groupedLines =
|
|
123
|
+
let groupedLines = "auto";
|
|
118
124
|
if (options.groupedLines) {
|
|
119
125
|
if (options.groupedLines === "all") {
|
|
120
126
|
groupedLines = "all";
|
|
127
|
+
} else if (options.groupedLines === "auto") {
|
|
128
|
+
groupedLines = "auto";
|
|
121
129
|
} else {
|
|
122
130
|
const parsedGroupedLines = parseInt(options.groupedLines);
|
|
123
131
|
if (parsedGroupedLines <= 0 || isNaN(parsedGroupedLines)) {
|
|
@@ -148,6 +156,7 @@ const runScript = handleProjectCommand(
|
|
|
148
156
|
groupedLines,
|
|
149
157
|
outputWriters,
|
|
150
158
|
terminalWidth,
|
|
159
|
+
terminalHeight,
|
|
151
160
|
),
|
|
152
161
|
prefixed: () =>
|
|
153
162
|
renderPlainOutput(output, outputWriters, {
|
|
@@ -196,7 +205,7 @@ const runScript = handleProjectCommand(
|
|
|
196
205
|
if (options.jsonOutfile) {
|
|
197
206
|
const fullOutputPath = path.resolve(
|
|
198
207
|
project.rootDirectory,
|
|
199
|
-
options.jsonOutfile,
|
|
208
|
+
expandHomePath(options.jsonOutfile),
|
|
200
209
|
);
|
|
201
210
|
// Check if can make directory
|
|
202
211
|
const jsonOutputDir = path.dirname(fullOutputPath);
|
|
@@ -67,8 +67,9 @@ export declare const renderGroupedOutput: (
|
|
|
67
67
|
output: RunScriptAcrossWorkspacesOutput,
|
|
68
68
|
summary: Promise<RunScriptsSummary<RunWorkspaceScriptMetadata>>,
|
|
69
69
|
scriptEventTarget: ScriptEventTarget,
|
|
70
|
-
activeScriptLines: number | "all",
|
|
70
|
+
activeScriptLines: number | "all" | "auto",
|
|
71
71
|
outputWriters: Required<WriteOutputOptions>,
|
|
72
72
|
terminalWidth: number,
|
|
73
|
+
terminalHeight: number,
|
|
73
74
|
) => Promise<void>;
|
|
74
75
|
export {};
|
|
@@ -55,7 +55,7 @@ const textOps = {
|
|
|
55
55
|
};
|
|
56
56
|
const STATUS_COLORS = {
|
|
57
57
|
pending: "gray",
|
|
58
|
-
running: "
|
|
58
|
+
running: "intenseMagenta",
|
|
59
59
|
skipped: "gray",
|
|
60
60
|
success: "intenseGreen",
|
|
61
61
|
failure: "intenseRed",
|
|
@@ -63,7 +63,8 @@ const STATUS_COLORS = {
|
|
|
63
63
|
cancelled: "gray",
|
|
64
64
|
killed: "intenseRed",
|
|
65
65
|
};
|
|
66
|
-
const BORDER_COLOR = "
|
|
66
|
+
const BORDER_COLOR = "intenseCyan";
|
|
67
|
+
const HEADER_ROWS_PER_WORKSPACE = 2;
|
|
67
68
|
const renderGroupedOutput = async (
|
|
68
69
|
workspaces,
|
|
69
70
|
output,
|
|
@@ -72,6 +73,7 @@ const renderGroupedOutput = async (
|
|
|
72
73
|
activeScriptLines,
|
|
73
74
|
outputWriters,
|
|
74
75
|
terminalWidth,
|
|
76
|
+
terminalHeight,
|
|
75
77
|
) => {
|
|
76
78
|
const workspaceState = workspaces.reduce((acc, workspace) => {
|
|
77
79
|
acc[workspace.name] = {
|
|
@@ -112,7 +114,26 @@ const renderGroupedOutput = async (
|
|
|
112
114
|
if (isFinal) {
|
|
113
115
|
didFinalRender = true;
|
|
114
116
|
}
|
|
115
|
-
const width = Math.max(2, terminalWidth || process.stdout.columns);
|
|
117
|
+
const width = Math.max(2, terminalWidth || process.stdout.columns || 2);
|
|
118
|
+
const height = Math.max(1, terminalHeight || process.stdout.rows || 1);
|
|
119
|
+
// Compute the max script lines to show per workspace based on terminal
|
|
120
|
+
// height, so the live TUI never exceeds the visible viewport (cursor up
|
|
121
|
+
// is clamped and cannot recover from overflow). Each workspace occupies
|
|
122
|
+
// HEADER_ROWS_PER_WORKSPACE rows plus one row for the hidden-lines
|
|
123
|
+
// indicator, with one additional safety row to prevent scroll on the
|
|
124
|
+
// final newline. The user's activeScriptLines acts as a ceiling if lower.
|
|
125
|
+
const availableRows = Math.max(
|
|
126
|
+
1,
|
|
127
|
+
height - 1 - workspaces.length * (HEADER_ROWS_PER_WORKSPACE + 1),
|
|
128
|
+
);
|
|
129
|
+
const computedScriptLines = Math.max(
|
|
130
|
+
1,
|
|
131
|
+
Math.floor(availableRows / workspaces.length),
|
|
132
|
+
);
|
|
133
|
+
const effectiveScriptLines =
|
|
134
|
+
activeScriptLines === "all" || activeScriptLines === "auto"
|
|
135
|
+
? computedScriptLines
|
|
136
|
+
: Math.min(activeScriptLines, computedScriptLines);
|
|
116
137
|
const linesToWrite = [];
|
|
117
138
|
const workspaceBoxContents = {};
|
|
118
139
|
workspaces.forEach((workspace) => {
|
|
@@ -134,69 +155,51 @@ const renderGroupedOutput = async (
|
|
|
134
155
|
} else if (exitState === "signal") {
|
|
135
156
|
statusText += ` (signal: ${state.signal})`;
|
|
136
157
|
}
|
|
137
|
-
const workspaceLine =
|
|
158
|
+
const workspaceLine =
|
|
159
|
+
textOps[BORDER_COLOR]("Workspace: ") + textOps.bold(workspace.name);
|
|
138
160
|
const statusLine =
|
|
139
|
-
" Status: " +
|
|
161
|
+
textOps[BORDER_COLOR](" Status: ") +
|
|
162
|
+
textOps[STATUS_COLORS[state.status]](statusText);
|
|
140
163
|
workspaceBoxContents[workspace.name] = {
|
|
141
164
|
name: workspaceLine,
|
|
142
165
|
status: statusLine,
|
|
143
166
|
};
|
|
144
167
|
});
|
|
145
168
|
const padding = 4; // left border, spaces, right border
|
|
146
|
-
const workspaceBoxWidth = Math.min(
|
|
147
|
-
width,
|
|
148
|
-
Math.max(
|
|
149
|
-
...Object.values(workspaceBoxContents).map((content) =>
|
|
150
|
-
Math.max(
|
|
151
|
-
calculateVisibleLength(content.name),
|
|
152
|
-
calculateVisibleLength(content.status),
|
|
153
|
-
),
|
|
154
|
-
),
|
|
155
|
-
) + padding,
|
|
156
|
-
);
|
|
157
169
|
workspaces.forEach((workspace) => {
|
|
158
170
|
const state = workspaceState[workspace.name];
|
|
159
171
|
const { name: workspaceNameContent, status: statusTextContent } =
|
|
160
172
|
workspaceBoxContents[workspace.name];
|
|
161
|
-
|
|
162
|
-
text: textOps[BORDER_COLOR](
|
|
163
|
-
"┌" + "─".repeat(workspaceBoxWidth - 2) + "┐",
|
|
164
|
-
),
|
|
165
|
-
type: "border",
|
|
166
|
-
});
|
|
167
|
-
const borderText = (text) => {
|
|
173
|
+
const borderText = (text, top, headerWidth) => {
|
|
168
174
|
const visibleLength = calculateVisibleLength(text);
|
|
169
175
|
const truncated =
|
|
170
176
|
visibleLength > width - padding
|
|
171
177
|
? truncateTerminalString(text, width - padding - 1) + "\x1b[0m…"
|
|
172
178
|
: text;
|
|
173
179
|
return (
|
|
174
|
-
textOps[BORDER_COLOR]("
|
|
180
|
+
textOps[BORDER_COLOR](top ? "┌ " : "└ ") +
|
|
175
181
|
truncated +
|
|
176
|
-
" ".repeat(Math.max(0,
|
|
177
|
-
textOps[BORDER_COLOR]("
|
|
182
|
+
" ".repeat(Math.max(0, headerWidth - visibleLength - padding)) +
|
|
183
|
+
textOps[BORDER_COLOR](top ? " ┐" : " ┘")
|
|
178
184
|
);
|
|
179
185
|
};
|
|
186
|
+
const headerWidth = Math.min(
|
|
187
|
+
width,
|
|
188
|
+
Math.max(
|
|
189
|
+
Bun.stripANSI(workspaceNameContent).length,
|
|
190
|
+
Bun.stripANSI(statusTextContent).length,
|
|
191
|
+
) + padding,
|
|
192
|
+
);
|
|
180
193
|
linesToWrite.push({
|
|
181
|
-
text: borderText(workspaceNameContent),
|
|
194
|
+
text: borderText(workspaceNameContent, true, headerWidth),
|
|
182
195
|
type: "borderedContent",
|
|
183
196
|
});
|
|
184
197
|
linesToWrite.push({
|
|
185
|
-
text: borderText(statusTextContent),
|
|
198
|
+
text: borderText(statusTextContent, false, headerWidth),
|
|
186
199
|
type: "borderedContent",
|
|
187
200
|
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
"└" + "─".repeat(workspaceBoxWidth - 2) + "┘",
|
|
191
|
-
),
|
|
192
|
-
type: "border",
|
|
193
|
-
});
|
|
194
|
-
if (
|
|
195
|
-
activeScriptLines !== "all" &&
|
|
196
|
-
state.lines.length > activeScriptLines &&
|
|
197
|
-
!isFinal
|
|
198
|
-
) {
|
|
199
|
-
const hiddenLines = state.lines.length - activeScriptLines;
|
|
201
|
+
if (state.lines.length > effectiveScriptLines && !isFinal) {
|
|
202
|
+
const hiddenLines = state.lines.length - effectiveScriptLines;
|
|
200
203
|
linesToWrite.push({
|
|
201
204
|
text: textOps.gray(
|
|
202
205
|
`(${hiddenLines} line${hiddenLines === 1 ? "" : "s"} hidden until exit)`,
|
|
@@ -206,7 +209,7 @@ const renderGroupedOutput = async (
|
|
|
206
209
|
}
|
|
207
210
|
linesToWrite.push(
|
|
208
211
|
...state.lines
|
|
209
|
-
.slice(isFinal ? undefined : -
|
|
212
|
+
.slice(isFinal ? undefined : -effectiveScriptLines)
|
|
210
213
|
.map((line) => ({
|
|
211
214
|
text: line.text,
|
|
212
215
|
type: "scriptOutput",
|
|
@@ -214,6 +217,12 @@ const renderGroupedOutput = async (
|
|
|
214
217
|
);
|
|
215
218
|
return linesToWrite;
|
|
216
219
|
});
|
|
220
|
+
if (isFinal) {
|
|
221
|
+
linesToWrite.push({
|
|
222
|
+
text: textOps[BORDER_COLOR]("─ Summary ─"),
|
|
223
|
+
type: "borderedContent",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
217
226
|
if (previousHeight > 0) {
|
|
218
227
|
// clear previous frame
|
|
219
228
|
outputWriters.stdout(cursorOps.up(previousHeight));
|
package/src/cli/createCli.d.ts
CHANGED
package/src/cli/createCli.mjs
CHANGED
|
@@ -30,6 +30,7 @@ const createCli = ({ defaultCwd = process.cwd(), defaultMiddleware } = {}) => {
|
|
|
30
30
|
middleware: _runMiddleware,
|
|
31
31
|
writeOutput,
|
|
32
32
|
terminalWidth = process.stdout.columns,
|
|
33
|
+
terminalHeight = process.stdout.rows,
|
|
33
34
|
} = {}) => {
|
|
34
35
|
const middleware = resolveMiddleware(
|
|
35
36
|
defaultMiddleware ?? {},
|
|
@@ -112,6 +113,7 @@ const createCli = ({ defaultCwd = process.cwd(), defaultMiddleware } = {}) => {
|
|
|
112
113
|
middleware,
|
|
113
114
|
outputWriters,
|
|
114
115
|
terminalWidth,
|
|
116
|
+
terminalHeight,
|
|
115
117
|
});
|
|
116
118
|
defineGlobalCommands({
|
|
117
119
|
program,
|
|
@@ -119,6 +121,7 @@ const createCli = ({ defaultCwd = process.cwd(), defaultMiddleware } = {}) => {
|
|
|
119
121
|
middleware,
|
|
120
122
|
outputWriters,
|
|
121
123
|
terminalWidth,
|
|
124
|
+
terminalHeight,
|
|
122
125
|
});
|
|
123
126
|
logger.debug(`Commands initialized. Parsing args...`);
|
|
124
127
|
middleware.preParse({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { Option } from "commander";
|
|
4
|
-
import { defineErrors } from "../../internal/core/index.mjs";
|
|
4
|
+
import { defineErrors, expandHomePath } from "../../internal/core/index.mjs";
|
|
5
5
|
import { logger } from "../../internal/logger/index.mjs";
|
|
6
6
|
import {
|
|
7
7
|
createFileSystemProject,
|
|
@@ -90,8 +90,9 @@ const defineGlobalOptions = (program, args, middleware) => {
|
|
|
90
90
|
program,
|
|
91
91
|
args,
|
|
92
92
|
);
|
|
93
|
-
const cwd =
|
|
94
|
-
cwdOption || (workspaceRootOption ? findRootFromCwd() : process.cwd())
|
|
93
|
+
const cwd = expandHomePath(
|
|
94
|
+
cwdOption || (workspaceRootOption ? findRootFromCwd() : process.cwd()),
|
|
95
|
+
);
|
|
95
96
|
const exists = fs.existsSync(cwd);
|
|
96
97
|
const isDirectory = exists ? fs.statSync(cwd).isDirectory() : false;
|
|
97
98
|
middleware.processWorkingDirectory({
|
|
@@ -2,3 +2,5 @@ export declare const IS_WINDOWS: boolean;
|
|
|
2
2
|
export declare const IS_MACOS: boolean;
|
|
3
3
|
export declare const IS_LINUX: boolean;
|
|
4
4
|
export declare const IS_POSIX: boolean;
|
|
5
|
+
/** Expands a leading `~` or `~/` to the user's home directory */
|
|
6
|
+
export declare const expandHomePath: (filePath: string) => string;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import path from "path"; // CONCATENATED MODULE: external "os"
|
|
3
|
+
// CONCATENATED MODULE: external "path"
|
|
1
4
|
// CONCATENATED MODULE: ./src/internal/core/runtime/os.ts
|
|
5
|
+
|
|
2
6
|
const IS_WINDOWS = process.platform === "win32";
|
|
3
7
|
const IS_MACOS = process.platform === "darwin";
|
|
4
8
|
const IS_LINUX = process.platform === "linux";
|
|
5
9
|
const IS_POSIX = IS_MACOS || IS_LINUX;
|
|
10
|
+
/** Expands a leading `~` or `~/` to the user's home directory */ const expandHomePath =
|
|
11
|
+
(filePath) => {
|
|
12
|
+
if (filePath === "~") return os.homedir();
|
|
13
|
+
if (filePath.startsWith("~/") || filePath.startsWith("~\\")) {
|
|
14
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
15
|
+
}
|
|
16
|
+
return filePath;
|
|
17
|
+
};
|
|
6
18
|
|
|
7
|
-
export { IS_LINUX, IS_MACOS, IS_POSIX, IS_WINDOWS };
|
|
19
|
+
export { IS_LINUX, IS_MACOS, IS_POSIX, IS_WINDOWS, expandHomePath };
|
|
@@ -39,8 +39,8 @@ export type RunWorkspaceScriptOptions = {
|
|
|
39
39
|
script: string;
|
|
40
40
|
/** Whether to run the script as an inline command */
|
|
41
41
|
inline?: boolean | InlineScriptOptions;
|
|
42
|
-
/** The arguments to append to the script command */
|
|
43
|
-
args?: string;
|
|
42
|
+
/** The arguments to append to the script command. If passed as a string, the argv will be parsed POSIX-style */
|
|
43
|
+
args?: string | string[];
|
|
44
44
|
/** Set to `true` to ignore all output from the script. This saves memory when you don't need script output. */
|
|
45
45
|
ignoreOutput?: boolean;
|
|
46
46
|
};
|
|
@@ -89,8 +89,8 @@ export type RunScriptAcrossWorkspacesOptions = {
|
|
|
89
89
|
script: string;
|
|
90
90
|
/** Whether to run the script as an inline command */
|
|
91
91
|
inline?: boolean | InlineScriptOptions;
|
|
92
|
-
/** The arguments to append to the script command.
|
|
93
|
-
args?: string;
|
|
92
|
+
/** The arguments to append to the script command. If passed as a string, the argv will be parsed POSIX-style */
|
|
93
|
+
args?: string | string[];
|
|
94
94
|
/** Whether to run the scripts in parallel (default: `true`). Pass `false` to run in series. */
|
|
95
95
|
parallel?: ParallelOption;
|
|
96
96
|
/** When `true`, run scripts so that dependent workspaces run only after their dependencies */
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { parse, quote } from "shell-quote/";
|
|
3
4
|
import { loadRootConfig } from "../../config/index.mjs";
|
|
4
5
|
import { getUserEnvVar } from "../../config/userEnvVars/index.mjs";
|
|
5
6
|
import {
|
|
6
7
|
DEFAULT_TEMP_DIR,
|
|
8
|
+
IS_WINDOWS,
|
|
9
|
+
InvalidJSTypeError,
|
|
10
|
+
expandHomePath,
|
|
7
11
|
isPlainObject,
|
|
12
|
+
validateJSArray,
|
|
8
13
|
validateJSTypes,
|
|
9
14
|
} from "../../internal/core/index.mjs";
|
|
10
15
|
import { logger } from "../../internal/logger/index.mjs";
|
|
@@ -26,6 +31,7 @@ import {
|
|
|
26
31
|
resolveWorkspacePath,
|
|
27
32
|
} from "./projectBase.mjs"; // CONCATENATED MODULE: external "fs"
|
|
28
33
|
// CONCATENATED MODULE: external "path"
|
|
34
|
+
// CONCATENATED MODULE: external "shell-quote/"
|
|
29
35
|
// CONCATENATED MODULE: external "../../config/index.mjs"
|
|
30
36
|
// CONCATENATED MODULE: external "../../config/userEnvVars/index.mjs"
|
|
31
37
|
// CONCATENATED MODULE: external "../../internal/core/index.mjs"
|
|
@@ -39,6 +45,41 @@ import {
|
|
|
39
45
|
// CONCATENATED MODULE: external "./projectBase.mjs"
|
|
40
46
|
// CONCATENATED MODULE: ./src/project/implementations/fileSystemProject.ts
|
|
41
47
|
|
|
48
|
+
const quoteArg = (arg, shell) =>
|
|
49
|
+
IS_WINDOWS && shell === "system"
|
|
50
|
+
? `"${arg.replace(/"/g, '""')}"`
|
|
51
|
+
: quote([arg]);
|
|
52
|
+
const serializeArgs = (args, metadata, shell) => {
|
|
53
|
+
if (!args || args.length === 0) return "";
|
|
54
|
+
if (Array.isArray(args)) {
|
|
55
|
+
return args
|
|
56
|
+
.map((arg) =>
|
|
57
|
+
quoteArg(interpolateScriptRuntimeMetadata(arg, metadata, shell), shell),
|
|
58
|
+
)
|
|
59
|
+
.join(" ");
|
|
60
|
+
}
|
|
61
|
+
const interpolated = interpolateScriptRuntimeMetadata(args, metadata, shell);
|
|
62
|
+
// Escape backslashes in interpolated values before POSIX parse on Windows,
|
|
63
|
+
// so that path separators survive parse's escape processing (\\→\)
|
|
64
|
+
const parseInput =
|
|
65
|
+
IS_WINDOWS && shell === "system"
|
|
66
|
+
? interpolated.replace(/\\/g, "\\\\")
|
|
67
|
+
: interpolated;
|
|
68
|
+
return parse(parseInput)
|
|
69
|
+
.flatMap((entry) => {
|
|
70
|
+
if (typeof entry === "string") {
|
|
71
|
+
return [quoteArg(entry, shell)];
|
|
72
|
+
}
|
|
73
|
+
if ("comment" in entry) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
if ("pattern" in entry) {
|
|
77
|
+
return [entry.pattern];
|
|
78
|
+
}
|
|
79
|
+
return [entry.op];
|
|
80
|
+
})
|
|
81
|
+
.join(" ");
|
|
82
|
+
};
|
|
42
83
|
class _FileSystemProject extends ProjectBase {
|
|
43
84
|
rootDirectory;
|
|
44
85
|
workspaces;
|
|
@@ -76,7 +117,7 @@ class _FileSystemProject extends ProjectBase {
|
|
|
76
117
|
}
|
|
77
118
|
this.rootDirectory = path.resolve(
|
|
78
119
|
process.cwd(),
|
|
79
|
-
options.rootDirectory ?? "",
|
|
120
|
+
expandHomePath(options.rootDirectory ?? ""),
|
|
80
121
|
);
|
|
81
122
|
const rootConfig = loadRootConfig(this.rootDirectory);
|
|
82
123
|
const { workspaces, workspaceMap, rootWorkspace } = findWorkspaces({
|
|
@@ -121,11 +162,6 @@ class _FileSystemProject extends ProjectBase {
|
|
|
121
162
|
typeofName: ["boolean", "object"],
|
|
122
163
|
optional: true,
|
|
123
164
|
},
|
|
124
|
-
"args option": {
|
|
125
|
-
value: options.args,
|
|
126
|
-
typeofName: "string",
|
|
127
|
-
optional: true,
|
|
128
|
-
},
|
|
129
165
|
"ignoreOutput option": {
|
|
130
166
|
value: options.ignoreOutput,
|
|
131
167
|
typeofName: "boolean",
|
|
@@ -136,6 +172,23 @@ class _FileSystemProject extends ProjectBase {
|
|
|
136
172
|
throw: true,
|
|
137
173
|
},
|
|
138
174
|
);
|
|
175
|
+
if (options.args !== undefined) {
|
|
176
|
+
if (typeof options.args !== "string" && !Array.isArray(options.args)) {
|
|
177
|
+
throw new InvalidJSTypeError(
|
|
178
|
+
`Type error: args option expects type string | string[], received ${typeof options.args}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(options.args)) {
|
|
182
|
+
const argsError = validateJSArray({
|
|
183
|
+
value: options.args,
|
|
184
|
+
valueLabel: "args option",
|
|
185
|
+
itemOptions: {
|
|
186
|
+
typeofName: "string",
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
if (argsError) throw argsError;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
139
192
|
if (isPlainObject(options.inline)) {
|
|
140
193
|
validateJSTypes(
|
|
141
194
|
{
|
|
@@ -184,11 +237,7 @@ class _FileSystemProject extends ProjectBase {
|
|
|
184
237
|
workspaceName: workspace.name,
|
|
185
238
|
scriptName: options.inline ? inlineScriptName : options.script,
|
|
186
239
|
};
|
|
187
|
-
const args =
|
|
188
|
-
options.args ?? "",
|
|
189
|
-
scriptRuntimeMetadata,
|
|
190
|
-
shell,
|
|
191
|
-
);
|
|
240
|
+
const args = serializeArgs(options.args, scriptRuntimeMetadata, shell);
|
|
192
241
|
const script = options.inline
|
|
193
242
|
? interpolateScriptRuntimeMetadata(
|
|
194
243
|
options.script,
|
|
@@ -242,11 +291,6 @@ class _FileSystemProject extends ProjectBase {
|
|
|
242
291
|
typeofName: ["boolean", "object"],
|
|
243
292
|
optional: true,
|
|
244
293
|
},
|
|
245
|
-
"args option": {
|
|
246
|
-
value: options.args,
|
|
247
|
-
typeofName: "string",
|
|
248
|
-
optional: true,
|
|
249
|
-
},
|
|
250
294
|
"parallel option": {
|
|
251
295
|
value: options.parallel,
|
|
252
296
|
typeofName: ["boolean", "object"],
|
|
@@ -296,6 +340,23 @@ class _FileSystemProject extends ProjectBase {
|
|
|
296
340
|
},
|
|
297
341
|
);
|
|
298
342
|
}
|
|
343
|
+
if (options.args !== undefined) {
|
|
344
|
+
if (typeof options.args !== "string" && !Array.isArray(options.args)) {
|
|
345
|
+
throw new InvalidJSTypeError(
|
|
346
|
+
`Type error: args option expects type string | string[], received ${typeof options.args}`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
if (Array.isArray(options.args)) {
|
|
350
|
+
const argsError = validateJSArray({
|
|
351
|
+
value: options.args,
|
|
352
|
+
valueLabel: "args option",
|
|
353
|
+
itemOptions: {
|
|
354
|
+
typeofName: "string",
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
if (argsError) throw argsError;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
299
360
|
if (isPlainObject(options.parallel)) {
|
|
300
361
|
validateJSTypes(
|
|
301
362
|
{
|
|
@@ -383,11 +444,7 @@ class _FileSystemProject extends ProjectBase {
|
|
|
383
444
|
workspaceName: workspace.name,
|
|
384
445
|
scriptName: options.inline ? inlineScriptName : options.script,
|
|
385
446
|
};
|
|
386
|
-
const args =
|
|
387
|
-
options.args ?? "",
|
|
388
|
-
scriptRuntimeMetadata,
|
|
389
|
-
shell,
|
|
390
|
-
);
|
|
447
|
+
const args = serializeArgs(options.args, scriptRuntimeMetadata, shell);
|
|
391
448
|
const script = options.inline
|
|
392
449
|
? interpolateScriptRuntimeMetadata(
|
|
393
450
|
options.script,
|