bun-workspaces 0.1.2 → 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 (33) hide show
  1. package/.vscode/extensions.json +12 -0
  2. package/.vscode/settings.json +23 -0
  3. package/README.md +23 -5
  4. package/ignore-me-CHANGELOG_TEMPLATE.md +264 -0
  5. package/ignore-me-test-projects/no-fail/applications/applicationA/package.json +8 -0
  6. package/ignore-me-test-projects/no-fail/applications/applicationB/package.json +8 -0
  7. package/ignore-me-test-projects/no-fail/libraries/libraryA/package.json +8 -0
  8. package/ignore-me-test-projects/no-fail/libraries/libraryB/package.json +8 -0
  9. package/ignore-me-test-projects/no-fail/libraries/nested/libraryC/package.json +8 -0
  10. package/ignore-me-test-projects/no-fail/package.json +7 -0
  11. package/ignore-me-test-projects/one-fail/applications/applicationA/package.json +8 -0
  12. package/ignore-me-test-projects/one-fail/applications/applicationB/package.json +8 -0
  13. package/ignore-me-test-projects/one-fail/libraries/libraryA/package.json +8 -0
  14. package/ignore-me-test-projects/one-fail/libraries/libraryB/package.json +8 -0
  15. package/ignore-me-test-projects/one-fail/libraries/nested/libraryC/package.json +8 -0
  16. package/ignore-me-test-projects/one-fail/package.json +7 -0
  17. package/ignore-me-test-projects/two-fail/applications/applicationA/package.json +8 -0
  18. package/ignore-me-test-projects/two-fail/applications/applicationB/package.json +8 -0
  19. package/ignore-me-test-projects/two-fail/libraries/libraryA/package.json +8 -0
  20. package/ignore-me-test-projects/two-fail/libraries/libraryB/package.json +8 -0
  21. package/ignore-me-test-projects/two-fail/libraries/nested/libraryC/package.json +8 -0
  22. package/ignore-me-test-projects/two-fail/package.json +7 -0
  23. package/package.json +2 -1
  24. package/src/cli/cli.ts +1 -1
  25. package/src/cli/projectCommands.ts +222 -110
  26. package/src/internal/bunVersion.ts +4 -2
  27. package/src/internal/logger.ts +7 -3
  28. package/src/internal/regex.ts +5 -0
  29. package/src/project/project.ts +10 -0
  30. package/src/workspaces/errors.ts +1 -0
  31. package/src/workspaces/findWorkspaces.ts +1 -3
  32. package/src/workspaces/packageJson.ts +15 -7
  33. package/tsconfig.json +1 -1
@@ -0,0 +1,12 @@
1
+ {
2
+ "recommendations": [
3
+ "esbenp.prettier-vscode",
4
+ "oven.bun-vscode",
5
+ "streetsidesoftware.code-spell-checker",
6
+ "jasonnutter.vscode-codeowners",
7
+ "EditorConfig.EditorConfig",
8
+ "dbaeumer.vscode-eslint",
9
+ "github.vscode-github-actions",
10
+ "aaron-bond.better-comments"
11
+ ]
12
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "editor.codeActionsOnSave": {
3
+ "source.fixAll.eslint": "explicit"
4
+ },
5
+ "eslint.codeActionsOnSave.rules": ["import/order"],
6
+ "eslint.validate": ["javascript", "typescript"],
7
+ "[typescript]": {
8
+ "editor.formatOnSave": true,
9
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
10
+ },
11
+ "[javascript]": {
12
+ "editor.formatOnSave": true,
13
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
14
+ },
15
+ "[json]": {
16
+ "editor.formatOnSave": true,
17
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
18
+ },
19
+ "[jsonc]": {
20
+ "editor.formatOnSave": true,
21
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
22
+ }
23
+ }
package/README.md CHANGED
@@ -23,11 +23,16 @@ 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
 
30
+ # Filter list of workspaces with wildcard
31
+ bw list-workspaces "my-*"
32
+
29
33
  # List all workspace scripts
30
34
  bw list-scripts
35
+
31
36
  # List script names only
32
37
  bw list-scripts --name-only
33
38
 
@@ -37,25 +42,38 @@ bw info my-workspace
37
42
 
38
43
  # Get info about a script
39
44
  bw script-info my-script
45
+
40
46
  # Only print list of workspace names that have the script
41
47
  bw script-info my-script --workspaces-only
42
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
+
43
55
  # Run a script for all
44
56
  # workspaces that have it
45
57
  # in their `scripts` field
46
- bw run-script my-script
58
+ bw run my-script
47
59
 
48
60
  # Run a script for a specific workspace
49
- bw run-script my-script my-workspace
61
+ bw run my-script my-workspace
50
62
 
51
63
  # Run a script for multiple workspaces
52
- bw run-script my-script workspace-a workspace-b
64
+ bw run my-script workspace-a workspace-b
65
+
66
+ # Run a script for workspaces using wildcard
67
+ bw run my-script "my-workspace-*"
53
68
 
54
69
  # Run script in parallel for all workspaces
55
- bw run-script my-script --parallel
70
+ bw run my-script --parallel
56
71
 
57
72
  # Append args to each script call
58
- bw run-script my-script --args "--my --args"
73
+ bw run my-script --args "--my --args"
74
+
75
+ # Use the workspace name in args
76
+ bw run my-script --args "--my --args=<workspace>"
59
77
 
60
78
  # Help (--help can also be passed to any command)
61
79
  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.1.2",
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,38 +22,62 @@ 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,
27
31
  printLines,
28
32
  }: ProjectCommandsContext) => {
29
33
  program
30
- .command("list-workspaces")
34
+ .command("list-workspaces [pattern]")
31
35
  .aliases(["ls", "list"])
32
36
  .description("List all workspaces")
33
37
  .option("--name-only", "Only show workspace names")
34
- .action((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
- project.workspaces.forEach((workspace) => {
43
- if (options.nameOnly) {
44
- 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
+ );
45
64
  } else {
46
- 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
+ });
47
72
  }
48
- });
49
73
 
50
- if (!lines.length) {
51
- lines.push("No workspaces found");
52
- }
74
+ if (!lines.length) {
75
+ lines.push("No workspaces found");
76
+ }
53
77
 
54
- printLines(...lines);
55
- });
78
+ printLines(...lines);
79
+ },
80
+ );
56
81
  };
57
82
 
58
83
  const listScripts = ({
@@ -64,27 +89,50 @@ const listScripts = ({
64
89
  .command("list-scripts")
65
90
  .description("List all scripts available with their workspaces")
66
91
  .option("--name-only", "Only show script names")
67
- .action((options) => {
68
- logger.debug("Command: List scripts");
69
-
70
- const scripts = project.listScriptsWithWorkspaces();
71
- const lines: string[] = [];
72
- Object.values(scripts)
73
- .sort(({ name: nameA }, { name: nameB }) => nameA.localeCompare(nameB))
74
- .forEach(({ name, workspaces }) => {
75
- if (options.nameOnly) {
76
- lines.push(name);
77
- } else {
78
- lines.push(...createScriptInfoLines(name, workspaces));
79
- }
80
- });
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
+ );
81
99
 
82
- if (!lines.length) {
83
- lines.push("No scripts found");
84
- }
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
+ }
85
132
 
86
- printLines(...lines);
87
- });
133
+ printLines(...lines);
134
+ },
135
+ );
88
136
  };
89
137
 
90
138
  const workspaceInfo = ({
@@ -96,17 +144,29 @@ const workspaceInfo = ({
96
144
  .command("workspace-info <workspace>")
97
145
  .aliases(["info"])
98
146
  .description("Show information about a workspace")
99
- .action((workspaceName) => {
100
- 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
+ );
101
154
 
102
- const workspace = project.findWorkspaceByName(workspaceName);
103
- if (!workspace) {
104
- logger.error(`Workspace not found: ${JSON.stringify(workspaceName)}`);
105
- return;
106
- }
155
+ const workspace = project.findWorkspaceByName(workspaceName);
156
+ if (!workspace) {
157
+ logger.error(
158
+ `Workspace not found: (options: ${JSON.stringify(workspaceName)})`,
159
+ );
160
+ return;
161
+ }
107
162
 
108
- printLines(...createWorkspaceInfoLines(workspace));
109
- });
163
+ printLines(
164
+ ...(options.json
165
+ ? createJsonLines(workspace, options)
166
+ : createWorkspaceInfoLines(workspace)),
167
+ );
168
+ },
169
+ );
110
170
  };
111
171
 
112
172
  const scriptInfo = ({
@@ -118,55 +178,80 @@ const scriptInfo = ({
118
178
  .command("script-info <script>")
119
179
  .description("Show information about a script")
120
180
  .option("--workspaces-only", "Only show script's workspace names")
121
- .action((script, options) => {
122
- 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
+ );
123
191
 
124
- const scripts = project.listScriptsWithWorkspaces();
125
- const scriptMetadata = scripts[script];
126
- 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
+ }
127
202
  printLines(
128
- `Script not found: ${JSON.stringify(
129
- script,
130
- )} (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)),
131
218
  );
132
- return;
133
- }
134
- printLines(
135
- ...(options.workspacesOnly
136
- ? scriptMetadata.workspaces.map(({ name }) => name)
137
- : createScriptInfoLines(script, scriptMetadata.workspaces)),
138
- );
139
- });
219
+ },
220
+ );
140
221
  };
141
222
 
142
- const runScript = ({
143
- program,
144
- project,
145
- printLines,
146
- }: ProjectCommandsContext) => {
223
+ const runScript = ({ program, project }: ProjectCommandsContext) => {
147
224
  program
148
225
  .command("run <script> [workspaces...]")
149
226
  .description("Run a script in all workspaces")
150
227
  .option("--parallel", "Run the scripts in parallel")
151
228
  .option("--args <args>", "Args to append to the script command", "")
152
- .action(async (script: string, workspaces: string[], options) => {
229
+ .action(async (script: string, _workspaces: string[], options) => {
153
230
  logger.debug(
154
231
  `Command: Run script ${JSON.stringify(script)} for ${
155
- workspaces.length
156
- ? "workspaces " + workspaces.join(", ")
232
+ _workspaces.length
233
+ ? "workspaces " + _workspaces.join(", ")
157
234
  : "all workspaces"
158
235
  } (parallel: ${!!options.parallel}, method: ${JSON.stringify(
159
236
  options.method,
160
237
  )}, args: ${JSON.stringify(options.args)})`,
161
238
  );
162
239
 
163
- workspaces = workspaces.length
164
- ? workspaces
240
+ const workspaces = _workspaces.length
241
+ ? _workspaces.flatMap((workspacePattern) => {
242
+ if (workspacePattern.includes("*")) {
243
+ return project
244
+ .findWorkspacesByPattern(workspacePattern)
245
+ .filter(({ packageJson: { scripts } }) => scripts?.[script])
246
+ .map(({ name }) => name);
247
+ }
248
+ return [workspacePattern];
249
+ })
165
250
  : project.listWorkspacesWithScript(script).map(({ name }) => name);
166
251
 
167
252
  if (!workspaces.length) {
168
253
  program.error(
169
- `No workspaces found for script ${JSON.stringify(script)}`,
254
+ `No ${_workspaces.length ? "matching " : ""}workspaces found for script ${JSON.stringify(script)}`,
170
255
  );
171
256
  }
172
257
 
@@ -190,9 +275,11 @@ const runScript = ({
190
275
  scriptName,
191
276
  workspace,
192
277
  }: (typeof scriptCommands)[number]) => {
278
+ const commandLogger = createLogger(`${workspace.name}:${scriptName}`);
279
+
193
280
  const splitCommand = command.command.split(/\s+/g);
194
281
 
195
- logger.debug(
282
+ commandLogger.debug(
196
283
  `Running script ${scriptName} in workspace ${workspace.name} (cwd: ${
197
284
  command.cwd
198
285
  }): ${splitCommand.join(" ")}`,
@@ -200,21 +287,25 @@ const runScript = ({
200
287
 
201
288
  const silent = logger.level === "silent";
202
289
 
203
- if (!silent) {
204
- printLines(
205
- `Running script ${JSON.stringify(
206
- scriptName,
207
- )} in workspace ${JSON.stringify(workspace.name)}`,
208
- );
209
- }
210
-
211
290
  const proc = Bun.spawn(command.command.split(/\s+/g), {
212
291
  cwd: command.cwd,
213
292
  env: process.env,
214
- stdout: silent ? "ignore" : "inherit",
215
- stderr: silent ? "ignore" : "inherit",
293
+ stdout: silent ? "ignore" : "pipe",
294
+ stderr: silent ? "ignore" : "pipe",
216
295
  });
217
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
+
218
309
  await proc.exited;
219
310
 
220
311
  return {
@@ -222,30 +313,20 @@ const runScript = ({
222
313
  workspace,
223
314
  command,
224
315
  success: proc.exitCode === 0,
316
+ error:
317
+ proc.exitCode === 0
318
+ ? null
319
+ : new BunWorkspacesError(
320
+ `Script exited with code ${proc.exitCode}`,
321
+ ),
225
322
  };
226
323
  };
227
324
 
228
- const handleError = (error: unknown, workspace: string) => {
229
- logger.error(error);
230
- program.error(
231
- `Script failed in ${workspace} (error: ${JSON.stringify((error as Error).message ?? error)})`,
232
- );
233
- };
234
-
235
- const handleResult = ({
236
- scriptName,
237
- workspace,
238
- success,
239
- }: (typeof scriptCommands)[number] & { success: boolean }) => {
240
- logger.info(
241
- `${success ? "✅" : "❌"} ${workspace.name}: ${scriptName}`,
242
- );
243
- if (!success) {
244
- program.error(
245
- `Script ${scriptName} failed in workspace ${workspace.name}`,
246
- );
247
- }
248
- };
325
+ const results = [] as {
326
+ success: boolean;
327
+ workspaceName: string;
328
+ error: Error | null;
329
+ }[];
249
330
 
250
331
  if (options.parallel) {
251
332
  let i = 0;
@@ -253,9 +334,17 @@ const runScript = ({
253
334
  scriptCommands.map(runCommand),
254
335
  )) {
255
336
  if (result.status === "rejected") {
256
- handleError(result.reason, workspaces[i]);
337
+ results.push({
338
+ success: false,
339
+ workspaceName: workspaces[i],
340
+ error: result.reason,
341
+ });
257
342
  } else {
258
- handleResult(result.value);
343
+ results.push({
344
+ success: result.value.success,
345
+ workspaceName: workspaces[i],
346
+ error: result.value.error,
347
+ });
259
348
  }
260
349
  i++;
261
350
  }
@@ -264,12 +353,35 @@ const runScript = ({
264
353
  for (const command of scriptCommands) {
265
354
  try {
266
355
  const result = await runCommand(command);
267
- handleResult(result);
356
+ results.push({
357
+ success: result.success,
358
+ workspaceName: workspaces[i],
359
+ error: result.error,
360
+ });
268
361
  } catch (error) {
269
- handleError(error, workspaces[i]);
362
+ results.push({
363
+ success: false,
364
+ workspaceName: workspaces[i],
365
+ error: error as Error,
366
+ });
270
367
  }
368
+ i++;
271
369
  }
272
- 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`);
273
385
  }
274
386
  });
275
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}] ` });
@@ -0,0 +1,5 @@
1
+ export const createRawPattern = (pattern: string) =>
2
+ pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
+
4
+ export const createWildcardRegex = (pattern: string) =>
5
+ new RegExp(`^${pattern.split("*").map(createRawPattern).join(".*")}$`);
@@ -1,4 +1,5 @@
1
1
  import path from "path";
2
+ import { createWildcardRegex } from "../internal/regex";
2
3
  import { findWorkspacesFromPackage, type Workspace } from "../workspaces";
3
4
  import { ERRORS } from "./errors";
4
5
  import {
@@ -32,6 +33,7 @@ export interface Project {
32
33
  listWorkspacesWithScript(scriptName: string): Workspace[];
33
34
  listScriptsWithWorkspaces(): Record<string, ScriptMetadata>;
34
35
  findWorkspaceByName(workspaceName: string): Workspace | null;
36
+ findWorkspacesByPattern(workspaceName: string): Workspace[];
35
37
  createScriptCommand(
36
38
  options: CreateProjectScriptCommandOptions,
37
39
  ): CreateProjectScriptCommandResult;
@@ -68,6 +70,7 @@ class _Project implements Project {
68
70
  );
69
71
  });
70
72
  return Array.from(scripts)
73
+ .sort((a, b) => a.localeCompare(b))
71
74
  .map((name) => ({
72
75
  name,
73
76
  workspaces: this.listWorkspacesWithScript(name),
@@ -88,6 +91,13 @@ class _Project implements Project {
88
91
  );
89
92
  }
90
93
 
94
+ /** Accepts wildcard for finding a list of workspaces */
95
+ findWorkspacesByPattern(workspacePattern: string): Workspace[] {
96
+ if (!workspacePattern) return [];
97
+ const regex = createWildcardRegex(workspacePattern);
98
+ return this.workspaces.filter((workspace) => regex.test(workspace.name));
99
+ }
100
+
91
101
  createScriptCommand(
92
102
  options: CreateProjectScriptCommandOptions,
93
103
  ): CreateProjectScriptCommandResult {
@@ -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",