bun-workspaces 1.1.1 → 1.2.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 +31 -12
- package/package.json +3 -2
- package/src/cli/commands/commandsConfig.d.ts +2 -2
- package/src/cli/commands/runScript/handleRunScript.mjs +9 -6
- package/src/cli/commands/runScript/output/outputStyle.d.ts +1 -0
- package/src/cli/commands/runScript/output/outputStyle.mjs +1 -1
- package/src/project/implementations/fileSystemProject.d.ts +4 -4
- package/src/project/implementations/fileSystemProject.mjs +76 -20
- package/src/workspaces/findWorkspaces.mjs +3 -2
package/README.md
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
### [**See Full Documentation Here**: _https://bunworkspaces.com_](https://bunworkspaces.com)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Big Recent Updates!**
|
|
10
|
+
|
|
11
|
+
- Version 1 is here after the initial alpha! 🍔🍔👁️🍔🍔
|
|
12
|
+
- You can demo the CLI [directly in the browser](https://bunworkspaces.com/web-cli)
|
|
13
|
+
- There's now [an official blog](https://bunworkspaces.com/blog/bun-workspaces-v1) to cover noteworthy releases and more!
|
|
14
|
+
<hr/>
|
|
15
|
+
|
|
16
|
+
This is a CLI and TypeScript API to enhance your monorepo development with Bun's [native workspaces](https://bun.sh/docs/install/workspaces) feature for nested JavaScript/TypeScript packages.
|
|
10
17
|
|
|
11
18
|
- Works right away, with no boilerplate required 🍔🍴
|
|
12
19
|
- Get metadata about your monorepo 🤖
|
|
@@ -65,9 +72,6 @@ bw run lint my-workspace # Run for a single workspace
|
|
|
65
72
|
bw run lint my-workspace-a my-workspace-b # Run for multiple workspaces
|
|
66
73
|
bw run lint my-alias-a my-alias-b # Run by alias (set by optional config)
|
|
67
74
|
|
|
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
75
|
# A workspace's script will wait until any workspaces it depends on have completed
|
|
72
76
|
# Similar to Bun's --filter behavior
|
|
73
77
|
bw run lint --dep-order
|
|
@@ -75,6 +79,9 @@ bw run lint --dep-order
|
|
|
75
79
|
# Continue running scripts even if a dependency fails
|
|
76
80
|
bw run lint --dep-order --ignore-dep-failure
|
|
77
81
|
|
|
82
|
+
bw run lint "my-workspace-*" # Run for matching workspace names
|
|
83
|
+
bw run lint "alias:my-alias-pattern-*" "path:my-glob/**/*" # Use matching specifiers
|
|
84
|
+
|
|
78
85
|
bw run lint --args="--my-appended-args" # Add args to each script call
|
|
79
86
|
bw run lint --args="--my-arg=<workspaceName>" # Use the workspace name in args
|
|
80
87
|
|
|
@@ -140,14 +147,22 @@ const runSingleScript = async () => {
|
|
|
140
147
|
const { output, exit } = project.runWorkspaceScript({
|
|
141
148
|
workspaceNameOrAlias: "my-workspace",
|
|
142
149
|
script: "my-script",
|
|
143
|
-
|
|
150
|
+
|
|
151
|
+
// Optional. Arguments to add to the command
|
|
152
|
+
// Can be a string or an array of strings
|
|
153
|
+
// If string, the argv will be parsed POSIX-style
|
|
154
|
+
args: ["--my", "--appended", "--args"],
|
|
155
|
+
|
|
156
|
+
// Optional. Whether to ignore all output from the script.
|
|
157
|
+
// This saves memory when you don't need script output.
|
|
158
|
+
ignoreOutput: false,
|
|
144
159
|
});
|
|
145
160
|
|
|
146
161
|
// Get a stream of the script subprocess's output
|
|
147
162
|
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); //
|
|
163
|
+
// console.log(chunk); // The output chunk's content (string)
|
|
164
|
+
// console.log(metadata.streamName); // The output stream, "stdout" or "stderr"
|
|
165
|
+
// console.log(metadata.workspace); // The target Workspace
|
|
151
166
|
}
|
|
152
167
|
|
|
153
168
|
// Get data about the script execution after it exits
|
|
@@ -173,8 +188,8 @@ const runManyScripts = async () => {
|
|
|
173
188
|
// Required. The package.json "scripts" field name to run
|
|
174
189
|
script: "my-script",
|
|
175
190
|
|
|
176
|
-
// Optional. Arguments to add to the command
|
|
177
|
-
args: "--my --appended --args",
|
|
191
|
+
// Optional. Arguments to add to the command (same as for runWorkspaceScript)
|
|
192
|
+
args: ["--my", "--appended", "--args"],
|
|
178
193
|
|
|
179
194
|
// Optional. Whether to run the scripts in parallel (default: true)
|
|
180
195
|
parallel: true,
|
|
@@ -187,6 +202,10 @@ const runManyScripts = async () => {
|
|
|
187
202
|
// continue running scripts even if a dependency fails
|
|
188
203
|
ignoreDependencyFailure: false,
|
|
189
204
|
|
|
205
|
+
// Optional. Whether to ignore all output from the scripts.
|
|
206
|
+
// This saves memory when you don't need script output.
|
|
207
|
+
ignoreOutput: false,
|
|
208
|
+
|
|
190
209
|
// Optional, callback when script starts, skips, or exits
|
|
191
210
|
onScriptEvent: (event, { workspace, exitResult }) => {
|
|
192
211
|
// event: "start", "skip", "exit"
|
|
@@ -195,9 +214,9 @@ const runManyScripts = async () => {
|
|
|
195
214
|
|
|
196
215
|
// Get a stream of script output
|
|
197
216
|
for await (const { chunk, metadata } of output.text()) {
|
|
198
|
-
// console.log(chunk); // the content (string)
|
|
217
|
+
// console.log(chunk); // the output chunk's content (string)
|
|
199
218
|
// console.log(metadata.streamName); // "stdout" or "stderr"
|
|
200
|
-
// console.log(metadata.workspace); // the
|
|
219
|
+
// console.log(metadata.workspace); // the Workspace that the output came from
|
|
201
220
|
}
|
|
202
221
|
|
|
203
222
|
// 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.
|
|
3
|
+
"version": "1.2.0",
|
|
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
|
}
|
|
@@ -148,7 +148,7 @@ export declare const CLI_COMMANDS_CONFIG: {
|
|
|
148
148
|
readonly outputStyle: {
|
|
149
149
|
readonly flags: ["-o", "--output-style <style>"];
|
|
150
150
|
readonly description: "The output style to use";
|
|
151
|
-
readonly values: ["grouped", "prefixed", "plain"];
|
|
151
|
+
readonly values: ["grouped", "prefixed", "plain", "none"];
|
|
152
152
|
};
|
|
153
153
|
readonly groupedLines: {
|
|
154
154
|
readonly flags: ["-L", "--grouped-lines <count>"];
|
|
@@ -309,7 +309,7 @@ export declare const getCliCommandConfig: (commandName: CliCommandName) =>
|
|
|
309
309
|
readonly outputStyle: {
|
|
310
310
|
readonly flags: ["-o", "--output-style <style>"];
|
|
311
311
|
readonly description: "The output style to use";
|
|
312
|
-
readonly values: ["grouped", "prefixed", "plain"];
|
|
312
|
+
readonly values: ["grouped", "prefixed", "plain", "none"];
|
|
313
313
|
};
|
|
314
314
|
readonly groupedLines: {
|
|
315
315
|
readonly flags: ["-L", "--grouped-lines <count>"];
|
|
@@ -58,7 +58,7 @@ const runScript = handleProjectCommand(
|
|
|
58
58
|
process.exit(1);
|
|
59
59
|
}
|
|
60
60
|
const scriptArgs = postTerminatorArgs.length
|
|
61
|
-
? postTerminatorArgs
|
|
61
|
+
? postTerminatorArgs
|
|
62
62
|
: options.args;
|
|
63
63
|
if (positionalWorkspacePatterns.length && options.workspacePatterns) {
|
|
64
64
|
logger.error(
|
|
@@ -73,6 +73,10 @@ const runScript = handleProjectCommand(
|
|
|
73
73
|
`Command: Run ${options.inline ? "inline " : ""}script ${JSON.stringify(script)} for ${workspacePatterns.length ? "workspaces " + workspacePatterns.join(", ") : "all workspaces"}`,
|
|
74
74
|
);
|
|
75
75
|
logger.debug(`Options: ${JSON.stringify(options)}`);
|
|
76
|
+
const outputStyle = options.outputStyle
|
|
77
|
+
? validateOutputStyle(options.outputStyle)
|
|
78
|
+
: getDefaultOutputStyle();
|
|
79
|
+
logger.debug(`Effective output style: ${outputStyle}`);
|
|
76
80
|
const scriptEventTarget = createScriptEventTarget();
|
|
77
81
|
const { output, summary, workspaces } = project.runScriptAcrossWorkspaces({
|
|
78
82
|
workspacePatterns: workspacePatterns.length
|
|
@@ -90,7 +94,7 @@ const runScript = handleProjectCommand(
|
|
|
90
94
|
args: scriptArgs,
|
|
91
95
|
dependencyOrder: options.depOrder,
|
|
92
96
|
ignoreDependencyFailure: options.ignoreDepFailure,
|
|
93
|
-
ignoreOutput:
|
|
97
|
+
ignoreOutput: outputStyle === "none",
|
|
94
98
|
onScriptEvent: (event, { workspace, exitResult }) => {
|
|
95
99
|
setTimeout(() =>
|
|
96
100
|
// place at end of call stack so listeners in render func receive event
|
|
@@ -168,11 +172,10 @@ const runScript = handleProjectCommand(
|
|
|
168
172
|
prefix: false,
|
|
169
173
|
stripDisruptiveControls,
|
|
170
174
|
}),
|
|
175
|
+
none: async () => {
|
|
176
|
+
// no-op
|
|
177
|
+
},
|
|
171
178
|
};
|
|
172
|
-
const outputStyle = options.outputStyle
|
|
173
|
-
? validateOutputStyle(options.outputStyle)
|
|
174
|
-
: getDefaultOutputStyle();
|
|
175
|
-
logger.debug(`Effective output style: ${outputStyle}`);
|
|
176
179
|
await outputStyleHandlers[outputStyle]();
|
|
177
180
|
const exitResults = await summary;
|
|
178
181
|
exitResults.scriptResults.forEach(
|
|
@@ -3,7 +3,7 @@ import { IS_TTY } from "../../../../internal/core/runtime/terminal.mjs"; // CONC
|
|
|
3
3
|
// CONCATENATED MODULE: external "../../../../internal/core/runtime/terminal.mjs"
|
|
4
4
|
// CONCATENATED MODULE: ./src/cli/commands/runScript/output/outputStyle.ts
|
|
5
5
|
|
|
6
|
-
const OUTPUT_STYLE_VALUES = ["grouped", "prefixed", "plain"];
|
|
6
|
+
const OUTPUT_STYLE_VALUES = ["grouped", "prefixed", "plain", "none"];
|
|
7
7
|
const getDefaultOutputStyle = () => (IS_TTY ? "grouped" : "prefixed");
|
|
8
8
|
const validateOutputStyle = (style) => {
|
|
9
9
|
if (!OUTPUT_STYLE_VALUES.includes(style)) {
|
|
@@ -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,11 +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,
|
|
7
10
|
expandHomePath,
|
|
8
11
|
isPlainObject,
|
|
12
|
+
validateJSArray,
|
|
9
13
|
validateJSTypes,
|
|
10
14
|
} from "../../internal/core/index.mjs";
|
|
11
15
|
import { logger } from "../../internal/logger/index.mjs";
|
|
@@ -27,6 +31,7 @@ import {
|
|
|
27
31
|
resolveWorkspacePath,
|
|
28
32
|
} from "./projectBase.mjs"; // CONCATENATED MODULE: external "fs"
|
|
29
33
|
// CONCATENATED MODULE: external "path"
|
|
34
|
+
// CONCATENATED MODULE: external "shell-quote/"
|
|
30
35
|
// CONCATENATED MODULE: external "../../config/index.mjs"
|
|
31
36
|
// CONCATENATED MODULE: external "../../config/userEnvVars/index.mjs"
|
|
32
37
|
// CONCATENATED MODULE: external "../../internal/core/index.mjs"
|
|
@@ -40,6 +45,41 @@ import {
|
|
|
40
45
|
// CONCATENATED MODULE: external "./projectBase.mjs"
|
|
41
46
|
// CONCATENATED MODULE: ./src/project/implementations/fileSystemProject.ts
|
|
42
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
|
+
};
|
|
43
83
|
class _FileSystemProject extends ProjectBase {
|
|
44
84
|
rootDirectory;
|
|
45
85
|
workspaces;
|
|
@@ -122,11 +162,6 @@ class _FileSystemProject extends ProjectBase {
|
|
|
122
162
|
typeofName: ["boolean", "object"],
|
|
123
163
|
optional: true,
|
|
124
164
|
},
|
|
125
|
-
"args option": {
|
|
126
|
-
value: options.args,
|
|
127
|
-
typeofName: "string",
|
|
128
|
-
optional: true,
|
|
129
|
-
},
|
|
130
165
|
"ignoreOutput option": {
|
|
131
166
|
value: options.ignoreOutput,
|
|
132
167
|
typeofName: "boolean",
|
|
@@ -137,6 +172,23 @@ class _FileSystemProject extends ProjectBase {
|
|
|
137
172
|
throw: true,
|
|
138
173
|
},
|
|
139
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
|
+
}
|
|
140
192
|
if (isPlainObject(options.inline)) {
|
|
141
193
|
validateJSTypes(
|
|
142
194
|
{
|
|
@@ -185,11 +237,7 @@ class _FileSystemProject extends ProjectBase {
|
|
|
185
237
|
workspaceName: workspace.name,
|
|
186
238
|
scriptName: options.inline ? inlineScriptName : options.script,
|
|
187
239
|
};
|
|
188
|
-
const args =
|
|
189
|
-
options.args ?? "",
|
|
190
|
-
scriptRuntimeMetadata,
|
|
191
|
-
shell,
|
|
192
|
-
);
|
|
240
|
+
const args = serializeArgs(options.args, scriptRuntimeMetadata, shell);
|
|
193
241
|
const script = options.inline
|
|
194
242
|
? interpolateScriptRuntimeMetadata(
|
|
195
243
|
options.script,
|
|
@@ -243,11 +291,6 @@ class _FileSystemProject extends ProjectBase {
|
|
|
243
291
|
typeofName: ["boolean", "object"],
|
|
244
292
|
optional: true,
|
|
245
293
|
},
|
|
246
|
-
"args option": {
|
|
247
|
-
value: options.args,
|
|
248
|
-
typeofName: "string",
|
|
249
|
-
optional: true,
|
|
250
|
-
},
|
|
251
294
|
"parallel option": {
|
|
252
295
|
value: options.parallel,
|
|
253
296
|
typeofName: ["boolean", "object"],
|
|
@@ -297,6 +340,23 @@ class _FileSystemProject extends ProjectBase {
|
|
|
297
340
|
},
|
|
298
341
|
);
|
|
299
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
|
+
}
|
|
300
360
|
if (isPlainObject(options.parallel)) {
|
|
301
361
|
validateJSTypes(
|
|
302
362
|
{
|
|
@@ -384,11 +444,7 @@ class _FileSystemProject extends ProjectBase {
|
|
|
384
444
|
workspaceName: workspace.name,
|
|
385
445
|
scriptName: options.inline ? inlineScriptName : options.script,
|
|
386
446
|
};
|
|
387
|
-
const args =
|
|
388
|
-
options.args ?? "",
|
|
389
|
-
scriptRuntimeMetadata,
|
|
390
|
-
shell,
|
|
391
|
-
);
|
|
447
|
+
const args = serializeArgs(options.args, scriptRuntimeMetadata, shell);
|
|
392
448
|
const script = options.inline
|
|
393
449
|
? interpolateScriptRuntimeMetadata(
|
|
394
450
|
options.script,
|
|
@@ -125,8 +125,9 @@ const findWorkspaces = ({
|
|
|
125
125
|
path.dirname(packageJsonPath),
|
|
126
126
|
);
|
|
127
127
|
const matchPattern =
|
|
128
|
-
workspaceGlobs.find((glob) =>
|
|
129
|
-
|
|
128
|
+
workspaceGlobs.find((glob) =>
|
|
129
|
+
new bun.Glob(glob.replace(/\/+$/, "")).match(relativePath),
|
|
130
|
+
) ?? "";
|
|
130
131
|
const isRootWorkspace = workspacePath === rootDirectory;
|
|
131
132
|
if (!matchPattern && !isRootWorkspace) {
|
|
132
133
|
logger.debug(`No match pattern found for ${relativePath}`);
|