bun-workspaces 1.8.1 → 1.9.0
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 +51 -13
- package/package.json +1 -1
- package/src/2392.mjs +184 -3
- package/src/5166.mjs +1 -0
- package/src/8529.mjs +10 -0
- package/src/affected/affectedBaseRef.mjs +12 -0
- package/src/affected/externalDependencyChanges.mjs +47 -0
- package/src/affected/fileAffectedWorkspaces.mjs +413 -0
- package/src/affected/gitAffectedFiles.mjs +196 -0
- package/src/affected/gitAffectedWorkspaces.mjs +108 -0
- package/src/affected/index.mjs +7 -0
- package/src/ai/mcp/serverState.mjs +1 -1
- package/src/cli/commands/commandHandlerUtils.mjs +12 -7
- package/src/cli/commands/commands.mjs +4 -1
- package/src/cli/commands/handleSimpleCommands.mjs +2 -2
- package/src/cli/commands/listAffected.mjs +184 -0
- package/src/cli/commands/runScript/handleRunAffected.mjs +99 -0
- package/src/cli/commands/runScript/handleRunScript.mjs +19 -202
- package/src/cli/commands/runScript/index.mjs +1 -0
- package/src/cli/commands/runScript/output/renderGroupedOutput.mjs +22 -7
- package/src/cli/commands/runScript/output/tuiTerminal.mjs +45 -0
- package/src/cli/commands/runScript/scriptRunFlow.mjs +213 -0
- package/src/cli/index.d.ts +749 -134
- package/src/config/public.d.ts +66 -2
- package/src/config/rootConfig/rootConfig.mjs +4 -0
- package/src/config/rootConfig/rootConfigSchema.mjs +3 -0
- package/src/config/workspaceConfig/mergeWorkspaceConfig.mjs +33 -19
- package/src/config/workspaceConfig/workspaceConfig.mjs +3 -0
- package/src/config/workspaceConfig/workspaceConfigSchema.mjs +26 -0
- package/src/index.d.ts +307 -5
- package/src/index.mjs +1 -0
- package/src/internal/bun/bunLock.mjs +33 -0
- package/src/internal/generated/aiDocs/docs.mjs +152 -3
- package/src/internal/generated/ajv/validateRootConfig.mjs +1 -1
- package/src/internal/generated/ajv/validateWorkspaceConfig.mjs +1 -1
- package/src/project/implementations/fileSystemProject/affectedWorkspaces.mjs +225 -0
- package/src/project/implementations/{fileSystemProject.mjs → fileSystemProject/fileSystemProject.mjs} +169 -12
- package/src/project/implementations/fileSystemProject/index.mjs +4 -0
- package/src/project/implementations/memoryProject.mjs +1 -0
- package/src/project/index.mjs +1 -1
- package/src/rslib-runtime.mjs +0 -31
- package/src/workspaces/applyWorkspacePatternConfigs.mjs +10 -1
- package/src/workspaces/dependencyGraph/resolveDependencies.mjs +68 -18
- package/src/workspaces/findWorkspaces.mjs +1 -0
- package/src/workspaces/index.mjs +1 -0
- package/src/workspaces/workspace.mjs +8 -2
- package/src/workspaces/workspacePattern.mjs +13 -3
|
@@ -3,12 +3,17 @@ import { BunWorkspacesError } from "../../internal/core/error/index.mjs";
|
|
|
3
3
|
import { createLogger, logger } from "../../internal/logger/index.mjs";
|
|
4
4
|
import { getCliCommandConfig } from "../../2392.mjs";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Splits a multi-value CLI arg on whitespace (any of space/tab/newline). A
|
|
8
|
+
* literal space inside a value can be preserved by escaping it with a
|
|
9
|
+
* backslash (e.g. `path/with\ space`). Used for `--files` and
|
|
10
|
+
* `--workspace-patterns`, both of which accept output of `$(bw ...)`
|
|
11
|
+
* substitutions, which are typically newline-separated.
|
|
12
|
+
*/ const splitWhitespaceArg = (raw) =>
|
|
13
|
+
raw
|
|
14
|
+
.split(/(?<!\\)\s+/)
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.map((value) => value.replace(/\\\s/g, " "));
|
|
12
17
|
const createWorkspaceInfoLines = (workspace) => [
|
|
13
18
|
`Workspace: ${workspace.name}${workspace.isRoot ? " (root)" : ""}`,
|
|
14
19
|
` - Aliases: ${workspace.aliases.join(", ")}`,
|
|
@@ -85,5 +90,5 @@ export {
|
|
|
85
90
|
createWorkspaceInfoLines,
|
|
86
91
|
handleGlobalCommand,
|
|
87
92
|
handleProjectCommand,
|
|
88
|
-
|
|
93
|
+
splitWhitespaceArg,
|
|
89
94
|
};
|
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
tagInfo,
|
|
8
8
|
workspaceInfo,
|
|
9
9
|
} from "./handleSimpleCommands.mjs";
|
|
10
|
+
import { listAffected } from "./listAffected.mjs";
|
|
10
11
|
import { mcpServer } from "./mcp.mjs";
|
|
11
|
-
import { runScript } from "./runScript/index.mjs";
|
|
12
|
+
import { runAffected, runScript } from "./runScript/index.mjs";
|
|
12
13
|
|
|
13
14
|
const defineGlobalCommands = (context) => {
|
|
14
15
|
doctor(context);
|
|
@@ -22,6 +23,8 @@ const defineProjectCommands = (context) => {
|
|
|
22
23
|
tagInfo(context);
|
|
23
24
|
mcpServer(context);
|
|
24
25
|
runScript(context);
|
|
26
|
+
listAffected(context);
|
|
27
|
+
runAffected(context);
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
export { defineGlobalCommands, defineProjectCommands };
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
createWorkspaceInfoLines,
|
|
8
8
|
handleGlobalCommand,
|
|
9
9
|
handleProjectCommand,
|
|
10
|
-
|
|
10
|
+
splitWhitespaceArg,
|
|
11
11
|
} from "./commandHandlerUtils.mjs";
|
|
12
12
|
import { isJSONObject } from "../../8257.mjs";
|
|
13
13
|
|
|
@@ -51,7 +51,7 @@ const listWorkspaces = handleProjectCommand(
|
|
|
51
51
|
}
|
|
52
52
|
const patterns = positionalWorkspacePatterns?.length
|
|
53
53
|
? positionalWorkspacePatterns
|
|
54
|
-
:
|
|
54
|
+
: splitWhitespaceArg(options.workspacePatterns ?? "");
|
|
55
55
|
const workspaces = patterns?.length
|
|
56
56
|
? project.findWorkspacesByPattern(...patterns)
|
|
57
57
|
: project.workspaces;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { logger } from "../../internal/logger/index.mjs";
|
|
3
|
+
import {
|
|
4
|
+
commandOutputLogger,
|
|
5
|
+
createJsonLines,
|
|
6
|
+
handleProjectCommand,
|
|
7
|
+
splitWhitespaceArg,
|
|
8
|
+
} from "./commandHandlerUtils.mjs";
|
|
9
|
+
|
|
10
|
+
const SHORT_SHA_LENGTH = 7;
|
|
11
|
+
const shortSha = (sha) => sha.slice(0, SHORT_SHA_LENGTH);
|
|
12
|
+
const formatGitHeader = (metadata) => {
|
|
13
|
+
if (metadata.diffSource !== "git" || !metadata.git) return null;
|
|
14
|
+
const { baseRef, headRef, baseSha, headSha } = metadata.git;
|
|
15
|
+
return [
|
|
16
|
+
`Git base ref: \x1b[1m${baseRef}\x1b[0m (${shortSha(baseSha)})`,
|
|
17
|
+
`Git head ref: \x1b[1m${headRef}\x1b[0m (${shortSha(headSha)})`,
|
|
18
|
+
].join("\n");
|
|
19
|
+
};
|
|
20
|
+
const formatDependencyChain = (dependency) => {
|
|
21
|
+
const segments = dependency.chain.map((entry, index) => {
|
|
22
|
+
if (index === 0 || !entry.edgeSource) return entry.workspaceName;
|
|
23
|
+
return `\x1b[90m--[${entry.edgeSource}]->\x1b[0m ${entry.workspaceName}`;
|
|
24
|
+
});
|
|
25
|
+
return segments.join(" ");
|
|
26
|
+
};
|
|
27
|
+
const formatSourceMarker = (change) =>
|
|
28
|
+
change.source === "devDependencies" ? " (dev)" : "";
|
|
29
|
+
const formatExternalDepEntryShort = (change) =>
|
|
30
|
+
`${change.name}${formatSourceMarker(change)}`;
|
|
31
|
+
const formatExternalDepEntryDetailed = (change) => {
|
|
32
|
+
const versions =
|
|
33
|
+
change.baseVersion === null && change.headVersion === null
|
|
34
|
+
? "lockfile changed; precise diff unavailable"
|
|
35
|
+
: `${change.baseVersion ?? "(absent)"} -> ${change.headVersion ?? "(absent)"}`;
|
|
36
|
+
return `${change.name}${formatSourceMarker(change)} \x1b[90m[${versions}]\x1b[0m`;
|
|
37
|
+
};
|
|
38
|
+
const createWorkspaceSummaryLines = (result) => {
|
|
39
|
+
const { workspace, affectedReasons } = result;
|
|
40
|
+
const lines = [
|
|
41
|
+
`\x1b[1mWorkspace: ${workspace.name}\x1b[0m`,
|
|
42
|
+
`Path: ${workspace.path}`,
|
|
43
|
+
];
|
|
44
|
+
lines.push(
|
|
45
|
+
`\x1b[96mChanged input files:\x1b[0m ${affectedReasons.changedFiles.length}`,
|
|
46
|
+
);
|
|
47
|
+
if (affectedReasons.dependencies.length) {
|
|
48
|
+
lines.push(
|
|
49
|
+
`\x1b[96mAffected dependencies:\x1b[0m ${affectedReasons.dependencies.map(({ dependencyName }) => dependencyName).join(", ")}`,
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
lines.push(`\x1b[96mAffected dependencies:\x1b[0m (none)`);
|
|
53
|
+
}
|
|
54
|
+
if (affectedReasons.externalDependencies.length) {
|
|
55
|
+
lines.push(
|
|
56
|
+
`\x1b[96mChanged external dependencies:\x1b[0m ${affectedReasons.externalDependencies.map(formatExternalDepEntryShort).join(", ")}`,
|
|
57
|
+
);
|
|
58
|
+
} else {
|
|
59
|
+
lines.push(`\x1b[96mChanged external dependencies:\x1b[0m (none)`);
|
|
60
|
+
}
|
|
61
|
+
return lines;
|
|
62
|
+
};
|
|
63
|
+
const createWorkspaceDetailedLines = (result) => {
|
|
64
|
+
const { workspace, affectedReasons } = result;
|
|
65
|
+
const lines = [
|
|
66
|
+
`\x1b[1mWorkspace: ${workspace.name}\x1b[0m`,
|
|
67
|
+
`Path: ${workspace.path}`,
|
|
68
|
+
];
|
|
69
|
+
if (affectedReasons.changedFiles.length) {
|
|
70
|
+
lines.push("\x1b[96mChanged input files:\x1b[0m");
|
|
71
|
+
for (const file of affectedReasons.changedFiles) {
|
|
72
|
+
const reasons = file.gitReasons
|
|
73
|
+
?.filter((reason) => reason !== "diff")
|
|
74
|
+
.join(", ");
|
|
75
|
+
lines.push(
|
|
76
|
+
` - ${path.relative(workspace.path, file.projectFilePath)} \x1b[90m(input: ${JSON.stringify(file.inputMatch)})${reasons ? ` [${reasons}]` : ""}\x1b[0m`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
lines.push("\x1b[96mChanged input files:\x1b[0m (none)");
|
|
81
|
+
}
|
|
82
|
+
if (affectedReasons.dependencies.length) {
|
|
83
|
+
lines.push("\x1b[96mAffected dependencies:\x1b[0m");
|
|
84
|
+
for (const dependency of affectedReasons.dependencies) {
|
|
85
|
+
lines.push(` - ${dependency.dependencyName}`);
|
|
86
|
+
lines.push(` chain: ${formatDependencyChain(dependency)}`);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
lines.push("\x1b[96mAffected dependencies:\x1b[0m (none)");
|
|
90
|
+
}
|
|
91
|
+
if (affectedReasons.externalDependencies.length) {
|
|
92
|
+
lines.push("\x1b[96mChanged external dependencies:\x1b[0m");
|
|
93
|
+
for (const change of affectedReasons.externalDependencies) {
|
|
94
|
+
lines.push(` - ${formatExternalDepEntryDetailed(change)}`);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
lines.push("\x1b[96mChanged external dependencies:\x1b[0m (none)");
|
|
98
|
+
}
|
|
99
|
+
return lines;
|
|
100
|
+
};
|
|
101
|
+
const listAffected = handleProjectCommand(
|
|
102
|
+
"listAffected",
|
|
103
|
+
async ({ project }, options) => {
|
|
104
|
+
logger.debug(`Options: ${JSON.stringify(options)}`);
|
|
105
|
+
if (options.files !== undefined && (options.base || options.head)) {
|
|
106
|
+
logger.error(
|
|
107
|
+
"CLI syntax error: --files cannot be used with --base or --head",
|
|
108
|
+
);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (options.detailed && !options.explain) {
|
|
113
|
+
logger.error("CLI syntax error: --detailed requires --explain");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const determineOptions =
|
|
118
|
+
options.files !== undefined
|
|
119
|
+
? {
|
|
120
|
+
diffSource: "fileList",
|
|
121
|
+
changedFiles: splitWhitespaceArg(options.files),
|
|
122
|
+
script: options.script,
|
|
123
|
+
ignoreWorkspaceDependencies:
|
|
124
|
+
options.ignoreWorkspaceDeps || undefined,
|
|
125
|
+
ignoreExternalDependencies: options.ignoreExternalDeps || undefined,
|
|
126
|
+
}
|
|
127
|
+
: {
|
|
128
|
+
diffSource: "git",
|
|
129
|
+
script: options.script,
|
|
130
|
+
ignoreWorkspaceDependencies:
|
|
131
|
+
options.ignoreWorkspaceDeps || undefined,
|
|
132
|
+
ignoreExternalDependencies: options.ignoreExternalDeps || undefined,
|
|
133
|
+
diffOptions: {
|
|
134
|
+
baseRef: options.base,
|
|
135
|
+
headRef: options.head,
|
|
136
|
+
ignoreUntracked: options.ignoreUntracked || undefined,
|
|
137
|
+
ignoreUnstaged: options.ignoreUnstaged || undefined,
|
|
138
|
+
ignoreStaged: options.ignoreStaged || undefined,
|
|
139
|
+
ignoreUncommitted: options.ignoreUncommitted || undefined,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
const result = await project.determineAffectedWorkspaces(determineOptions);
|
|
143
|
+
const affectedResults = result.workspaceResults.filter(
|
|
144
|
+
({ isAffected }) => isAffected,
|
|
145
|
+
);
|
|
146
|
+
if (options.json) {
|
|
147
|
+
const payload = options.explain
|
|
148
|
+
? result
|
|
149
|
+
: affectedResults.map(({ workspace }) => workspace.name);
|
|
150
|
+
commandOutputLogger.info(createJsonLines(payload, options).join("\n"));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!options.explain) {
|
|
154
|
+
if (affectedResults.length) {
|
|
155
|
+
commandOutputLogger.info(
|
|
156
|
+
affectedResults.map(({ workspace }) => workspace.name).join("\n"),
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
logger.info("No affected workspaces");
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const lines = [""];
|
|
164
|
+
const gitHeader = formatGitHeader(result.metadata);
|
|
165
|
+
if (gitHeader) lines.push(gitHeader, "");
|
|
166
|
+
if (!affectedResults.length) {
|
|
167
|
+
lines.push("No affected workspaces");
|
|
168
|
+
} else {
|
|
169
|
+
const renderWorkspace = options.detailed
|
|
170
|
+
? createWorkspaceDetailedLines
|
|
171
|
+
: createWorkspaceSummaryLines;
|
|
172
|
+
for (const workspaceResult of affectedResults) {
|
|
173
|
+
lines.push(...renderWorkspace(workspaceResult), "");
|
|
174
|
+
}
|
|
175
|
+
if (!options.detailed) {
|
|
176
|
+
// gray
|
|
177
|
+
lines.push("\x1b[90mPass --detailed for more info\x1b[0m");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
commandOutputLogger.info(lines.join("\n"));
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
export { listAffected };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { logger } from "../../../internal/logger/index.mjs";
|
|
2
|
+
import {
|
|
3
|
+
handleProjectCommand,
|
|
4
|
+
splitWhitespaceArg,
|
|
5
|
+
} from "../commandHandlerUtils.mjs";
|
|
6
|
+
import { handleScriptRunFlow } from "./scriptRunFlow.mjs";
|
|
7
|
+
|
|
8
|
+
const runAffected = handleProjectCommand(
|
|
9
|
+
"runAffected",
|
|
10
|
+
async (
|
|
11
|
+
{
|
|
12
|
+
project,
|
|
13
|
+
postTerminatorArgs,
|
|
14
|
+
outputWriters,
|
|
15
|
+
terminalWidth,
|
|
16
|
+
terminalHeight,
|
|
17
|
+
},
|
|
18
|
+
positionalScript,
|
|
19
|
+
options,
|
|
20
|
+
) => {
|
|
21
|
+
options.inlineName = options.inlineName?.trim();
|
|
22
|
+
options.args = options.args?.trim();
|
|
23
|
+
options.jsonOutfile = options.jsonOutfile?.trim();
|
|
24
|
+
options.parallel =
|
|
25
|
+
typeof options.parallel === "string"
|
|
26
|
+
? options.parallel.trim()
|
|
27
|
+
: options.parallel;
|
|
28
|
+
if (positionalScript && options.script) {
|
|
29
|
+
logger.error(
|
|
30
|
+
"CLI syntax error: Cannot use both inline script positional and --script|-S option",
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const script = options.script || positionalScript;
|
|
36
|
+
if (postTerminatorArgs.length && options.args) {
|
|
37
|
+
logger.error(
|
|
38
|
+
"CLI syntax error: Cannot use both --args and inline script args after --",
|
|
39
|
+
);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const scriptArgs = postTerminatorArgs.length
|
|
44
|
+
? postTerminatorArgs
|
|
45
|
+
: options.args;
|
|
46
|
+
if (options.files !== undefined && (options.base || options.head)) {
|
|
47
|
+
logger.error(
|
|
48
|
+
"CLI syntax error: --files cannot be used with --base or --head",
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const affectedOptions =
|
|
54
|
+
options.files !== undefined
|
|
55
|
+
? {
|
|
56
|
+
diffSource: "fileList",
|
|
57
|
+
changedFiles: splitWhitespaceArg(options.files),
|
|
58
|
+
ignoreWorkspaceDependencies:
|
|
59
|
+
options.ignoreWorkspaceDeps || undefined,
|
|
60
|
+
ignoreExternalDependencies: options.ignoreExternalDeps || undefined,
|
|
61
|
+
}
|
|
62
|
+
: {
|
|
63
|
+
diffSource: "git",
|
|
64
|
+
ignoreWorkspaceDependencies:
|
|
65
|
+
options.ignoreWorkspaceDeps || undefined,
|
|
66
|
+
ignoreExternalDependencies: options.ignoreExternalDeps || undefined,
|
|
67
|
+
diffOptions: {
|
|
68
|
+
baseRef: options.base,
|
|
69
|
+
headRef: options.head,
|
|
70
|
+
ignoreUntracked: options.ignoreUntracked || undefined,
|
|
71
|
+
ignoreUnstaged: options.ignoreUnstaged || undefined,
|
|
72
|
+
ignoreStaged: options.ignoreStaged || undefined,
|
|
73
|
+
ignoreUncommitted: options.ignoreUncommitted || undefined,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
logger.debug(
|
|
77
|
+
`Command: Run ${options.inline ? "inline " : ""}script ${JSON.stringify(script)} across affected workspaces (${affectedOptions.diffSource})`,
|
|
78
|
+
);
|
|
79
|
+
logger.debug(`Options: ${JSON.stringify(options)}`);
|
|
80
|
+
await handleScriptRunFlow({
|
|
81
|
+
project,
|
|
82
|
+
context: {
|
|
83
|
+
outputWriters,
|
|
84
|
+
terminalWidth,
|
|
85
|
+
terminalHeight,
|
|
86
|
+
},
|
|
87
|
+
script,
|
|
88
|
+
scriptArgs,
|
|
89
|
+
cliOptions: options,
|
|
90
|
+
runner: (scriptOptions) =>
|
|
91
|
+
project.runAffectedWorkspaceScript({
|
|
92
|
+
affectedOptions,
|
|
93
|
+
scriptOptions,
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
export { runAffected };
|
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { expandHomePath } from "../../../internal/core/index.mjs";
|
|
4
1
|
import { logger } from "../../../internal/logger/index.mjs";
|
|
5
2
|
import {
|
|
6
3
|
handleProjectCommand,
|
|
7
|
-
|
|
4
|
+
splitWhitespaceArg,
|
|
8
5
|
} from "../commandHandlerUtils.mjs";
|
|
9
|
-
import {
|
|
10
|
-
getDefaultOutputStyle,
|
|
11
|
-
validateOutputStyle,
|
|
12
|
-
} from "./output/outputStyle.mjs";
|
|
13
|
-
import {
|
|
14
|
-
createScriptEvent,
|
|
15
|
-
createScriptEventTarget,
|
|
16
|
-
renderGroupedOutput,
|
|
17
|
-
} from "./output/renderGroupedOutput.mjs";
|
|
18
|
-
import { renderPlainOutput } from "./output/renderPlainOutput.mjs";
|
|
6
|
+
import { handleScriptRunFlow } from "./scriptRunFlow.mjs";
|
|
19
7
|
|
|
20
8
|
const runScript = handleProjectCommand(
|
|
21
9
|
"runScript",
|
|
@@ -62,200 +50,29 @@ const runScript = handleProjectCommand(
|
|
|
62
50
|
}
|
|
63
51
|
const workspacePatterns = positionalWorkspacePatterns?.length
|
|
64
52
|
? positionalWorkspacePatterns
|
|
65
|
-
:
|
|
53
|
+
: splitWhitespaceArg(options.workspacePatterns ?? "");
|
|
66
54
|
logger.debug(
|
|
67
55
|
`Command: Run ${options.inline ? "inline " : ""}script ${JSON.stringify(script)} for ${workspacePatterns.length ? "workspaces " + workspacePatterns.join(", ") : "all workspaces"}`,
|
|
68
56
|
);
|
|
69
57
|
logger.debug(`Options: ${JSON.stringify(options)}`);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
workspacePatterns: workspacePatterns.length
|
|
77
|
-
? workspacePatterns
|
|
78
|
-
: undefined,
|
|
79
|
-
script,
|
|
80
|
-
inline: options.inline
|
|
81
|
-
? options.inlineName || options.shell
|
|
82
|
-
? {
|
|
83
|
-
scriptName: options.inlineName,
|
|
84
|
-
shell: options.shell,
|
|
85
|
-
}
|
|
86
|
-
: true
|
|
87
|
-
: undefined,
|
|
88
|
-
args: scriptArgs,
|
|
89
|
-
dependencyOrder: options.depOrder,
|
|
90
|
-
ignoreDependencyFailure: options.ignoreDepFailure,
|
|
91
|
-
ignoreOutput: outputStyle === "none",
|
|
92
|
-
onScriptEvent: (event, { workspace, exitResult }) => {
|
|
93
|
-
setTimeout(() =>
|
|
94
|
-
// place at end of call stack so listeners in render func receive event
|
|
95
|
-
scriptEventTarget.dispatchEvent(
|
|
96
|
-
createScriptEvent[event]({
|
|
97
|
-
workspace,
|
|
98
|
-
exitResult,
|
|
99
|
-
}),
|
|
100
|
-
),
|
|
101
|
-
);
|
|
58
|
+
await handleScriptRunFlow({
|
|
59
|
+
project,
|
|
60
|
+
context: {
|
|
61
|
+
outputWriters,
|
|
62
|
+
terminalWidth,
|
|
63
|
+
terminalHeight,
|
|
102
64
|
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
max: options.parallel,
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
const scriptName = options.inline
|
|
116
|
-
? options.inlineName || "(inline)"
|
|
117
|
-
: script;
|
|
118
|
-
logger.debug(`Script name: ${scriptName}`);
|
|
119
|
-
const stripDisruptiveControls = workspaces.length > 1 || !!options.parallel;
|
|
120
|
-
logger.debug(`Strip disruptive controls: ${stripDisruptiveControls}`);
|
|
121
|
-
let groupedLines = "auto";
|
|
122
|
-
if (options.groupedLines) {
|
|
123
|
-
if (options.groupedLines === "all") {
|
|
124
|
-
groupedLines = "all";
|
|
125
|
-
} else if (options.groupedLines === "auto") {
|
|
126
|
-
groupedLines = "auto";
|
|
127
|
-
} else {
|
|
128
|
-
const parsedGroupedLines = parseInt(options.groupedLines);
|
|
129
|
-
if (parsedGroupedLines <= 0 || isNaN(parsedGroupedLines)) {
|
|
130
|
-
logger.error(
|
|
131
|
-
`Invalid max grouped lines value: ${options.groupedLines}. Must be a positive number or "all".`,
|
|
132
|
-
);
|
|
133
|
-
process.exit(1);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
groupedLines = parsedGroupedLines;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
logger.debug(`Effective grouped lines: ${JSON.stringify(groupedLines)}`);
|
|
140
|
-
if (!options.prefix) {
|
|
141
|
-
logger.warn(
|
|
142
|
-
"--no-prefix is deprecated and will be removed in a future version. Use --output-style=plain instead.",
|
|
143
|
-
);
|
|
144
|
-
if (!options.outputStyle) {
|
|
145
|
-
options.outputStyle = "plain";
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const outputStyleHandlers = {
|
|
149
|
-
grouped: () =>
|
|
150
|
-
renderGroupedOutput(
|
|
151
|
-
workspaces,
|
|
152
|
-
output,
|
|
153
|
-
summary,
|
|
154
|
-
scriptEventTarget,
|
|
155
|
-
groupedLines,
|
|
156
|
-
outputWriters,
|
|
157
|
-
terminalWidth,
|
|
158
|
-
terminalHeight,
|
|
159
|
-
),
|
|
160
|
-
prefixed: () =>
|
|
161
|
-
renderPlainOutput(output, outputWriters, {
|
|
162
|
-
prefix: true,
|
|
163
|
-
stripDisruptiveControls,
|
|
164
|
-
}),
|
|
165
|
-
plain: () =>
|
|
166
|
-
renderPlainOutput(output, outputWriters, {
|
|
167
|
-
prefix: false,
|
|
168
|
-
stripDisruptiveControls,
|
|
65
|
+
script,
|
|
66
|
+
scriptArgs,
|
|
67
|
+
cliOptions: options,
|
|
68
|
+
runner: (scriptOptions) =>
|
|
69
|
+
project.runScriptAcrossWorkspaces({
|
|
70
|
+
...scriptOptions,
|
|
71
|
+
workspacePatterns: workspacePatterns.length
|
|
72
|
+
? workspacePatterns
|
|
73
|
+
: undefined,
|
|
169
74
|
}),
|
|
170
|
-
|
|
171
|
-
// no-op
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
await outputStyleHandlers[outputStyle]();
|
|
175
|
-
const exitResults = await summary;
|
|
176
|
-
exitResults.scriptResults.forEach(
|
|
177
|
-
({ success, metadata: { workspace }, exitCode }) => {
|
|
178
|
-
const isSkipped = exitCode === -1;
|
|
179
|
-
if (isSkipped) {
|
|
180
|
-
logger.info(
|
|
181
|
-
`➖ ${workspace.name}: ${scriptName} (skipped due to dependency failure)`,
|
|
182
|
-
);
|
|
183
|
-
} else {
|
|
184
|
-
logger.info(
|
|
185
|
-
`${success ? "✅" : "❌"} ${workspace.name}: ${scriptName}${exitCode ? ` (exited with code ${exitCode})` : ""}`,
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
);
|
|
190
|
-
const s = exitResults.scriptResults.length === 1 ? "" : "s";
|
|
191
|
-
const skippedCount = exitResults.scriptResults.filter(
|
|
192
|
-
({ exitCode }) => exitCode === -1,
|
|
193
|
-
).length;
|
|
194
|
-
const skippedMessage = skippedCount ? ` (${skippedCount} skipped)` : "";
|
|
195
|
-
if (exitResults.failureCount) {
|
|
196
|
-
const message = `${exitResults.failureCount} of ${exitResults.scriptResults.length} script${s} failed${skippedMessage}`;
|
|
197
|
-
logger.info(message);
|
|
198
|
-
} else {
|
|
199
|
-
logger.info(
|
|
200
|
-
`${exitResults.scriptResults.length} script${s} ran successfully${skippedMessage}`,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
if (options.jsonOutfile) {
|
|
204
|
-
const fullOutputPath = path.resolve(
|
|
205
|
-
project.rootDirectory,
|
|
206
|
-
expandHomePath(options.jsonOutfile),
|
|
207
|
-
);
|
|
208
|
-
// Check if can make directory
|
|
209
|
-
const jsonOutputDir = path.dirname(fullOutputPath);
|
|
210
|
-
if (!fs.existsSync(jsonOutputDir)) {
|
|
211
|
-
try {
|
|
212
|
-
logger.debug(
|
|
213
|
-
`Creating JSON output file directory "${jsonOutputDir}"`,
|
|
214
|
-
);
|
|
215
|
-
fs.mkdirSync(jsonOutputDir, {
|
|
216
|
-
recursive: true,
|
|
217
|
-
});
|
|
218
|
-
} catch (error) {
|
|
219
|
-
logger.error(
|
|
220
|
-
`Failed to create JSON output file directory "${jsonOutputDir}": ${error}`,
|
|
221
|
-
);
|
|
222
|
-
process.exit(1);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
} else if (fs.statSync(jsonOutputDir).isFile()) {
|
|
226
|
-
logger.error(
|
|
227
|
-
`Given JSON output file directory "${jsonOutputDir}" is an existing file`,
|
|
228
|
-
);
|
|
229
|
-
process.exit(1);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
// Check if can make file
|
|
233
|
-
if (
|
|
234
|
-
fs.existsSync(fullOutputPath) &&
|
|
235
|
-
fs.statSync(fullOutputPath).isDirectory()
|
|
236
|
-
) {
|
|
237
|
-
logger.error(
|
|
238
|
-
`Given JSON output file path "${fullOutputPath}" is an existing directory`,
|
|
239
|
-
);
|
|
240
|
-
process.exit(1);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
try {
|
|
244
|
-
logger.debug(`Writing JSON output file "${fullOutputPath}"`);
|
|
245
|
-
fs.writeFileSync(fullOutputPath, JSON.stringify(exitResults, null, 2));
|
|
246
|
-
} catch (error) {
|
|
247
|
-
logger.error(
|
|
248
|
-
`Failed to write JSON output file "${fullOutputPath}": ${error}`,
|
|
249
|
-
);
|
|
250
|
-
process.exit(1);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
logger.info(`JSON output written to ${fullOutputPath}`);
|
|
254
|
-
}
|
|
255
|
-
if (exitResults.failureCount) {
|
|
256
|
-
process.exit(1);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
75
|
+
});
|
|
259
76
|
},
|
|
260
77
|
);
|
|
261
78
|
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
} from "../../../../internal/core/language/string/utf/visibleLength.mjs";
|
|
10
10
|
import { logger } from "../../../../internal/logger/index.mjs";
|
|
11
11
|
import { generatePlainOutputLines } from "./renderPlainOutput.mjs";
|
|
12
|
+
import {
|
|
13
|
+
initializeTuiTerminalState,
|
|
14
|
+
resetTuiTerminalState,
|
|
15
|
+
} from "./tuiTerminal.mjs";
|
|
12
16
|
|
|
13
17
|
class ScriptEventTarget extends TypedEventTarget {}
|
|
14
18
|
const createScriptEventTarget = () => new ScriptEventTarget();
|
|
@@ -86,8 +90,10 @@ const renderGroupedOutput = async (
|
|
|
86
90
|
}
|
|
87
91
|
isInitialized = true;
|
|
88
92
|
logger.debug("Initializing TUI state");
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
initializeTuiTerminalState({
|
|
94
|
+
stdout: outputWriters.stdout,
|
|
95
|
+
stdin: process.stdin,
|
|
96
|
+
});
|
|
91
97
|
};
|
|
92
98
|
let isReset = false;
|
|
93
99
|
const resetTuiTerminal = () => {
|
|
@@ -96,9 +102,10 @@ const renderGroupedOutput = async (
|
|
|
96
102
|
}
|
|
97
103
|
isReset = true;
|
|
98
104
|
logger.debug("Resetting TUI state");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
resetTuiTerminalState({
|
|
106
|
+
stdout: outputWriters.stdout,
|
|
107
|
+
stdin: process.stdin,
|
|
108
|
+
});
|
|
102
109
|
};
|
|
103
110
|
let previousHeight = 0;
|
|
104
111
|
let didFinalRender = false;
|
|
@@ -285,8 +292,16 @@ const renderGroupedOutput = async (
|
|
|
285
292
|
process.stdin.on("data", (data) => {
|
|
286
293
|
// Send to the entire process group (pid=0) so child processes also receive
|
|
287
294
|
// the signal — raw mode prevents the terminal from doing this automatically.
|
|
288
|
-
|
|
289
|
-
|
|
295
|
+
const signal =
|
|
296
|
+
data[0] === 0x03 ? "SIGINT" : data[0] === 0x1c ? "SIGQUIT" : null;
|
|
297
|
+
if (!signal) return;
|
|
298
|
+
// Restore the tty before fanning the signal: once SIGINT lands across
|
|
299
|
+
// the process group, child cleanup races with our own tcsetattr and
|
|
300
|
+
// setRawMode reliably returns EIO, leaving the user's terminal stuck
|
|
301
|
+
// in raw mode. Doing it here, synchronously, while we still own the
|
|
302
|
+
// tty cleanly, is the only place this can run before the race.
|
|
303
|
+
resetTuiTerminal();
|
|
304
|
+
process.kill(0, signal);
|
|
290
305
|
});
|
|
291
306
|
runOnExit((reason) => {
|
|
292
307
|
try {
|