@smoothbricks/cli 0.1.1 → 0.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.
Files changed (117) hide show
  1. package/README.md +155 -30
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +91 -7
  4. package/dist/devenv/index.d.ts +10 -0
  5. package/dist/devenv/index.d.ts.map +1 -0
  6. package/dist/devenv/index.js +27 -0
  7. package/dist/lib/cli-package.d.ts +4 -0
  8. package/dist/lib/cli-package.d.ts.map +1 -0
  9. package/dist/lib/cli-package.js +15 -0
  10. package/dist/lib/run.d.ts +5 -0
  11. package/dist/lib/run.d.ts.map +1 -1
  12. package/dist/lib/run.js +13 -0
  13. package/dist/lib/workspace.d.ts +15 -1
  14. package/dist/lib/workspace.d.ts.map +1 -1
  15. package/dist/lib/workspace.js +25 -2
  16. package/dist/monorepo/commit-msg.d.ts +8 -1
  17. package/dist/monorepo/commit-msg.d.ts.map +1 -1
  18. package/dist/monorepo/commit-msg.js +85 -7
  19. package/dist/monorepo/index.d.ts +19 -2
  20. package/dist/monorepo/index.d.ts.map +1 -1
  21. package/dist/monorepo/index.js +104 -16
  22. package/dist/monorepo/managed-files.js +1 -1
  23. package/dist/monorepo/nx-sync.d.ts +2 -1
  24. package/dist/monorepo/nx-sync.d.ts.map +1 -1
  25. package/dist/monorepo/nx-sync.js +8 -2
  26. package/dist/monorepo/package-hygiene.d.ts +6 -2
  27. package/dist/monorepo/package-hygiene.d.ts.map +1 -1
  28. package/dist/monorepo/package-hygiene.js +12 -4
  29. package/dist/monorepo/package-policy.d.ts +22 -2
  30. package/dist/monorepo/package-policy.d.ts.map +1 -1
  31. package/dist/monorepo/package-policy.js +1167 -8
  32. package/dist/monorepo/packed-package.d.ts +3 -0
  33. package/dist/monorepo/packed-package.d.ts.map +1 -1
  34. package/dist/monorepo/packed-package.js +68 -36
  35. package/dist/monorepo/packs/index.d.ts +20 -1
  36. package/dist/monorepo/packs/index.d.ts.map +1 -1
  37. package/dist/monorepo/packs/index.js +238 -19
  38. package/dist/monorepo/publish-workflow.d.ts +0 -4
  39. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  40. package/dist/monorepo/publish-workflow.js +5 -12
  41. package/dist/monorepo/tool-validation.d.ts +22 -0
  42. package/dist/monorepo/tool-validation.d.ts.map +1 -0
  43. package/dist/monorepo/tool-validation.js +399 -0
  44. package/dist/nx/index.d.ts +24 -0
  45. package/dist/nx/index.d.ts.map +1 -0
  46. package/dist/nx/index.js +94 -0
  47. package/dist/release/bootstrap-npm-packages.d.ts +19 -0
  48. package/dist/release/bootstrap-npm-packages.d.ts.map +1 -0
  49. package/dist/release/bootstrap-npm-packages.js +58 -0
  50. package/dist/release/candidates.d.ts +11 -0
  51. package/dist/release/candidates.d.ts.map +1 -0
  52. package/dist/release/candidates.js +181 -0
  53. package/dist/release/core.d.ts +1 -0
  54. package/dist/release/core.d.ts.map +1 -1
  55. package/dist/release/github-release.js +2 -2
  56. package/dist/release/index.d.ts +25 -0
  57. package/dist/release/index.d.ts.map +1 -1
  58. package/dist/release/index.js +247 -49
  59. package/dist/release/npm-auth.d.ts +0 -1
  60. package/dist/release/npm-auth.d.ts.map +1 -1
  61. package/dist/release/npm-auth.js +4 -16
  62. package/dist/release/orchestration.d.ts +2 -1
  63. package/dist/release/orchestration.d.ts.map +1 -1
  64. package/dist/release/orchestration.js +9 -1
  65. package/dist/release/publish-plan.js +1 -1
  66. package/managed/raw/git-format-staged.yml +54 -0
  67. package/managed/raw/tooling/git-hooks/commit-msg.sh +1 -1
  68. package/managed/templates/github/actions/setup-devenv/action.yml +1 -7
  69. package/managed/templates/github/workflows/ci.yml +1 -0
  70. package/package.json +15 -3
  71. package/src/cli.ts +134 -20
  72. package/src/devenv/index.test.ts +49 -0
  73. package/src/devenv/index.ts +38 -0
  74. package/src/lib/cli-package.ts +18 -0
  75. package/src/lib/run.ts +19 -0
  76. package/src/lib/workspace.ts +41 -2
  77. package/src/monorepo/__tests__/publish-workflow.test.ts +16 -9
  78. package/src/monorepo/commit-msg.test.ts +45 -0
  79. package/src/monorepo/commit-msg.ts +107 -7
  80. package/src/monorepo/index.ts +142 -16
  81. package/src/monorepo/managed-files.ts +1 -1
  82. package/src/monorepo/nx-sync.ts +9 -2
  83. package/src/monorepo/package-hygiene.test.ts +34 -0
  84. package/src/monorepo/package-hygiene.ts +26 -4
  85. package/src/monorepo/package-policy.test.ts +902 -0
  86. package/src/monorepo/package-policy.ts +1393 -7
  87. package/src/monorepo/packed-package.ts +89 -37
  88. package/src/monorepo/packs/index.test.ts +189 -0
  89. package/src/monorepo/packs/index.ts +311 -20
  90. package/src/monorepo/publish-workflow.ts +8 -16
  91. package/src/monorepo/setup-test-tracing.test.ts +120 -0
  92. package/src/monorepo/tool-validation.test.ts +261 -0
  93. package/src/monorepo/tool-validation.ts +462 -0
  94. package/src/nx/index.test.ts +64 -0
  95. package/src/nx/index.ts +119 -0
  96. package/src/release/__tests__/bootstrap-npm-packages.test.ts +127 -0
  97. package/src/release/__tests__/candidates.test.ts +217 -0
  98. package/src/release/__tests__/core-properties.test.ts +4 -4
  99. package/src/release/__tests__/core-scenarios.test.ts +15 -5
  100. package/src/release/__tests__/core.test.ts +2 -2
  101. package/src/release/__tests__/fixture-repo.test.ts +22 -13
  102. package/src/release/__tests__/github-release.test.ts +13 -8
  103. package/src/release/__tests__/helpers/fixture-repo.ts +1 -0
  104. package/src/release/__tests__/npm-auth.test.ts +12 -21
  105. package/src/release/__tests__/orchestration.test.ts +51 -7
  106. package/src/release/__tests__/publish-plan.test.ts +9 -3
  107. package/src/release/__tests__/retag-unpublished.test.ts +1 -1
  108. package/src/release/__tests__/trust-publisher.test.ts +133 -0
  109. package/src/release/bootstrap-npm-packages.ts +88 -0
  110. package/src/release/candidates.ts +225 -0
  111. package/src/release/core.ts +1 -0
  112. package/src/release/github-release.ts +2 -2
  113. package/src/release/index.ts +346 -56
  114. package/src/release/npm-auth.ts +4 -33
  115. package/src/release/orchestration.ts +12 -2
  116. package/src/release/publish-plan.ts +1 -1
  117. package/managed/raw/.git-format-staged.yml +0 -47
package/README.md CHANGED
@@ -36,13 +36,15 @@ smoo monorepo validate-commit-msg <commit-msg-file>
36
36
  smoo monorepo sync-bun-lockfile-versions
37
37
  smoo monorepo list-release-packages [--fail-empty] [--github-output <path>]
38
38
  smoo monorepo validate-public-tags
39
+ smoo monorepo setup-test-tracing (--all | --projects <projects>) [--dry-run]
39
40
 
40
41
  smoo release npm-status
41
42
  smoo release repair-pending [--dry-run]
42
43
  smoo release version --bump <auto|patch|minor|major|prerelease> [--dry-run] [--github-output <path>]
43
44
  smoo release publish --bump <auto|patch|minor|major|prerelease> [--dry-run]
44
45
  smoo release retag-unpublished <tag...> [--to <ref>] [--push] [--dispatch] [--remote <remote>] [--branch <branch>] [--dry-run]
45
- smoo release trust-publisher [--dry-run] [--otp <code>] [--skip-login]
46
+ smoo release bootstrap-npm-packages [--dry-run] [--skip-login] [--package <name...>]
47
+ smoo release trust-publisher [--bootstrap] [--dry-run] [--otp <code>] [--skip-login]
46
48
 
47
49
  smoo github-ci cleanup-cache
48
50
  smoo github-ci nx-smart --target <target> --name <check-name> --step <number>
@@ -62,6 +64,10 @@ It currently:
62
64
  - Synchronizes root runtime versions inside devenv, or when `--sync-runtime` is passed.
63
65
  - Applies safe publish metadata defaults to `npm:public` packages without inferring package ownership.
64
66
  - Normalizes internal workspace dependency ranges to `workspace:*`.
67
+ - Rewrites safe package scripts in packages with workspace dependencies so developer commands like `bun run test` and
68
+ `bun run dev` stay available while delegating through Nx targets.
69
+ - `smoo monorepo validate --fix` creates/updates `tooling/package.json`, keeps `@smoothbricks/cli` there instead of the
70
+ root workspace package, and fills required workspace/devenv tool declarations.
65
71
  - Runs [`sherif --fix --select highest`][sherif] for broad monorepo package hygiene.
66
72
  - Normalizes conditional export ordering so `types` comes first and `default` comes last.
67
73
  - Adds `src` to package `files` when development-only exports intentionally point at source files.
@@ -70,6 +76,19 @@ The workspace dependency rule is generic. `smoo` does not know about individual
70
76
  For every root or workspace `package.json`, if a dependency name matches an actual package in the same workspace, `smoo`
71
77
  rewrites that range to `workspace:*`.
72
78
 
79
+ Packages with internal workspace dependencies also need Nx-aware scripts so dependent builds run before local commands.
80
+ For safe build, test, typecheck, benchmark, dev, and preview commands, `smoo monorepo validate --fix` moves the real
81
+ command into `package.json` `nx.targets.<target>.options.command`, sets `cwd` to `{projectRoot}`, and replaces the
82
+ script with an `nx run <project>:<target>` alias. Continuous commands such as `astro dev`, `vite dev`, and previews get
83
+ `--tui=false --outputStyle=stream` on the alias and `continuous: true` on the Nx target. Simple leading environment
84
+ assignments are moved into `nx.targets.<target>.options.env` so commands such as
85
+ `NODE_OPTIONS='--import=extensionless/register' astro dev` remain shell-independent.
86
+
87
+ The rewrite is intentionally conservative. `smoo` does not rewrite deploy, database, release, sync, subtree, publish, or
88
+ pack scripts, and it rejects Nx target commands that recurse through package scripts such as `bun run test`. The reason
89
+ is dependency correctness without hiding unsafe operational commands behind generated Nx targets: workspace-dependent
90
+ packages should get `^build` ordering for ordinary development commands, while publishing and deployment stay explicit.
91
+
73
92
  `smoo monorepo init --runtime-only` only synchronizes root runtime versions. It is used from direnv setup so
74
93
  `packageManager`, `engines.node`, and `@types/node` stay aligned with the active devenv shell without duplicating that
75
94
  policy in the direnv script.
@@ -79,6 +98,36 @@ while package manifests are not forced to depend on Bun just because a test tsco
79
98
  does explicitly declare `@types/bun`, `sherif` can keep duplicate declarations consistent, but `smoo` owns the semantic
80
99
  root `bun@x.y.z` to `@types/bun x.y.z` relationship.
81
100
 
101
+ ## LMAO Test Tracing
102
+
103
+ `smoo monorepo setup-test-tracing` configures LMAO-backed Bun test tracing for workspace packages. It is a bulk wrapper
104
+ around the `@smoothbricks/nx-plugin:bun-test-tracing` generator, so the Nx plugin remains the single source of truth for
105
+ the files written.
106
+
107
+ Configure every workspace package:
108
+
109
+ ```bash
110
+ smoo monorepo setup-test-tracing --all
111
+ ```
112
+
113
+ Configure selected packages by Nx project name, package name, or package root:
114
+
115
+ ```bash
116
+ smoo monorepo setup-test-tracing --projects cli,@smoothbricks/lmao,packages/nx-plugin
117
+ ```
118
+
119
+ The command infers the op context module from each package's `package.json` `name`, assumes an `opContext` named export,
120
+ and imports `defineTestTracer` from `@smoothbricks/lmao/testing/bun`. Override those defaults when a repository uses a
121
+ different convention:
122
+
123
+ ```bash
124
+ smoo monorepo setup-test-tracing --projects my-lib --op-context-export myOpContext
125
+ smoo monorepo setup-test-tracing --projects my-lib --tracer-module @scope/testing/bun
126
+ ```
127
+
128
+ Use `--dry-run` to print the `nx g @smoothbricks/nx-plugin:bun-test-tracing ...` invocations without writing files.
129
+ After setup, run `smoo monorepo validate --fix` to apply the broader SmoothBricks monorepo policy.
130
+
82
131
  ## Validation
83
132
 
84
133
  `smoo monorepo validate` is the read-only gate. It should pass in local shells and CI after packages have been built.
@@ -88,12 +137,16 @@ It checks:
88
137
  - Managed file drift.
89
138
  - Root package policy.
90
139
  - Root Bun type version matches the Bun package manager version.
140
+ - Tooling policy: root `package.json` owns workspace-level tools like `nx`, `tooling/package.json` owns `smoo`, and
141
+ `tooling/direnv/devenv.nix` owns shell-provided tools like `bun`, `git-format-staged`, and `fmt`.
91
142
  - Nx release policy, including project package release tags, project-level GitHub Release changelogs, and the temporary
92
143
  Bun lockfile versionActions hook.
93
144
  - `bun.lock` workspace versions match package manifests.
94
145
  - Public package tag policy.
95
146
  - Public package metadata.
96
147
  - Workspace dependency ranges.
148
+ - Workspace-dependent package scripts delegate safe commands through Nx targets without recursive script runners.
149
+ - Nx target conventions and inferred-task setup.
97
150
  - [`sherif`] package hygiene, with warnings treated as validation failures.
98
151
  - Packed public package artifacts with [`publint`].
99
152
  - Packed public package type resolution with the [`attw`][are-the-types-wrong] CLI.
@@ -146,24 +199,70 @@ package `repository.url` is a validation failure so new packages must consciousl
146
199
  current repository or mirrored from another one. Init still sets `publishConfig.access = "public"`, repository type,
147
200
  repository directory, export ordering, and source-file publish entries when those can be derived safely.
148
201
 
149
- `smoo monorepo list-release-packages` prints the comma-separated package names that are both `npm:public` and owned by
150
- the current repository. Release commands, trusted-publisher setup, and the managed publish workflow use this owned
151
- release package list instead of every public package in the workspace.
202
+ `smoo monorepo list-release-packages` prints the comma-separated Nx project names for packages that are both
203
+ `npm:public` and owned by the current repository. Release commands, trusted-publisher setup, and the managed publish
204
+ workflow use this owned release package list instead of every public package in the workspace. smoo keeps both names in
205
+ release metadata: Nx commands receive `projectName`, while npm publish, GitHub Releases, and release tags use the real
206
+ package `name`.
152
207
 
153
208
  For [GitHub Actions], `smoo monorepo list-release-packages --fail-empty --github-output "$GITHUB_OUTPUT"` appends the
154
- `projects=<package-list>` output expected by the managed publish workflow and fails with a clear error when no owned
209
+ `projects=<nx-project-list>` output expected by the managed publish workflow and fails with a clear error when no owned
155
210
  release packages exist.
156
211
 
157
212
  `smoo release npm-status` shows whether each owned release package's current `name@version` already exists on npm. It is
158
213
  an npm registry check, not a full release workflow status check.
159
214
 
215
+ `smoo release version --bump auto` first selects direct release candidates, then delegates versioning to [Nx]. Direct
216
+ candidates are owned public packages with package-local changes that can affect published users: files matched by the
217
+ package's resolved Nx `build`/`production` inputs, packaged assets listed in `package.json` `files`, package metadata
218
+ docs such as README/LICENSE/CHANGELOG, or user-visible `package.json` fields such as `exports`, `bin`, `types`,
219
+ `dependencies`, `peerDependencies`, and `publishConfig`. Test-only and local automation changes such as `scripts`, `nx`,
220
+ `devDependencies`, and `tsconfig.test.json` do not select a package by themselves.
221
+
222
+ Downstream dependency bumps are intentionally left to Nx release. If package A is selected and bumped, Nx may also bump
223
+ public package B when B depends on A, even when B has no direct file changes. `smoo` should not pre-expand direct
224
+ candidates to downstream dependents because that would duplicate Nx's dependency graph and can over-select packages.
225
+
226
+ ## Nx Conventions
227
+
228
+ `smoo` keeps Nx target names predictable and separates tool work from aggregate workflows.
229
+
230
+ Official Nx plugins own targets they already know how to infer. For example, `@nx/js/typescript` owns TypeScript library
231
+ builds, so a package `tsconfig.lib.json` produces the concrete target `tsc-js`.
232
+
233
+ `@smoothbricks/nx-plugin` only fills SmoothBricks convention gaps that official plugins do not provide. Today that means
234
+ Bun test typechecking, non-TypeScript build-tool steps, and aggregate targets.
235
+
236
+ Concrete targets describe the tool that runs and the artifact or purpose it produces:
237
+
238
+ - `tsc-js` comes from the official TypeScript plugin and runs `tsc` for package JavaScript/declaration output.
239
+ - Packages that run `bun test` must have `tsconfig.test.json`. Bun executes tests without typechecking, so smoo creates
240
+ a no-emit `typecheck-tests` target from that config and wires it into validation. Other test runners may own their own
241
+ typecheck path.
242
+ - Test tsconfigs are validation configs, not TypeScript build-mode projects. They must use `noEmit`, must not set
243
+ `composite: true`, and package root `tsconfig.json` must not reference `./tsconfig.test.json`. The inferred
244
+ `typecheck-tests` target runs `tsc --noEmit -p tsconfig.test.json` after `build` instead.
245
+ - Non-TypeScript build steps come from explicit tool configuration. For example, a package `build.zig` must expose named
246
+ `b.step("name", ...)` entries; each non-reserved step becomes a `zig-name` target such as `zig-wasm`.
247
+ - `build` is an aggregate. It exists only when there is at least one concrete build target such as `tsc-js` or another
248
+ tool-output target, and it depends on those targets instead of duplicating their commands.
249
+ - `lint` is an aggregate validation target. It is not a formatting target.
250
+
251
+ Explicit Nx target names must not contain `:`. Nx already uses colon syntax at the CLI boundary:
252
+ `project:target:configuration`. Allowing target names like `build:wasm` makes command parsing and package-script aliases
253
+ look like configurations, and it prevents a clean split between concrete tool-output targets and aggregate targets.
254
+
255
+ Use tool-output names for concrete targets, such as `tsc-js` and `zig-wasm`. Use `build` and `lint` only as aggregate
256
+ targets. Package scripts may still use developer-friendly colon names, for example `build:wasm`, but those scripts
257
+ should delegate to unambiguous Nx targets such as `nx run pkg:zig-wasm`.
258
+
160
259
  ## Managed Files
161
260
 
162
261
  `smoo monorepo update` writes the managed files into a repository.
163
262
 
164
263
  Managed files include:
165
264
 
166
- - [`.git-format-staged.yml`][git-format-staged]
265
+ - [`tooling/git-hooks/git-format-staged.yml`][git-format-staged]
167
266
  - Git hook scripts under `tooling/git-hooks`
168
267
  - [direnv]/[GitHub Actions] bootstrap scripts under `tooling/direnv`
169
268
  - [GitHub Actions] workflows under `.github/workflows`
@@ -185,10 +284,13 @@ smoo monorepo diff
185
284
 
186
285
  `check` fails when a managed file is missing or stale. `diff` reports drift without writing files.
187
286
 
188
- ## Git Hooks
287
+ ## Formatting And Git Hooks
189
288
 
190
- The generated pre-commit hook runs [`git-format-staged`][git-format-staged] from the repository root with `tooling`,
191
- `node_modules/.bin`, and the [devenv] profile on `PATH`.
289
+ The root `lint:fix` script runs [`git-format-staged`][git-format-staged] with
290
+ `--config tooling/git-hooks/git-format-staged.yml --unstaged`. The formatter config intentionally excludes `bun.lock`.
291
+
292
+ The generated pre-commit hook runs the same formatter path from the repository root with `tooling`, `node_modules/.bin`,
293
+ and the [devenv] profile on `PATH`.
192
294
 
193
295
  After formatting, the hook runs `smoo monorepo validate --fail-fast --only-if-new-workspace-package`. This keeps normal
194
296
  commits fast while still catching incomplete package setup and conditional managed-file drift when a new workspace
@@ -197,10 +299,16 @@ package manifest is staged.
197
299
  The generated commit-msg hook delegates conventional commit validation to:
198
300
 
199
301
  ```bash
200
- smoo monorepo validate-commit-msg <commit-msg-file>
302
+ smoo monorepo validate-commit-msg --fix <commit-msg-file>
201
303
  ```
202
304
 
203
- This keeps hook behavior consistent with CI and avoids duplicating commit message parsing in shell.
305
+ This keeps hook behavior consistent with CI and avoids duplicating commit message parsing in shell. With `--fix`, smoo
306
+ wraps prose body paragraphs through `fmt -w 72` while preserving fenced code blocks, quoted markdown, indented blocks,
307
+ bullets, trailers, URLs, and comment lines.
308
+
309
+ Conventional commit scopes should use Nx project names. For packages in the same npm scope as the root package, smoo
310
+ requires `package.json` `nx.name` to be the unscoped package name, such as `cli` for `@smoothbricks/cli`, so subjects
311
+ like `fix(cli): repair release notes` map cleanly to Nx Release.
204
312
 
205
313
  ## GitHub Actions
206
314
 
@@ -246,14 +354,25 @@ Release commands wrap [Nx Release][nx-release] but keep SmoothBricks policy in o
246
354
 
247
355
  Versioning:
248
356
 
249
- - `--bump auto` uses [Nx Release][nx-release] [Conventional Commits] versioning.
250
- - `--bump patch|minor|major|prerelease` forces the release specifier.
357
+ - `--bump auto` first filters owned release packages to package-local candidates, then lets [Nx Release][nx-release]
358
+ derive the semver bump from [Conventional Commits]. A tagged package is an auto candidate only when files under its
359
+ package root changed since its current `name@version` release tag. An untagged package is an auto candidate only when
360
+ its package root has git history. Root-only changes, workflow edits, lockfile-only churn, and other workspace-global
361
+ changes may still affect Nx tasks, but they do not make unrelated package artifacts releasable.
362
+ - `--bump patch|minor|major|prerelease` forces the release specifier for the full owned release package set. Forced
363
+ bumps intentionally bypass the package-local auto filter.
251
364
  - Release packages are discovered from `npm:public` packages whose `repository.url` exactly matches the root package.
252
365
  - [Nx Release][nx-release] config must use `currentVersionResolver: "git-tag"` with
253
366
  `fallbackCurrentVersionResolver: "disk"`. Conventional-commit versioning requires git tags as the primary source,
254
367
  while the disk fallback supports initial releases before package tags exist.
255
368
  - [Nx Release][nx-release] config must use `versionActions: "@smoothbricks/cli/nx-version-actions"`. This wraps Nx's JS
256
369
  version actions and temporarily syncs `bun.lock` workspace versions after Nx runs `bun install --lockfile-only`.
370
+ - Same-org scoped packages must define short `package.json` `nx.name` values, for example `@smoothbricks/money` uses
371
+ `"nx": { "name": "money" }`. This lets Nx Release understand commit scopes like `fix(money): ...` without requiring
372
+ the npm org in every commit subject.
373
+ - Nx project names and npm package names are different release identities. Nx project filters, workflow `projects=`
374
+ outputs, build/lint/test validation, and project changelog lookup use `projectName`. npm publish checks, tarball
375
+ validation, GitHub Release titles, and durable release tags use package `name`.
257
376
  - [Nx Release][nx-release] `preVersionCommand` is intentionally not used. smoo builds exactly the packages that still
258
377
  need npm publish immediately before packing them, while the managed workflow separately builds, lints, tests, and
259
378
  validates newly created release commits.
@@ -265,8 +384,9 @@ Versioning:
265
384
  target, versioning returns `mode=none` and leaves idempotent completion to `smoo release publish`.
266
385
  - `smoo release version --github-output "$GITHUB_OUTPUT"` appends `mode=new|none` and `projects=<comma-list>`. The
267
386
  managed publish workflow uses `mode != "none"` to build, lint, test, and validate exactly the commit that
268
- `smoo release publish` will publish. The validation and publish step names include the selected mode so the [GitHub
269
- Actions] run shows whether it is creating a new release or recording a no-op.
387
+ `smoo release publish` will publish. `projects` is a comma-separated Nx project-name list, not an npm package-name
388
+ list. The validation and publish step names include the selected mode so the [GitHub Actions] run shows whether it is
389
+ creating a new release or recording a no-op.
270
390
  - Explicit bumps are mandatory when `HEAD` is not already a release target: after pending releases are repaired,
271
391
  `bump=patch|minor|major|prerelease` must make Nx create a new release commit. smoo fails if Nx returns without moving
272
392
  `HEAD`; `auto` may no-op when there are no releasable conventional commits.
@@ -278,8 +398,9 @@ Versioning:
278
398
  [oven-sh/bun#18906](https://github.com/oven-sh/bun/issues/18906),
279
399
  [oven-sh/bun#20477](https://github.com/oven-sh/bun/issues/20477), and
280
400
  [oven-sh/bun#20829](https://github.com/oven-sh/bun/issues/20829).
281
- - Package release tags must use `{projectName}@{version}`. smoo derives release package/version pairs from that tag
282
- shape and recreates missing local tags for Nx release commits before repairing remote state.
401
+ - Package release tags must use the npm package name and version, for example `@smoothbricks/nx-plugin@0.0.2`. smoo
402
+ derives release package/version pairs from that tag shape and recreates missing local tags for Nx release commits
403
+ before repairing remote state. Nx project names are only CLI/project-graph identifiers.
283
404
  - `smoo release retag-unpublished <tag...>` is a break-glass recovery command for the case where Nx already committed a
284
405
  version bump but npm publish failed before the package version became durable. It moves exact owned release tags to
285
406
  `HEAD` by default without bumping package manifests again. It refuses to move a tag when `package@version` already
@@ -305,8 +426,8 @@ decide which tags still need work; it does not walk normal commits looking for r
305
426
  4. Sort the remaining repair commits oldest-to-newest. Only after this sorted non-HEAD repair list exists does smoo
306
427
  start checking out commits.
307
428
  5. For each repair commit, check out the commit once and load that checkout's direnv environment once. If any grouped
308
- tag still needs npm publish, run `nx run-many -t build --projects=<comma-separated npm-missing packages>` once, then
309
- publish those packages using the npm dist-tag implied by each package version. If the commit only needs GitHub
429
+ tag still needs npm publish, run `nx run-many -t build --projects=<comma-separated npm-missing Nx projects>` once,
430
+ then publish those packages using the npm dist-tag implied by each package version. If the commit only needs GitHub
310
431
  Releases, skip the build. Finally, create the missing GitHub Releases for the grouped tags that need them.
311
432
 
312
433
  Pending release state should be a suffix of the release-target timeline because `repair-pending` runs before every
@@ -323,26 +444,30 @@ Publishing:
323
444
  - npm registry state gates publish idempotency only. It decides which already-versioned package tarballs still need to
324
445
  be published during a real release retry, not whether versioning should run or whether the workflow has a release to
325
446
  publish.
326
- - Before npm publish, smoo runs `nx run-many -t build --projects=<comma-separated npm-missing packages>` for exactly the
327
- packages whose `name@version` is not on npm yet. Nx cache makes this cheap when the managed workflow already built the
328
- same projects, and it keeps reruns self-sufficient when repairing a previously selected `HEAD` release target.
447
+ - Before npm publish, smoo runs `nx run-many -t build --projects=<comma-separated npm-missing Nx projects>` for exactly
448
+ the packages whose `name@version` is not on npm yet. Nx cache makes this cheap when the managed workflow already built
449
+ the same projects, and it keeps reruns self-sufficient when repairing a previously selected `HEAD` release target.
329
450
  - Publish uses `bun pm pack` to create package tarballs, then publishes those tarballs with latest npm CLI and
330
451
  `--provenance`. Each package uses the npm dist-tag implied by its own version (`next` for prereleases, `latest` for
331
452
  stable versions). Bun pack resolves internal `workspace:*` dependency ranges to real versions in the tarball manifest;
332
453
  smoo fails before publish if a packed manifest still contains `workspace:` or if an internal dependency does not match
333
454
  the current workspace package version.
334
- - [npm CLI][npm] owns publish authentication. Existing packages use [trusted publishing][npm-trusted-publishing] with
335
- [GitHub Actions OIDC][github-actions-oidc] from the workflow's `id-token: write` permission. Package names that do not
336
- exist on npm yet use `secrets.NPM_TOKEN` as `NODE_AUTH_TOKEN` for the first publish, because trusted publishing can
337
- only be configured after the package exists. Once `smoo release trust-publisher` succeeds for a package, future
338
- publishes for that package use OIDC even if the bootstrap token is still present in repository secrets.
455
+ - [npm CLI][npm] owns publish authentication. Packages use [trusted publishing][npm-trusted-publishing] with [GitHub
456
+ Actions OIDC][github-actions-oidc] from the workflow's `id-token: write` permission. Package names must exist on npm
457
+ before CI publish runs; use `smoo release trust-publisher --bootstrap` locally to publish `0.0.0-bootstrap.0` under
458
+ the `bootstrap` dist-tag for new package names before configuring trust.
459
+ - `smoo release bootstrap-npm-packages` scans owned `npm:public` release packages missing from npm, runs
460
+ `npm login --auth-type=web` through `nix shell nixpkgs#nodejs_latest` unless `--skip-login` is passed, and publishes a
461
+ minimal placeholder package with `--access public --tag bootstrap`. It supports `--dry-run` and `--package <name...>`
462
+ for targeted bootstraps.
339
463
  - `smoo release trust-publisher` configures [npm trusted publishing][npm-trusted-publishing] for every owned release
340
464
  package. It uses the root `package.json` `repository.url` as the GitHub `owner/repo`, uses `publish.yml` as the
341
465
  trusted workflow, and runs `npm trust` through `nix shell nixpkgs#nodejs_latest` because the Lambda-pinned Node 24/npm
342
466
  toolchain may lag the npm CLI feature. By default it runs `npm login --auth-type=web` first so npm can open a browser
343
- login; pass `--skip-login` when the current npm session is already authenticated. Packages must already exist on npm
344
- before trust can be configured. npm may still require operation-level 2FA for `npm trust`; smoo prompts for a hidden
345
- OTP per package, or you can pass `--otp <code>` for non-interactive use.
467
+ login; pass `--skip-login` when the current npm session is already authenticated. Pass `--bootstrap` to create missing
468
+ npm package names first, then configure trusted publishing in the same command. npm may still require operation-level
469
+ 2FA for `npm trust`; smoo prompts for a hidden OTP per package, or you can pass `--otp <code>` for non-interactive
470
+ use.
346
471
 
347
472
  GitHub Releases:
348
473
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,wBAAsB,MAAM,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAexE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,wBAAsB,MAAM,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAexE"}
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Command, CommanderError } from 'commander';
2
+ import { cliPackageVersion } from './lib/cli-package.js';
2
3
  import { findRepoRoot } from './lib/run.js';
3
4
  export async function runCli(argv = process.argv.slice(2)) {
4
5
  const program = buildProgram();
@@ -19,7 +20,12 @@ export async function runCli(argv = process.argv.slice(2)) {
19
20
  }
20
21
  function buildProgram() {
21
22
  const program = new Command();
22
- program.name('smoo').description('SmoothBricks monorepo tooling').exitOverride().showHelpAfterError();
23
+ program
24
+ .name('smoo')
25
+ .description('SmoothBricks monorepo tooling')
26
+ .version(cliPackageVersion, '-v, --version', 'print smoo version')
27
+ .exitOverride()
28
+ .showHelpAfterError();
23
29
  const monorepo = program.command('monorepo').description('Manage SmoothBricks-style monorepos');
24
30
  monorepo
25
31
  .command('init')
@@ -31,8 +37,10 @@ function buildProgram() {
31
37
  });
32
38
  monorepo
33
39
  .command('validate')
40
+ .option('--fix', 'apply safe monorepo policy fixes before validation')
34
41
  .option('--fail-fast', 'stop after the first failing validation pack')
35
42
  .option('--only-if-new-workspace-package', 'skip validation unless a new workspace package manifest is staged')
43
+ .option('--verbose', 'print validation progress and successful checks')
36
44
  .action(async (options) => {
37
45
  const { validateMonorepo } = await import('./monorepo/index.js');
38
46
  await validateMonorepo(await findRepoRoot(), options);
@@ -49,9 +57,12 @@ function buildProgram() {
49
57
  const { diffManagedFiles } = await import('./monorepo/index.js');
50
58
  diffManagedFiles(await findRepoRoot());
51
59
  });
52
- monorepo.command('validate-commit-msg <commitMsgFile>').action(async (commitMsgFile) => {
60
+ monorepo
61
+ .command('validate-commit-msg <commitMsgFile>')
62
+ .option('--fix', 'format the commit message before validation')
63
+ .action(async (commitMsgFile, options) => {
53
64
  const { validateCommitMessageFile } = await import('./monorepo/index.js');
54
- validateCommitMessageFile(commitMsgFile);
65
+ validateCommitMessageFile(commitMsgFile, options, await findRepoRoot());
55
66
  });
56
67
  monorepo.command('sync-bun-lockfile-versions').action(async () => {
57
68
  const { syncBunLockfileVersions } = await import('./monorepo/index.js');
@@ -60,10 +71,10 @@ function buildProgram() {
60
71
  monorepo
61
72
  .command('list-release-packages')
62
73
  .option('--fail-empty', 'fail when no owned release packages are found')
63
- .option('--github-output <path>', 'append projects=<packages> to a GitHub Actions output file')
74
+ .option('--github-output <path>', 'append projects=<nx-projects> to a GitHub Actions output file')
64
75
  .action(async (options) => {
65
- const { listReleasePackagesForNx } = await import('./monorepo/index.js');
66
- const packages = listReleasePackagesForNx(await findRepoRoot(), options);
76
+ const { listReleaseProjectNamesForNx } = await import('./monorepo/index.js');
77
+ const packages = listReleaseProjectNamesForNx(await findRepoRoot(), options);
67
78
  if (!options.githubOutput) {
68
79
  console.log(packages);
69
80
  }
@@ -72,6 +83,18 @@ function buildProgram() {
72
83
  const { validatePublicPackageTags } = await import('./monorepo/index.js');
73
84
  validatePublicPackageTags(await findRepoRoot());
74
85
  });
86
+ monorepo
87
+ .command('setup-test-tracing')
88
+ .description('Configure LMAO Bun test tracing for workspace packages')
89
+ .option('--all', 'configure every workspace package')
90
+ .option('--projects <projects>', 'comma-separated Nx project names, package names, or package roots')
91
+ .option('--op-context-export <exportName>', 'named op context export imported by test-suite-tracer', 'opContext')
92
+ .option('--tracer-module <module>', 'module specifier that exports defineTestTracer', '@smoothbricks/lmao/testing/bun')
93
+ .option('--dry-run', 'print generator invocations without writing files')
94
+ .action(async (options) => {
95
+ const { setupTestTracing } = await import('./monorepo/index.js');
96
+ await setupTestTracing(await findRepoRoot(), options);
97
+ });
75
98
  const release = program.command('release').description('Version, publish, and create GitHub Releases');
76
99
  release.command('npm-status').action(async () => {
77
100
  const { printReleaseState } = await import('./release/index.js');
@@ -89,7 +112,7 @@ function buildProgram() {
89
112
  .command('version')
90
113
  .option('--bump <bump>', 'auto, patch, minor, major, or prerelease', 'auto')
91
114
  .option('--dry-run [dryRun]', 'run without writing versions or tags')
92
- .option('--github-output <path>', 'append mode=<mode> and projects=<packages> to a GitHub Actions output file')
115
+ .option('--github-output <path>', 'append mode=<mode> and projects=<nx-projects> to a GitHub Actions output file')
93
116
  .action(async (options) => {
94
117
  const { releaseVersion } = await import('./release/index.js');
95
118
  await releaseVersion(await findRepoRoot(), {
@@ -128,20 +151,81 @@ function buildProgram() {
128
151
  dryRun: booleanOption(options.dryRun),
129
152
  });
130
153
  });
154
+ release
155
+ .command('bootstrap-npm-packages')
156
+ .alias('bootstrap')
157
+ .description('Publish minimal npm placeholder packages so trusted publishing can be configured')
158
+ .option('--dry-run [dryRun]', 'show placeholder publishes without logging in or publishing')
159
+ .option('--skip-login', 'skip npm browser login before publishing placeholders')
160
+ .option('--otp <otp>', 'npm one-time password for placeholder publish operations')
161
+ .option('--package <name...>', 'only bootstrap the selected owned release package names')
162
+ .action(async (options) => {
163
+ const { releaseBootstrapNpmPackages } = await import('./release/index.js');
164
+ await releaseBootstrapNpmPackages(await findRepoRoot(), {
165
+ dryRun: booleanOption(options.dryRun),
166
+ skipLogin: options.skipLogin === true,
167
+ otp: options.otp,
168
+ packages: options.package ?? [],
169
+ });
170
+ });
131
171
  release
132
172
  .command('trust-publisher')
133
173
  .description('Configure npm trusted publishing for owned release packages')
134
174
  .option('--dry-run [dryRun]', 'show npm trust changes without saving them')
175
+ .option('--bootstrap', 'publish missing npm placeholder packages before configuring trust')
135
176
  .option('--otp <otp>', 'npm one-time password for trust operations')
177
+ .option('--bootstrap-otp <otp>', 'npm one-time password for placeholder publishes during --bootstrap')
136
178
  .option('--skip-login', 'skip npm browser login before configuring trust')
137
179
  .action(async (options) => {
138
180
  const { releaseTrustPublisher } = await import('./release/index.js');
139
181
  await releaseTrustPublisher(await findRepoRoot(), {
140
182
  dryRun: booleanOption(options.dryRun),
183
+ bootstrap: options.bootstrap === true,
141
184
  otp: options.otp,
185
+ bootstrapOtp: options.bootstrapOtp,
142
186
  skipLogin: options.skipLogin === true,
143
187
  });
144
188
  });
189
+ const devenv = program.command('devenv').description('Manage the repository devenv shell');
190
+ devenv.command('update').action(async () => {
191
+ const { updateDevenv } = await import('./devenv/index.js');
192
+ await updateDevenv(await findRepoRoot());
193
+ });
194
+ devenv.command('reload').action(async () => {
195
+ const { reloadDevenv } = await import('./devenv/index.js');
196
+ await reloadDevenv(await findRepoRoot());
197
+ });
198
+ const nixpkgsOverlay = program.command('nixpkgs-overlay').description('Manage the repository nixpkgs overlay');
199
+ nixpkgsOverlay.command('update').action(async () => {
200
+ const { updateNixpkgsOverlay } = await import('./devenv/index.js');
201
+ await updateNixpkgsOverlay(await findRepoRoot());
202
+ });
203
+ const nx = program.command('nx').description('Nx workspace helpers');
204
+ nx.command('list-targets')
205
+ .description('List project:target entries for every Nx project')
206
+ .action(async () => {
207
+ const { listTargets } = await import('./nx/index.js');
208
+ await listTargets(await findRepoRoot());
209
+ });
210
+ nx.command('list-projects')
211
+ .description('List Nx projects matching filters')
212
+ .requiredOption('--with-target <target>', 'only include projects defining this target')
213
+ .action(async (options) => {
214
+ const { listProjects } = await import('./nx/index.js');
215
+ await listProjects(await findRepoRoot(), options);
216
+ });
217
+ nx.command('reset-cache')
218
+ .description('Run nx reset to clear Nx daemon and cache state')
219
+ .action(async () => {
220
+ const { resetCache } = await import('./nx/index.js');
221
+ await resetCache(await findRepoRoot());
222
+ });
223
+ nx.command('clean-cache')
224
+ .description('Remove local Nx cache directories when present')
225
+ .action(async () => {
226
+ const { cleanCache } = await import('./nx/index.js');
227
+ await cleanCache(await findRepoRoot());
228
+ });
145
229
  const githubCi = program.command('github-ci').description('GitHub Actions helpers');
146
230
  githubCi.command('cleanup-cache').action(async () => {
147
231
  const { cleanupGithubCiCache } = await import('./github-ci/index.js');
@@ -0,0 +1,10 @@
1
+ export interface DevenvCommandShell {
2
+ run(command: string, args: string[], cwd: string): Promise<void>;
3
+ remove(path: string): Promise<void>;
4
+ }
5
+ export declare function updateDevenv(root: string, shell?: DevenvCommandShell): Promise<void>;
6
+ export declare function reloadDevenv(root: string, shell?: DevenvCommandShell): Promise<void>;
7
+ export declare function updateNixpkgsOverlay(root: string, shell?: DevenvCommandShell): Promise<void>;
8
+ export declare function direnvRoot(root: string): string;
9
+ export declare function nixpkgsOverlayRoot(root: string): string;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/devenv/index.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AASD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,kBAAiC,GAAG,OAAO,CAAC,IAAI,CAAC,CAExG;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,kBAAiC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxG;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,kBAAiC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhH;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD"}
@@ -0,0 +1,27 @@
1
+ import { rm } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { run } from '../lib/run.js';
4
+ const defaultShell = {
5
+ run,
6
+ async remove(path) {
7
+ await rm(path, { recursive: true, force: true });
8
+ },
9
+ };
10
+ export async function updateDevenv(root, shell = defaultShell) {
11
+ await shell.run('devenv', ['update'], direnvRoot(root));
12
+ }
13
+ export async function reloadDevenv(root, shell = defaultShell) {
14
+ const cwd = direnvRoot(root);
15
+ await shell.remove(join(cwd, '.direnv'));
16
+ await shell.remove(join(cwd, '.devenv'));
17
+ await shell.run('direnv', ['reload'], cwd);
18
+ }
19
+ export async function updateNixpkgsOverlay(root, shell = defaultShell) {
20
+ await shell.run('nix', ['shell', 'nixpkgs#nvfetcher', '-c', 'nvfetcher', '-o', '_sources'], nixpkgsOverlayRoot(root));
21
+ }
22
+ export function direnvRoot(root) {
23
+ return join(root, 'tooling', 'direnv');
24
+ }
25
+ export function nixpkgsOverlayRoot(root) {
26
+ return join(direnvRoot(root), 'nixpkgs-overlay');
27
+ }
@@ -0,0 +1,4 @@
1
+ export declare const smoothBricksCodebasePackageName = "@smoothbricks/codebase";
2
+ export declare const cliPackageVersion: string;
3
+ export declare function isSmoothBricksCodebasePackageName(name: string | undefined): boolean;
4
+ //# sourceMappingURL=cli-package.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-package.d.ts","sourceRoot":"","sources":["../../src/lib/cli-package.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,+BAA+B,2BAA2B,CAAC;AACxE,eAAO,MAAM,iBAAiB,QAA0B,CAAC;AAWzD,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEnF"}
@@ -0,0 +1,15 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { readJsonObject, stringProperty } from './json.js';
3
+ export const smoothBricksCodebasePackageName = '@smoothbricks/codebase';
4
+ export const cliPackageVersion = readCliPackageVersion();
5
+ function readCliPackageVersion() {
6
+ const pkg = readJsonObject(fileURLToPath(new URL('../../package.json', import.meta.url)));
7
+ const version = pkg ? stringProperty(pkg, 'version') : null;
8
+ if (!version) {
9
+ throw new Error('Unable to read @smoothbricks/cli package version.');
10
+ }
11
+ return version;
12
+ }
13
+ export function isSmoothBricksCodebasePackageName(name) {
14
+ return name === smoothBricksCodebasePackageName;
15
+ }
package/dist/lib/run.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  export declare function run(command: string, args: string[], cwd: string, env?: Record<string, string>): Promise<void>;
2
2
  export declare function runStatus(command: string, args: string[], cwd: string, quiet?: boolean, env?: Record<string, string>): Promise<number>;
3
+ export declare function runResult(command: string, args: string[], cwd: string, env?: Record<string, string>): Promise<{
4
+ exitCode: number;
5
+ stdout: string;
6
+ stderr: string;
7
+ }>;
3
8
  export declare function findRepoRoot(): Promise<string>;
4
9
  export declare function decode(bytes: Uint8Array): string;
5
10
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/lib/run.ts"],"names":[],"mappings":"AAKA,wBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnH;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,KAAK,UAAQ,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAQjB;AAuCD,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAMpD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEhD"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/lib/run.ts"],"names":[],"mappings":"AAKA,wBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnH;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,KAAK,UAAQ,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAY/D;AAuCD,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAMpD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEhD"}
package/dist/lib/run.js CHANGED
@@ -17,6 +17,19 @@ export async function runStatus(command, args, cwd, quiet = false, env) {
17
17
  const result = quiet ? await shell.quiet() : await shell;
18
18
  return result.exitCode;
19
19
  }
20
+ export async function runResult(command, args, cwd, env) {
21
+ const invocation = resolveCommandInvocation(cwd, command, args);
22
+ let shell = $ `${invocation.command} ${invocation.args}`.cwd(cwd).nothrow().quiet();
23
+ if (env) {
24
+ shell = shell.env(mergeEnv(env));
25
+ }
26
+ const result = await shell;
27
+ return {
28
+ exitCode: result.exitCode,
29
+ stdout: decode(result.stdout),
30
+ stderr: decode(result.stderr),
31
+ };
32
+ }
20
33
  function mergeEnv(env) {
21
34
  const merged = {};
22
35
  for (const [key, value] of Object.entries(process.env)) {