bun-workspaces 0.2.0 → 0.3.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.
Files changed (30) hide show
  1. package/README.md +12 -0
  2. package/ignore-me-CHANGELOG_TEMPLATE.md +264 -0
  3. package/ignore-me-test-projects/no-fail/applications/applicationA/package.json +8 -0
  4. package/ignore-me-test-projects/no-fail/applications/applicationB/package.json +8 -0
  5. package/ignore-me-test-projects/no-fail/libraries/libraryA/package.json +8 -0
  6. package/ignore-me-test-projects/no-fail/libraries/libraryB/package.json +8 -0
  7. package/ignore-me-test-projects/no-fail/libraries/nested/libraryC/package.json +8 -0
  8. package/ignore-me-test-projects/no-fail/package.json +7 -0
  9. package/ignore-me-test-projects/one-fail/applications/applicationA/package.json +8 -0
  10. package/ignore-me-test-projects/one-fail/applications/applicationB/package.json +8 -0
  11. package/ignore-me-test-projects/one-fail/libraries/libraryA/package.json +8 -0
  12. package/ignore-me-test-projects/one-fail/libraries/libraryB/package.json +8 -0
  13. package/ignore-me-test-projects/one-fail/libraries/nested/libraryC/package.json +8 -0
  14. package/ignore-me-test-projects/one-fail/package.json +7 -0
  15. package/ignore-me-test-projects/two-fail/applications/applicationA/package.json +8 -0
  16. package/ignore-me-test-projects/two-fail/applications/applicationB/package.json +8 -0
  17. package/ignore-me-test-projects/two-fail/libraries/libraryA/package.json +8 -0
  18. package/ignore-me-test-projects/two-fail/libraries/libraryB/package.json +8 -0
  19. package/ignore-me-test-projects/two-fail/libraries/nested/libraryC/package.json +8 -0
  20. package/ignore-me-test-projects/two-fail/package.json +7 -0
  21. package/package.json +2 -1
  22. package/src/cli/cli.ts +1 -1
  23. package/src/cli/projectCommands.ts +207 -106
  24. package/src/internal/bunVersion.ts +4 -2
  25. package/src/internal/logger.ts +7 -3
  26. package/src/project/project.ts +1 -0
  27. package/src/workspaces/errors.ts +1 -0
  28. package/src/workspaces/findWorkspaces.ts +1 -3
  29. package/src/workspaces/packageJson.ts +15 -7
  30. package/tsconfig.json +1 -1
package/README.md CHANGED
@@ -23,6 +23,7 @@ alias bw="bunx bun-workspaces"
23
23
  # List all workspaces
24
24
  bw list-workspaces
25
25
  bw ls
26
+
26
27
  # List workspace names only
27
28
  bw list-workspaces --name-only
28
29
 
@@ -31,6 +32,7 @@ bw list-workspaces "my-*"
31
32
 
32
33
  # List all workspace scripts
33
34
  bw list-scripts
35
+
34
36
  # List script names only
35
37
  bw list-scripts --name-only
36
38
 
@@ -40,9 +42,16 @@ bw info my-workspace
40
42
 
41
43
  # Get info about a script
42
44
  bw script-info my-script
45
+
43
46
  # Only print list of workspace names that have the script
44
47
  bw script-info my-script --workspaces-only
45
48
 
49
+ # Get JSON output
50
+ bw list-workspaces --json --pretty # optionally pretty print JSON
51
+ bw list-scripts --json
52
+ bw workspace-info my-workspace --json
53
+ bw script-info my-script --json
54
+
46
55
  # Run a script for all
47
56
  # workspaces that have it
48
57
  # in their `scripts` field
@@ -63,6 +72,9 @@ bw run my-script --parallel
63
72
  # Append args to each script call
64
73
  bw run my-script --args "--my --args"
65
74
 
75
+ # Use the workspace name in args
76
+ bw run my-script --args "--my --args=<workspace>"
77
+
66
78
  # Help (--help can also be passed to any command)
67
79
  bw help
68
80
  bw --help
@@ -0,0 +1,264 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - v1.1 Brazilian Portuguese translation.
13
+ - v1.1 German Translation
14
+ - v1.1 Spanish translation.
15
+ - v1.1 Italian translation.
16
+ - v1.1 Polish translation.
17
+ - v1.1 Ukrainian translation.
18
+
19
+ ### Changed
20
+
21
+ - Use frontmatter title & description in each language version template
22
+ - Replace broken OpenGraph image with an appropriately-sized Keep a Changelog
23
+ image that will render properly (although in English for all languages)
24
+ - Fix OpenGraph title & description for all languages so the title and
25
+ description when links are shared are language-appropriate
26
+
27
+ ### Removed
28
+
29
+ - Trademark sign previously shown after the project description in version
30
+ 0.3.0
31
+
32
+ ## [1.1.1] - 2023-03-05
33
+
34
+ ### Added
35
+
36
+ - Arabic translation (#444).
37
+ - v1.1 French translation.
38
+ - v1.1 Dutch translation (#371).
39
+ - v1.1 Russian translation (#410).
40
+ - v1.1 Japanese translation (#363).
41
+ - v1.1 Norwegian Bokmål translation (#383).
42
+ - v1.1 "Inconsistent Changes" Turkish translation (#347).
43
+ - Default to most recent versions available for each languages.
44
+ - Display count of available translations (26 to date!).
45
+ - Centralize all links into `/data/links.json` so they can be updated easily.
46
+
47
+ ### Fixed
48
+
49
+ - Improve French translation (#377).
50
+ - Improve id-ID translation (#416).
51
+ - Improve Persian translation (#457).
52
+ - Improve Russian translation (#408).
53
+ - Improve Swedish title (#419).
54
+ - Improve zh-CN translation (#359).
55
+ - Improve French translation (#357).
56
+ - Improve zh-TW translation (#360, #355).
57
+ - Improve Spanish (es-ES) transltion (#362).
58
+ - Foldout menu in Dutch translation (#371).
59
+ - Missing periods at the end of each change (#451).
60
+ - Fix missing logo in 1.1 pages.
61
+ - Display notice when translation isn't for most recent version.
62
+ - Various broken links, page versions, and indentations.
63
+
64
+ ### Changed
65
+
66
+ - Upgrade dependencies: Ruby 3.2.1, Middleman, etc.
67
+
68
+ ### Removed
69
+
70
+ - Unused normalize.css file.
71
+ - Identical links assigned in each translation file.
72
+ - Duplicate index file for the english version.
73
+
74
+ ## [1.1.0] - 2019-02-15
75
+
76
+ ### Added
77
+
78
+ - Danish translation (#297).
79
+ - Georgian translation from (#337).
80
+ - Changelog inconsistency section in Bad Practices.
81
+
82
+ ### Fixed
83
+
84
+ - Italian translation (#332).
85
+ - Indonesian translation (#336).
86
+
87
+ ## [1.0.0] - 2017-06-20
88
+
89
+ ### Added
90
+
91
+ - New visual identity by [@tylerfortune8](https://github.com/tylerfortune8).
92
+ - Version navigation.
93
+ - Links to latest released version in previous versions.
94
+ - "Why keep a changelog?" section.
95
+ - "Who needs a changelog?" section.
96
+ - "How do I make a changelog?" section.
97
+ - "Frequently Asked Questions" section.
98
+ - New "Guiding Principles" sub-section to "How do I make a changelog?".
99
+ - Simplified and Traditional Chinese translations from [@tianshuo](https://github.com/tianshuo).
100
+ - German translation from [@mpbzh](https://github.com/mpbzh) & [@Art4](https://github.com/Art4).
101
+ - Italian translation from [@azkidenz](https://github.com/azkidenz).
102
+ - Swedish translation from [@magol](https://github.com/magol).
103
+ - Turkish translation from [@emreerkan](https://github.com/emreerkan).
104
+ - French translation from [@zapashcanon](https://github.com/zapashcanon).
105
+ - Brazilian Portuguese translation from [@Webysther](https://github.com/Webysther).
106
+ - Polish translation from [@amielucha](https://github.com/amielucha) & [@m-aciek](https://github.com/m-aciek).
107
+ - Russian translation from [@aishek](https://github.com/aishek).
108
+ - Czech translation from [@h4vry](https://github.com/h4vry).
109
+ - Slovak translation from [@jkostolansky](https://github.com/jkostolansky).
110
+ - Korean translation from [@pierceh89](https://github.com/pierceh89).
111
+ - Croatian translation from [@porx](https://github.com/porx).
112
+ - Persian translation from [@Hameds](https://github.com/Hameds).
113
+ - Ukrainian translation from [@osadchyi-s](https://github.com/osadchyi-s).
114
+
115
+ ### Changed
116
+
117
+ - Start using "changelog" over "change log" since it's the common usage.
118
+ - Start versioning based on the current English version at 0.3.0 to help
119
+ translation authors keep things up-to-date.
120
+ - Rewrite "What makes unicorns cry?" section.
121
+ - Rewrite "Ignoring Deprecations" sub-section to clarify the ideal
122
+ scenario.
123
+ - Improve "Commit log diffs" sub-section to further argument against
124
+ them.
125
+ - Merge "Why can’t people just use a git log diff?" with "Commit log
126
+ diffs".
127
+ - Fix typos in Simplified Chinese and Traditional Chinese translations.
128
+ - Fix typos in Brazilian Portuguese translation.
129
+ - Fix typos in Turkish translation.
130
+ - Fix typos in Czech translation.
131
+ - Fix typos in Swedish translation.
132
+ - Improve phrasing in French translation.
133
+ - Fix phrasing and spelling in German translation.
134
+
135
+ ### Removed
136
+
137
+ - Section about "changelog" vs "CHANGELOG".
138
+
139
+ ## [0.3.0] - 2015-12-03
140
+
141
+ ### Added
142
+
143
+ - RU translation from [@aishek](https://github.com/aishek).
144
+ - pt-BR translation from [@tallesl](https://github.com/tallesl).
145
+ - es-ES translation from [@ZeliosAriex](https://github.com/ZeliosAriex).
146
+
147
+ ## [0.2.0] - 2015-10-06
148
+
149
+ ### Changed
150
+
151
+ - Remove exclusionary mentions of "open source" since this project can
152
+ benefit both "open" and "closed" source projects equally.
153
+
154
+ ## [0.1.0] - 2015-10-06
155
+
156
+ ### Added
157
+
158
+ - Answer "Should you ever rewrite a change log?".
159
+
160
+ ### Changed
161
+
162
+ - Improve argument against commit logs.
163
+ - Start following [SemVer](https://semver.org) properly.
164
+
165
+ ## [0.0.8] - 2015-02-17
166
+
167
+ ### Changed
168
+
169
+ - Update year to match in every README example.
170
+ - Reluctantly stop making fun of Brits only, since most of the world
171
+ writes dates in a strange way.
172
+
173
+ ### Fixed
174
+
175
+ - Fix typos in recent README changes.
176
+ - Update outdated unreleased diff link.
177
+
178
+ ## [0.0.7] - 2015-02-16
179
+
180
+ ### Added
181
+
182
+ - Link, and make it obvious that date format is ISO 8601.
183
+
184
+ ### Changed
185
+
186
+ - Clarified the section on "Is there a standard change log format?".
187
+
188
+ ### Fixed
189
+
190
+ - Fix Markdown links to tag comparison URL with footnote-style links.
191
+
192
+ ## [0.0.6] - 2014-12-12
193
+
194
+ ### Added
195
+
196
+ - README section on "yanked" releases.
197
+
198
+ ## [0.0.5] - 2014-08-09
199
+
200
+ ### Added
201
+
202
+ - Markdown links to version tags on release headings.
203
+ - Unreleased section to gather unreleased changes and encourage note
204
+ keeping prior to releases.
205
+
206
+ ## [0.0.4] - 2014-08-09
207
+
208
+ ### Added
209
+
210
+ - Better explanation of the difference between the file ("CHANGELOG")
211
+ and its function "the change log".
212
+
213
+ ### Changed
214
+
215
+ - Refer to a "change log" instead of a "CHANGELOG" throughout the site
216
+ to differentiate between the file and the purpose of the file — the
217
+ logging of changes.
218
+
219
+ ### Removed
220
+
221
+ - Remove empty sections from CHANGELOG, they occupy too much space and
222
+ create too much noise in the file. People will have to assume that the
223
+ missing sections were intentionally left out because they contained no
224
+ notable changes.
225
+
226
+ ## [0.0.3] - 2014-08-09
227
+
228
+ ### Added
229
+
230
+ - "Why should I care?" section mentioning The Changelog podcast.
231
+
232
+ ## [0.0.2] - 2014-07-10
233
+
234
+ ### Added
235
+
236
+ - Explanation of the recommended reverse chronological release ordering.
237
+
238
+ ## [0.0.1] - 2014-05-31
239
+
240
+ ### Added
241
+
242
+ - This CHANGELOG file to hopefully serve as an evolving example of a
243
+ standardized open source project CHANGELOG.
244
+ - CNAME file to enable GitHub Pages custom domain.
245
+ - README now contains answers to common questions about CHANGELOGs.
246
+ - Good examples and basic guidelines, including proper date formatting.
247
+ - Counter-examples: "What makes unicorns cry?".
248
+
249
+ [unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.1.1...HEAD
250
+ [1.1.1]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.1.0...v1.1.1
251
+ [1.1.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v1.0.0...v1.1.0
252
+ [1.0.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.3.0...v1.0.0
253
+ [0.3.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.2.0...v0.3.0
254
+ [0.2.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.1.0...v0.2.0
255
+ [0.1.0]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.8...v0.1.0
256
+ [0.0.8]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.7...v0.0.8
257
+ [0.0.7]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.6...v0.0.7
258
+ [0.0.6]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.5...v0.0.6
259
+ [0.0.5]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.4...v0.0.5
260
+ [0.0.4]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.3...v0.0.4
261
+ [0.0.3]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.2...v0.0.3
262
+ [0.0.2]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.0.1...v0.0.2
263
+ [0.0.1]: https://github.com/olivierlacan/keep-a-changelog/releases/tag/v0.0.1
264
+ Wh
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "application-a",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "a-workspaces": "echo 'script for a workspaces'",
6
+ "application-a": "echo 'hello'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "application-b",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "b-workspaces": "echo 'script for b workspaces'",
6
+ "application-b": "echo 'script for application-b'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-a",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "a-workspaces": "echo 'script for a workspaces'",
6
+ "library-a": "echo 'script for library-a'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-b",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "b-workspaces": "echo 'script for b workspaces'",
6
+ "library-b": "echo 'script for library-b'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-c",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "c-workspaces": "echo 'script for c workspaces'",
6
+ "library-c": "echo 'script for library-c'"
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "test-root",
3
+ "workspaces": [
4
+ "applications/*",
5
+ "libraries/**/*"
6
+ ]
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "application-a",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "a-workspaces": "echo 'script for a workspaces'",
6
+ "application-a": "echo 'script for application-a'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "application-b",
3
+ "scripts": {
4
+ "all-workspaces": "commandthatshouldnotexist",
5
+ "b-workspaces": "echo 'script for b workspaces'",
6
+ "application-b": "echo 'script for application-b'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-a",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "a-workspaces": "echo 'script for a workspaces'",
6
+ "library-a": "echo 'script for library-a'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-b",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "b-workspaces": "echo 'script for b workspaces'",
6
+ "library-b": "echo 'script for library-b'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-c",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "c-workspaces": "echo 'script for c workspaces'",
6
+ "library-c": "echo 'script for library-c'"
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "test-root",
3
+ "workspaces": [
4
+ "applications/*",
5
+ "libraries/**/*"
6
+ ]
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "application-a",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "a-workspaces": "echo 'script for a workspaces'",
6
+ "application-a": "echo 'script for application-a'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "application-b",
3
+ "scripts": {
4
+ "all-workspaces": "commandthatshouldnotexist",
5
+ "b-workspaces": "echo 'script for b workspaces'",
6
+ "application-b": "echo 'script for application-b'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-a",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "a-workspaces": "echo 'script for a workspaces'",
6
+ "library-a": "echo 'script for library-a'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-b",
3
+ "scripts": {
4
+ "all-workspaces": "commandthatshouldnotexist",
5
+ "b-workspaces": "echo 'script for b workspaces'",
6
+ "library-b": "echo 'script for library-b'"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "library-c",
3
+ "scripts": {
4
+ "all-workspaces": "echo 'script for all workspaces'",
5
+ "c-workspaces": "echo 'script for c workspaces'",
6
+ "library-c": "echo 'script for library-c'"
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "test-root",
3
+ "workspaces": [
4
+ "applications/*",
5
+ "libraries/**/*"
6
+ ]
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-workspaces",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "main": "src/index.ts",
5
5
  "homepage": "https://github.com/ScottMorse/bun-workspaces#readme",
6
6
  "bin": {
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "commander": "^12.1.0",
35
+ "glob": "^11.0.0",
35
36
  "pino": "^9.5.0",
36
37
  "pino-pretty": "^13.0.0"
37
38
  },
package/src/cli/cli.ts CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  } from "../internal/bunVersion";
7
7
  import { logger } from "../internal/logger";
8
8
  import { initializeWithGlobalOptions } from "./globalOptions";
9
- import { defineProjectCommands } from "./projectCommands";
10
9
  import { OUTPUT_CONFIG } from "./output";
10
+ import { defineProjectCommands } from "./projectCommands";
11
11
 
12
12
  export interface RunCliOptions {
13
13
  argv?: string | string[];
@@ -1,5 +1,6 @@
1
1
  import { type Command } from "commander";
2
- import { logger } from "../internal/logger";
2
+ import { BunWorkspacesError } from "../internal/error";
3
+ import { logger, createLogger } from "../internal/logger";
3
4
  import type { Project } from "../project";
4
5
  import type { Workspace } from "../workspaces";
5
6
 
@@ -21,6 +22,9 @@ const createScriptInfoLines = (script: string, workspaces: Workspace[]) => [
21
22
  ...workspaces.map((workspace) => ` - ${workspace.name}`),
22
23
  ];
23
24
 
25
+ const createJsonLines = (data: unknown, options: { pretty: boolean }) =>
26
+ JSON.stringify(data, null, options.pretty ? 2 : undefined).split("\n");
27
+
24
28
  const listWorkspaces = ({
25
29
  program,
26
30
  project,
@@ -31,31 +35,49 @@ const listWorkspaces = ({
31
35
  .aliases(["ls", "list"])
32
36
  .description("List all workspaces")
33
37
  .option("--name-only", "Only show workspace names")
34
- .action((pattern, options) => {
35
- logger.debug("Command: List workspaces");
38
+ .option("--json", "Output as JSON")
39
+ .option("--pretty", "Pretty print JSON")
40
+ .action(
41
+ (
42
+ pattern,
43
+ options: { nameOnly: boolean; json: boolean; pretty: boolean },
44
+ ) => {
45
+ logger.debug(
46
+ `Command: List workspaces (options: ${JSON.stringify(options)})`,
47
+ );
36
48
 
37
- if (options.more) {
38
- logger.debug("Showing more metadata");
39
- }
49
+ const lines: string[] = [];
50
+
51
+ const workspaces = pattern
52
+ ? project.findWorkspacesByPattern(pattern)
53
+ : project.workspaces;
40
54
 
41
- const lines: string[] = [];
42
- (pattern
43
- ? project.findWorkspacesByPattern(pattern)
44
- : project.workspaces
45
- ).forEach((workspace) => {
46
- if (options.nameOnly) {
47
- lines.push(workspace.name);
55
+ if (options.json) {
56
+ lines.push(
57
+ ...createJsonLines(
58
+ options.nameOnly
59
+ ? workspaces.map(({ name }) => name)
60
+ : workspaces,
61
+ options,
62
+ ),
63
+ );
48
64
  } else {
49
- lines.push(...createWorkspaceInfoLines(workspace));
65
+ workspaces.forEach((workspace) => {
66
+ if (options.nameOnly) {
67
+ lines.push(workspace.name);
68
+ } else {
69
+ lines.push(...createWorkspaceInfoLines(workspace));
70
+ }
71
+ });
50
72
  }
51
- });
52
73
 
53
- if (!lines.length) {
54
- lines.push("No workspaces found");
55
- }
74
+ if (!lines.length) {
75
+ lines.push("No workspaces found");
76
+ }
56
77
 
57
- printLines(...lines);
58
- });
78
+ printLines(...lines);
79
+ },
80
+ );
59
81
  };
60
82
 
61
83
  const listScripts = ({
@@ -67,27 +89,50 @@ const listScripts = ({
67
89
  .command("list-scripts")
68
90
  .description("List all scripts available with their workspaces")
69
91
  .option("--name-only", "Only show script names")
70
- .action((options) => {
71
- logger.debug("Command: List scripts");
72
-
73
- const scripts = project.listScriptsWithWorkspaces();
74
- const lines: string[] = [];
75
- Object.values(scripts)
76
- .sort(({ name: nameA }, { name: nameB }) => nameA.localeCompare(nameB))
77
- .forEach(({ name, workspaces }) => {
78
- if (options.nameOnly) {
79
- lines.push(name);
80
- } else {
81
- lines.push(...createScriptInfoLines(name, workspaces));
82
- }
83
- });
92
+ .option("--json", "Output as JSON")
93
+ .option("--pretty", "Pretty print JSON")
94
+ .action(
95
+ (options: { nameOnly: boolean; json: boolean; pretty: boolean }) => {
96
+ logger.debug(
97
+ `Command: List scripts (options: ${JSON.stringify(options)})`,
98
+ );
84
99
 
85
- if (!lines.length) {
86
- lines.push("No scripts found");
87
- }
100
+ const scripts = project.listScriptsWithWorkspaces();
101
+ const lines: string[] = [];
102
+
103
+ if (options.json) {
104
+ lines.push(
105
+ ...createJsonLines(
106
+ options.nameOnly
107
+ ? Object.keys(scripts)
108
+ : Object.values(scripts).map(({ workspaces, ...rest }) => ({
109
+ ...rest,
110
+ workspaces: workspaces.map(({ name }) => name),
111
+ })),
112
+ options,
113
+ ),
114
+ );
115
+ } else {
116
+ Object.values(scripts)
117
+ .sort(({ name: nameA }, { name: nameB }) =>
118
+ nameA.localeCompare(nameB),
119
+ )
120
+ .forEach(({ name, workspaces }) => {
121
+ if (options.nameOnly) {
122
+ lines.push(name);
123
+ } else {
124
+ lines.push(...createScriptInfoLines(name, workspaces));
125
+ }
126
+ });
127
+
128
+ if (!lines.length) {
129
+ lines.push("No scripts found");
130
+ }
131
+ }
88
132
 
89
- printLines(...lines);
90
- });
133
+ printLines(...lines);
134
+ },
135
+ );
91
136
  };
92
137
 
93
138
  const workspaceInfo = ({
@@ -99,17 +144,29 @@ const workspaceInfo = ({
99
144
  .command("workspace-info <workspace>")
100
145
  .aliases(["info"])
101
146
  .description("Show information about a workspace")
102
- .action((workspaceName) => {
103
- logger.debug(`Command: Workspace info for ${workspaceName}`);
147
+ .option("--json", "Output as JSON")
148
+ .option("--pretty", "Pretty print JSON")
149
+ .action(
150
+ (workspaceName: string, options: { json: boolean; pretty: boolean }) => {
151
+ logger.debug(
152
+ `Command: Workspace info for ${workspaceName} (options: ${JSON.stringify(options)})`,
153
+ );
104
154
 
105
- const workspace = project.findWorkspaceByName(workspaceName);
106
- if (!workspace) {
107
- logger.error(`Workspace not found: ${JSON.stringify(workspaceName)}`);
108
- return;
109
- }
155
+ const workspace = project.findWorkspaceByName(workspaceName);
156
+ if (!workspace) {
157
+ logger.error(
158
+ `Workspace not found: (options: ${JSON.stringify(workspaceName)})`,
159
+ );
160
+ return;
161
+ }
110
162
 
111
- printLines(...createWorkspaceInfoLines(workspace));
112
- });
163
+ printLines(
164
+ ...(options.json
165
+ ? createJsonLines(workspace, options)
166
+ : createWorkspaceInfoLines(workspace)),
167
+ );
168
+ },
169
+ );
113
170
  };
114
171
 
115
172
  const scriptInfo = ({
@@ -121,32 +178,49 @@ const scriptInfo = ({
121
178
  .command("script-info <script>")
122
179
  .description("Show information about a script")
123
180
  .option("--workspaces-only", "Only show script's workspace names")
124
- .action((script, options) => {
125
- logger.debug(`Command: Script info for ${script}`);
181
+ .option("--json", "Output as JSON")
182
+ .option("--pretty", "Pretty print JSON")
183
+ .action(
184
+ (
185
+ script,
186
+ options: { workspacesOnly: boolean; json: boolean; pretty: boolean },
187
+ ) => {
188
+ logger.debug(
189
+ `Command: Script info for ${script} (options: ${JSON.stringify(options)})`,
190
+ );
126
191
 
127
- const scripts = project.listScriptsWithWorkspaces();
128
- const scriptMetadata = scripts[script];
129
- if (!scriptMetadata) {
192
+ const scripts = project.listScriptsWithWorkspaces();
193
+ const scriptMetadata = scripts[script];
194
+ if (!scriptMetadata) {
195
+ printLines(
196
+ `Script not found: ${JSON.stringify(
197
+ script,
198
+ )} (available: ${Object.keys(scripts).join(", ")})`,
199
+ );
200
+ return;
201
+ }
130
202
  printLines(
131
- `Script not found: ${JSON.stringify(
132
- script,
133
- )} (available: ${Object.keys(scripts).join(", ")})`,
203
+ ...(options.json
204
+ ? createJsonLines(
205
+ options.workspacesOnly
206
+ ? scriptMetadata.workspaces.map(({ name }) => name)
207
+ : {
208
+ name: scriptMetadata.name,
209
+ workspaces: scriptMetadata.workspaces.map(
210
+ ({ name }) => name,
211
+ ),
212
+ },
213
+ options,
214
+ )
215
+ : options.workspacesOnly
216
+ ? scriptMetadata.workspaces.map(({ name }) => name)
217
+ : createScriptInfoLines(script, scriptMetadata.workspaces)),
134
218
  );
135
- return;
136
- }
137
- printLines(
138
- ...(options.workspacesOnly
139
- ? scriptMetadata.workspaces.map(({ name }) => name)
140
- : createScriptInfoLines(script, scriptMetadata.workspaces)),
141
- );
142
- });
219
+ },
220
+ );
143
221
  };
144
222
 
145
- const runScript = ({
146
- program,
147
- project,
148
- printLines,
149
- }: ProjectCommandsContext) => {
223
+ const runScript = ({ program, project }: ProjectCommandsContext) => {
150
224
  program
151
225
  .command("run <script> [workspaces...]")
152
226
  .description("Run a script in all workspaces")
@@ -201,9 +275,11 @@ const runScript = ({
201
275
  scriptName,
202
276
  workspace,
203
277
  }: (typeof scriptCommands)[number]) => {
278
+ const commandLogger = createLogger(`${workspace.name}:${scriptName}`);
279
+
204
280
  const splitCommand = command.command.split(/\s+/g);
205
281
 
206
- logger.debug(
282
+ commandLogger.debug(
207
283
  `Running script ${scriptName} in workspace ${workspace.name} (cwd: ${
208
284
  command.cwd
209
285
  }): ${splitCommand.join(" ")}`,
@@ -211,21 +287,25 @@ const runScript = ({
211
287
 
212
288
  const silent = logger.level === "silent";
213
289
 
214
- if (!silent) {
215
- printLines(
216
- `Running script ${JSON.stringify(
217
- scriptName,
218
- )} in workspace ${JSON.stringify(workspace.name)}`,
219
- );
220
- }
221
-
222
290
  const proc = Bun.spawn(command.command.split(/\s+/g), {
223
291
  cwd: command.cwd,
224
292
  env: process.env,
225
- stdout: silent ? "ignore" : "inherit",
226
- stderr: silent ? "ignore" : "inherit",
293
+ stdout: silent ? "ignore" : "pipe",
294
+ stderr: silent ? "ignore" : "pipe",
227
295
  });
228
296
 
297
+ if (proc.stdout) {
298
+ for await (const chunk of proc.stdout) {
299
+ commandLogger.info(new TextDecoder().decode(chunk).trim());
300
+ }
301
+ }
302
+
303
+ if (proc.stderr) {
304
+ for await (const chunk of proc.stderr) {
305
+ commandLogger.error(new TextDecoder().decode(chunk).trim());
306
+ }
307
+ }
308
+
229
309
  await proc.exited;
230
310
 
231
311
  return {
@@ -233,30 +313,20 @@ const runScript = ({
233
313
  workspace,
234
314
  command,
235
315
  success: proc.exitCode === 0,
316
+ error:
317
+ proc.exitCode === 0
318
+ ? null
319
+ : new BunWorkspacesError(
320
+ `Script exited with code ${proc.exitCode}`,
321
+ ),
236
322
  };
237
323
  };
238
324
 
239
- const handleError = (error: unknown, workspace: string) => {
240
- logger.error(error);
241
- program.error(
242
- `Script failed in ${workspace} (error: ${JSON.stringify((error as Error).message ?? error)})`,
243
- );
244
- };
245
-
246
- const handleResult = ({
247
- scriptName,
248
- workspace,
249
- success,
250
- }: (typeof scriptCommands)[number] & { success: boolean }) => {
251
- logger.info(
252
- `${success ? "✅" : "❌"} ${workspace.name}: ${scriptName}`,
253
- );
254
- if (!success) {
255
- program.error(
256
- `Script ${scriptName} failed in workspace ${workspace.name}`,
257
- );
258
- }
259
- };
325
+ const results = [] as {
326
+ success: boolean;
327
+ workspaceName: string;
328
+ error: Error | null;
329
+ }[];
260
330
 
261
331
  if (options.parallel) {
262
332
  let i = 0;
@@ -264,9 +334,17 @@ const runScript = ({
264
334
  scriptCommands.map(runCommand),
265
335
  )) {
266
336
  if (result.status === "rejected") {
267
- handleError(result.reason, workspaces[i]);
337
+ results.push({
338
+ success: false,
339
+ workspaceName: workspaces[i],
340
+ error: result.reason,
341
+ });
268
342
  } else {
269
- handleResult(result.value);
343
+ results.push({
344
+ success: result.value.success,
345
+ workspaceName: workspaces[i],
346
+ error: result.value.error,
347
+ });
270
348
  }
271
349
  i++;
272
350
  }
@@ -275,12 +353,35 @@ const runScript = ({
275
353
  for (const command of scriptCommands) {
276
354
  try {
277
355
  const result = await runCommand(command);
278
- handleResult(result);
356
+ results.push({
357
+ success: result.success,
358
+ workspaceName: workspaces[i],
359
+ error: result.error,
360
+ });
279
361
  } catch (error) {
280
- handleError(error, workspaces[i]);
362
+ results.push({
363
+ success: false,
364
+ workspaceName: workspaces[i],
365
+ error: error as Error,
366
+ });
281
367
  }
368
+ i++;
282
369
  }
283
- i++;
370
+ }
371
+
372
+ let failCount = 0;
373
+ results.forEach(({ success, workspaceName }) => {
374
+ if (!success) failCount++;
375
+ logger.info(`${success ? "✅" : "❌"} ${workspaceName}: ${script}`);
376
+ });
377
+
378
+ const s = results.length === 1 ? "" : "s";
379
+ if (failCount) {
380
+ const message = `${failCount} of ${results.length} script${s} failed`;
381
+ logger.info(message);
382
+ process.exit(1);
383
+ } else {
384
+ logger.info(`${results.length} script${s} ran successfully`);
284
385
  }
285
386
  });
286
387
  };
@@ -8,12 +8,14 @@ export const BUILD_BUN_VERSION = rootPackageJson.custom.bunVersion.build;
8
8
  export const getRequiredBunVersion = (build?: boolean) =>
9
9
  build ? BUILD_BUN_VERSION : LIBRARY_CONSUMER_BUN_VERSION;
10
10
 
11
+ const _Bun = typeof Bun === "undefined" ? ({} as typeof Bun) : Bun;
12
+
11
13
  /**
12
14
  * Validates that the provided version satisfies the required Bun version
13
15
  * specified in the root `package.json`.
14
16
  */
15
17
  export const validateBunVersion = (version: string, build?: boolean) =>
16
- Bun.semver.satisfies(version, getRequiredBunVersion(build));
18
+ _Bun ? _Bun.semver.satisfies(version, getRequiredBunVersion(build)) : true;
17
19
 
18
20
  /**
19
21
  *
@@ -21,4 +23,4 @@ export const validateBunVersion = (version: string, build?: boolean) =>
21
23
  * required Bun version specified in the root `package.json`.
22
24
  */
23
25
  export const validateCurrentBunVersion = (build?: boolean) =>
24
- validateBunVersion(Bun.version, build);
26
+ validateBunVersion(_Bun?.version, build);
@@ -1,7 +1,7 @@
1
- import createLogger from "pino";
1
+ import createPinoLogger from "pino";
2
2
 
3
- export const logger = createLogger({
4
- msgPrefix: "[bun-workspaces] ",
3
+ export const logger = createPinoLogger({
4
+ msgPrefix: `[bw] `,
5
5
  level:
6
6
  process.env.NODE_ENV === "test"
7
7
  ? "silent"
@@ -12,6 +12,10 @@ export const logger = createLogger({
12
12
  target: "pino-pretty",
13
13
  options: {
14
14
  color: true,
15
+ ignore: "hostname,pid,time",
15
16
  },
16
17
  },
17
18
  });
19
+
20
+ export const createLogger = (prefixContent: string) =>
21
+ logger.child({}, { msgPrefix: `[${prefixContent}] ` });
@@ -70,6 +70,7 @@ class _Project implements Project {
70
70
  );
71
71
  });
72
72
  return Array.from(scripts)
73
+ .sort((a, b) => a.localeCompare(b))
73
74
  .map((name) => ({
74
75
  name,
75
76
  workspaces: this.listWorkspacesWithScript(name),
@@ -4,6 +4,7 @@ export const ERRORS = defineErrors(
4
4
  "PackageNotFound",
5
5
  "InvalidPackageJson",
6
6
  "DuplicateWorkspaceName",
7
+ "InvalidWorkspaceName",
7
8
  "NoWorkspaceName",
8
9
  "InvalidScripts",
9
10
  "InvalidWorkspaces",
@@ -1,6 +1,5 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { Glob } from "bun";
4
3
  import { logger } from "../internal/logger";
5
4
  import { ERRORS } from "./errors";
6
5
  import {
@@ -52,8 +51,7 @@ export const findWorkspaces = ({
52
51
  for (const pattern of workspaceGlobs) {
53
52
  if (!validatePattern(pattern)) continue;
54
53
 
55
- const glob = new Glob(pattern);
56
- for (const item of scanWorkspaceGlob(glob, rootDir)) {
54
+ for (const item of scanWorkspaceGlob(pattern, rootDir)) {
57
55
  const packageJsonPath = resolvePackageJsonPath(item);
58
56
  if (packageJsonPath) {
59
57
  const packageJsonContent = resolvePackageJsonContent(
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { Glob } from "bun";
3
+ import { Glob } from "glob";
4
4
  import { logger } from "../internal/logger";
5
5
  import { ERRORS } from "./errors";
6
6
 
@@ -22,12 +22,8 @@ export type ResolvedPackageJsonContent = {
22
22
 
23
23
  type UnknownPackageJson = Record<string, unknown>;
24
24
 
25
- export const scanWorkspaceGlob = (glob: Glob, rootDir: string) =>
26
- glob.scanSync({
27
- cwd: rootDir,
28
- onlyFiles: false,
29
- absolute: true,
30
- });
25
+ export const scanWorkspaceGlob = (pattern: string, rootDir: string) =>
26
+ new Glob(pattern, { absolute: true, cwd: rootDir }).iterateSync();
31
27
 
32
28
  const validateJsonRoot = (json: UnknownPackageJson) => {
33
29
  if (!json || typeof json !== "object" || Array.isArray(json)) {
@@ -46,6 +42,18 @@ const validateName = (json: UnknownPackageJson) => {
46
42
  );
47
43
  }
48
44
 
45
+ if (!json.name.trim()) {
46
+ throw new ERRORS.NoWorkspaceName(
47
+ `Expected package.json to have a non-empty "name" field`,
48
+ );
49
+ }
50
+
51
+ if (json.name.includes("*")) {
52
+ throw new ERRORS.InvalidWorkspaceName(
53
+ `Package name cannot contain the character '*' (workspace: "${json.name}")`,
54
+ );
55
+ }
56
+
49
57
  return json.name;
50
58
  };
51
59
 
package/tsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  // Enable latest features
4
- "lib": ["ESNext", "DOM"],
4
+ "lib": ["ESNext", "DOM", "DOM.AsyncIterable", "DOM.Iterable"],
5
5
  "target": "ESNext",
6
6
  "module": "ESNext",
7
7
  "moduleDetection": "force",