@smoothbricks/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +289 -0
- package/bin/smoo +4 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +141 -0
- package/dist/github-ci/index.d.ts +11 -0
- package/dist/github-ci/index.d.ts.map +1 -0
- package/dist/github-ci/index.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/json.d.ts +13 -0
- package/dist/lib/json.d.ts.map +1 -0
- package/dist/lib/json.js +57 -0
- package/dist/lib/run.d.ts +5 -0
- package/dist/lib/run.d.ts.map +1 -0
- package/dist/lib/run.js +64 -0
- package/dist/lib/workspace.d.ts +23 -0
- package/dist/lib/workspace.d.ts.map +1 -0
- package/dist/lib/workspace.js +101 -0
- package/dist/monorepo/commit-msg.d.ts +2 -0
- package/dist/monorepo/commit-msg.d.ts.map +1 -0
- package/dist/monorepo/commit-msg.js +34 -0
- package/dist/monorepo/git-config.d.ts +2 -0
- package/dist/monorepo/git-config.d.ts.map +1 -0
- package/dist/monorepo/git-config.js +37 -0
- package/dist/monorepo/index.d.ts +19 -0
- package/dist/monorepo/index.d.ts.map +1 -0
- package/dist/monorepo/index.js +59 -0
- package/dist/monorepo/lockfile.d.ts +2 -0
- package/dist/monorepo/lockfile.d.ts.map +1 -0
- package/dist/monorepo/lockfile.js +34 -0
- package/dist/monorepo/managed-files.d.ts +8 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -0
- package/dist/monorepo/managed-files.js +130 -0
- package/dist/monorepo/nx-sync.d.ts +2 -0
- package/dist/monorepo/nx-sync.d.ts.map +1 -0
- package/dist/monorepo/nx-sync.js +5 -0
- package/dist/monorepo/package-hygiene.d.ts +3 -0
- package/dist/monorepo/package-hygiene.d.ts.map +1 -0
- package/dist/monorepo/package-hygiene.js +12 -0
- package/dist/monorepo/package-policy.d.ts +8 -0
- package/dist/monorepo/package-policy.d.ts.map +1 -0
- package/dist/monorepo/package-policy.js +279 -0
- package/dist/monorepo/packed-package.d.ts +2 -0
- package/dist/monorepo/packed-package.d.ts.map +1 -0
- package/dist/monorepo/packed-package.js +92 -0
- package/dist/monorepo/packs/index.d.ts +10 -0
- package/dist/monorepo/packs/index.d.ts.map +1 -0
- package/dist/monorepo/packs/index.js +87 -0
- package/dist/monorepo/runtime.d.ts +2 -0
- package/dist/monorepo/runtime.d.ts.map +1 -0
- package/dist/monorepo/runtime.js +37 -0
- package/dist/release/index.d.ts +24 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +325 -0
- package/managed/raw/.git-format-staged.yml +47 -0
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +66 -0
- package/managed/raw/tooling/git-hooks/commit-msg.sh +9 -0
- package/managed/raw/tooling/git-hooks/pre-commit.sh +19 -0
- package/managed/templates/github/actions/cache-nix-devenv/action.yml +79 -0
- package/managed/templates/github/actions/cache-node-modules/action.yml +13 -0
- package/managed/templates/github/actions/cache-nx/action.yml +21 -0
- package/managed/templates/github/actions/save-nix-devenv/action.yml +39 -0
- package/managed/templates/github/actions/setup-devenv/action.yml +63 -0
- package/managed/templates/github/workflows/ci.yml +98 -0
- package/managed/templates/github/workflows/publish.yml +136 -0
- package/package.json +68 -0
- package/src/cli.ts +149 -0
- package/src/github-ci/index.ts +196 -0
- package/src/index.ts +1 -0
- package/src/lib/json.ts +68 -0
- package/src/lib/run.ts +76 -0
- package/src/lib/workspace.ts +125 -0
- package/src/monorepo/commit-msg.ts +35 -0
- package/src/monorepo/git-config.ts +43 -0
- package/src/monorepo/index.ts +78 -0
- package/src/monorepo/lockfile.ts +37 -0
- package/src/monorepo/managed-files.ts +153 -0
- package/src/monorepo/nx-sync.ts +6 -0
- package/src/monorepo/package-hygiene.ts +14 -0
- package/src/monorepo/package-policy.ts +307 -0
- package/src/monorepo/packed-package.ts +101 -0
- package/src/monorepo/packs/index.ts +115 -0
- package/src/monorepo/runtime.ts +41 -0
- package/src/release/index.ts +394 -0
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# @smoothbricks/cli
|
|
2
|
+
|
|
3
|
+
`@smoothbricks/cli` provides `smoo`, the SmoothBricks monorepo automation CLI. It is the control plane for shared CI,
|
|
4
|
+
release, Git hook, package metadata, and publish validation conventions across SmoothBricks-style repositories.
|
|
5
|
+
|
|
6
|
+
The tool is intentionally convention-over-configuration. SmoothBricks repos use Nx, Bun, Nix, devenv, and direnv, so
|
|
7
|
+
`smoo` assumes those pieces exist instead of adding another local config file. Repos should be made correct by running
|
|
8
|
+
the mutating initialization path, then kept correct by the read-only validation path.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Add the CLI to the root workspace:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add -d @smoothbricks/cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The package exposes a Bun-native executable:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
smoo --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
SmoothBricks itself self-hosts before `dist` exists by using `tooling/smoo`, which imports `packages/cli/src/cli.ts`
|
|
25
|
+
directly. Published installs use the package binary in `bin/smoo`, which imports built JavaScript from `dist`.
|
|
26
|
+
|
|
27
|
+
## Core Commands
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
smoo monorepo init [--runtime-only] [--sync-runtime]
|
|
31
|
+
smoo monorepo validate
|
|
32
|
+
smoo monorepo update
|
|
33
|
+
smoo monorepo check
|
|
34
|
+
smoo monorepo diff
|
|
35
|
+
smoo monorepo validate-commit-msg <commit-msg-file>
|
|
36
|
+
smoo monorepo sync-bun-lockfile-versions
|
|
37
|
+
smoo monorepo list-public-projects
|
|
38
|
+
smoo monorepo validate-public-tags
|
|
39
|
+
smoo monorepo release-state
|
|
40
|
+
|
|
41
|
+
smoo release version --bump <auto|patch|minor|major|prerelease> [--dry-run]
|
|
42
|
+
smoo release publish --bump <auto|patch|minor|major|prerelease> [--tag <tag>] [--npm-tag <tag>] [--dry-run]
|
|
43
|
+
smoo release github-release --tags <tags> --bump <auto|patch|minor|major|prerelease> [--tag <tag>] [--npm-tag <tag>] [--dry-run]
|
|
44
|
+
smoo release trust-publisher [--dry-run] [--otp <code>] [--skip-login]
|
|
45
|
+
|
|
46
|
+
smoo github-ci cleanup-cache
|
|
47
|
+
smoo github-ci nx-smart --target <target> --name <check-name> --step <number>
|
|
48
|
+
smoo github-ci nx-run-many --targets <targets> [--projects <projects>]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Initialization
|
|
52
|
+
|
|
53
|
+
`smoo monorepo init` is the fix-first command. It mutates the repository toward the SmoothBricks convention instead of
|
|
54
|
+
only reporting drift.
|
|
55
|
+
|
|
56
|
+
It currently:
|
|
57
|
+
|
|
58
|
+
- Updates managed CI, release, hook, and formatting files.
|
|
59
|
+
- Ensures the local `tooling/smoo` source shim is executable when present.
|
|
60
|
+
- Synchronizes root runtime versions inside devenv, or when `--sync-runtime` is passed.
|
|
61
|
+
- Applies publish metadata defaults to `npm:public` packages.
|
|
62
|
+
- Normalizes internal workspace dependency ranges to `workspace:*`.
|
|
63
|
+
- Runs `sherif --fix --select highest` for broad monorepo package hygiene.
|
|
64
|
+
- Normalizes conditional export ordering so `types` comes first and `default` comes last.
|
|
65
|
+
- Adds `src` to package `files` when development-only exports intentionally point at source files.
|
|
66
|
+
|
|
67
|
+
The workspace dependency rule is generic. `smoo` does not know about individual package names such as `eslint-stdout`.
|
|
68
|
+
For every root or workspace `package.json`, if a dependency name matches an actual package in the same workspace, `smoo`
|
|
69
|
+
rewrites that range to `workspace:*`.
|
|
70
|
+
|
|
71
|
+
`smoo monorepo init --runtime-only` only synchronizes root runtime versions. It is used from direnv setup so
|
|
72
|
+
`packageManager`, `engines.node`, and `@types/node` stay aligned with the active devenv shell without duplicating that
|
|
73
|
+
policy in the direnv script.
|
|
74
|
+
|
|
75
|
+
Bun types are also a root runtime policy. The root `@types/bun` version follows the exact `packageManager` Bun version,
|
|
76
|
+
while package manifests are not forced to depend on Bun just because a test tsconfig opts into Bun globals. If a package
|
|
77
|
+
does explicitly declare `@types/bun`, `sherif` can keep duplicate declarations consistent, but `smoo` owns the semantic
|
|
78
|
+
root `bun@x.y.z` to `@types/bun x.y.z` relationship.
|
|
79
|
+
|
|
80
|
+
## Validation
|
|
81
|
+
|
|
82
|
+
`smoo monorepo validate` is the read-only gate. It should pass in local shells and CI after packages have been built.
|
|
83
|
+
|
|
84
|
+
It checks:
|
|
85
|
+
|
|
86
|
+
- Managed file drift.
|
|
87
|
+
- Root package policy.
|
|
88
|
+
- Root Bun type version matches the Bun package manager version.
|
|
89
|
+
- Nx release policy.
|
|
90
|
+
- Public package tag policy.
|
|
91
|
+
- Public package metadata.
|
|
92
|
+
- Workspace dependency ranges.
|
|
93
|
+
- `sherif` package hygiene.
|
|
94
|
+
- Packed public package artifacts with `publint`.
|
|
95
|
+
- Packed public package type resolution with the `attw` CLI.
|
|
96
|
+
|
|
97
|
+
The packed-package checks validate what npm users will install, not only the source tree. `smoo` packs each `npm:public`
|
|
98
|
+
package with Bun, runs `publint` on the tarball, then runs `attw` on the same tarball.
|
|
99
|
+
|
|
100
|
+
The `attw` check uses the `node16` profile and ignores the CJS-to-ESM warning. SmoothBricks packages are ESM-first, so
|
|
101
|
+
CommonJS consumers can use dynamic import. Node 10-only subpath failures are intentionally ignored because Node 10 is
|
|
102
|
+
not part of the supported package contract.
|
|
103
|
+
|
|
104
|
+
## Publishable Packages
|
|
105
|
+
|
|
106
|
+
Publishability is declared with an Nx tag:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"nx": {
|
|
111
|
+
"tags": ["npm:public"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This tag is the source of truth for release selection, metadata validation, and publish artifact validation. `smoo`
|
|
117
|
+
never relies on a second hardcoded package list.
|
|
118
|
+
|
|
119
|
+
Rules:
|
|
120
|
+
|
|
121
|
+
- Publishable packages must not be private.
|
|
122
|
+
- Private packages must not have `npm:public`.
|
|
123
|
+
- Every non-private workspace package must have `npm:public`.
|
|
124
|
+
- Public packages must define license metadata.
|
|
125
|
+
- Public packages must publish with `publishConfig.access = "public"`.
|
|
126
|
+
- Public package repositories must point at the root repo and include `repository.directory`.
|
|
127
|
+
- Public packages must define `files`.
|
|
128
|
+
- Public library packages must define `types`.
|
|
129
|
+
- Public packages must define either `exports` or `bin`.
|
|
130
|
+
|
|
131
|
+
`smoo monorepo init` fixes the metadata that can be derived from the root package. For example, it copies the root
|
|
132
|
+
license and repository URL, then sets each package's `repository.directory` from its workspace path.
|
|
133
|
+
|
|
134
|
+
## Managed Files
|
|
135
|
+
|
|
136
|
+
`smoo monorepo update` writes the managed files into a repository.
|
|
137
|
+
|
|
138
|
+
Managed files include:
|
|
139
|
+
|
|
140
|
+
- `.git-format-staged.yml`
|
|
141
|
+
- Git hook scripts under `tooling/git-hooks`
|
|
142
|
+
- direnv/GitHub Actions bootstrap scripts under `tooling/direnv`
|
|
143
|
+
- GitHub Actions workflows under `.github/workflows`
|
|
144
|
+
- Local composite GitHub Actions under `.github/actions`
|
|
145
|
+
|
|
146
|
+
When a managed target is a symlink, `smoo` leaves it alone. SmoothBricks uses symlinks back to `packages/cli/managed` so
|
|
147
|
+
changes to the CLI package are tested immediately. Downstream repos receive ordinary committed copies.
|
|
148
|
+
|
|
149
|
+
Use:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
smoo monorepo update
|
|
153
|
+
smoo monorepo check
|
|
154
|
+
smoo monorepo diff
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`check` fails when a managed file is missing or stale. `diff` reports drift without writing files.
|
|
158
|
+
|
|
159
|
+
## Git Hooks
|
|
160
|
+
|
|
161
|
+
The generated pre-commit hook runs `git-format-staged` from the repository root with `tooling`, `node_modules/.bin`, and
|
|
162
|
+
the devenv profile on `PATH`.
|
|
163
|
+
|
|
164
|
+
The generated commit-msg hook delegates conventional commit validation to:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
smoo monorepo validate-commit-msg <commit-msg-file>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This keeps hook behavior consistent with CI and avoids duplicating commit message parsing in shell.
|
|
171
|
+
|
|
172
|
+
## GitHub Actions
|
|
173
|
+
|
|
174
|
+
The generated workflows keep readable YAML and named top-level steps, while larger logic lives in `smoo` commands,
|
|
175
|
+
post-checkout composite actions, or the small pre-smoo bootstrap script. Checkout stays inline in each workflow because
|
|
176
|
+
repository-local composite actions do not exist until `actions/checkout` has populated the working tree.
|
|
177
|
+
|
|
178
|
+
CI uses explicit lint, test, and build phases. The publish workflow does the same before running release commands so
|
|
179
|
+
GitHub output remains readable even though Nx release also has its own `preVersionCommand` safety net.
|
|
180
|
+
|
|
181
|
+
CI status deeplinks depend on GitHub's top-level job step anchors. The generated CI workflow keeps `# Step N` comments
|
|
182
|
+
next to each top-level step, and the `smoo github-ci nx-smart --step <number>` values for lint, test, and build must
|
|
183
|
+
stay synchronized with those comments. Composite action internals do not change the top-level step numbers.
|
|
184
|
+
|
|
185
|
+
Managed CI setup is split across local composite actions:
|
|
186
|
+
|
|
187
|
+
- `setup-devenv` installs Nix, restores the Nix cache segment, imports the store NAR, restores `.devenv`/`.direnv` only
|
|
188
|
+
after an exact Nix cache hit, enables Cachix, installs devenv, restores `node_modules`, and builds the shell.
|
|
189
|
+
- `save-nix-devenv` runs under `always()`, calls `smoo github-ci cleanup-cache`, and explicitly saves cache segments
|
|
190
|
+
only when cleanup reports `cache-ready=true`.
|
|
191
|
+
- `cache-nix-devenv` is the shared restore/save primitive for the separate `nix` and `devenv` cache segments.
|
|
192
|
+
|
|
193
|
+
The cache split is intentional. The Nix segment contains the profile, Nix state, and exported store NAR. It is large and
|
|
194
|
+
keyed by the expensive shell closure inputs. The `.devenv`/`.direnv` segment is small, but it contains absolute
|
|
195
|
+
`/nix/store` pointers, so restoring it without the exact matching Nix store can leave metadata pointing at missing store
|
|
196
|
+
paths.
|
|
197
|
+
|
|
198
|
+
`smoo github-ci cleanup-cache` prepares the Nix segment for saving. It verifies and repairs the store, scans `.devenv`,
|
|
199
|
+
`.direnv`, and `~/.nix-profile` for embedded `/nix/store/...` references, protects the live paths with temporary GC
|
|
200
|
+
roots, runs garbage collection, and exports the resulting closure to `NIX_STORE_NAR`. The command writes
|
|
201
|
+
`cache-ready=true` or `cache-ready=false` to `GITHUB_OUTPUT` so the save action can avoid uploading incomplete caches.
|
|
202
|
+
|
|
203
|
+
The bootstrap script is intentionally small. It only handles work required before `smoo` can run in GitHub Actions:
|
|
204
|
+
|
|
205
|
+
- Restore Nix store cache state, clearing `.devenv` and `.direnv` if the matching NAR is missing or fails to import.
|
|
206
|
+
- Install `devenv`.
|
|
207
|
+
- Build the devenv shell and add repo-local tooling to `GITHUB_PATH`.
|
|
208
|
+
|
|
209
|
+
## Releases
|
|
210
|
+
|
|
211
|
+
Release commands wrap Nx release but keep SmoothBricks policy in one place.
|
|
212
|
+
|
|
213
|
+
Versioning:
|
|
214
|
+
|
|
215
|
+
- `--bump auto` uses Nx conventional-commit versioning.
|
|
216
|
+
- `--bump patch|minor|major|prerelease` forces the release specifier.
|
|
217
|
+
- Release projects are discovered from `npm:public` packages.
|
|
218
|
+
- Nx release config must use `currentVersionResolver: "git-tag"` with `fallbackCurrentVersionResolver: "disk"`.
|
|
219
|
+
Conventional-commit versioning requires git tags as the primary source, while the disk fallback supports initial
|
|
220
|
+
releases before package tags exist.
|
|
221
|
+
- `smoo release version` lets Nx own local package versioning, `bun.lock` updates, the release commit, and annotated
|
|
222
|
+
tags. smoo records `HEAD` before and after Nx runs, then pushes with `git push --follow-tags --atomic` only if Nx
|
|
223
|
+
created a new release commit. This keeps the release commit and tags atomic without pushing an older checkout on
|
|
224
|
+
retries where Nx has nothing new to commit.
|
|
225
|
+
- Reruns call Nx versioning again rather than repairing tags in `smoo`. Nx's git-tag current-version resolver plus disk
|
|
226
|
+
fallback handles already-tagged releases and first releases before package tags exist; smoo skips the remote push when
|
|
227
|
+
Nx leaves `HEAD` unchanged.
|
|
228
|
+
|
|
229
|
+
Publishing:
|
|
230
|
+
|
|
231
|
+
- `prerelease` publishes with npm dist-tag `next`.
|
|
232
|
+
- Stable bumps publish with npm dist-tag `latest`.
|
|
233
|
+
- Conflicting explicit dist-tags are rejected.
|
|
234
|
+
- `smoo release publish` checks every current `name@version` for `npm:public` packages before publishing. Already
|
|
235
|
+
published versions are skipped, so reruns after auth or network failures retry only the package versions npm does not
|
|
236
|
+
have yet.
|
|
237
|
+
- Publish uses `bun pm pack` to create package tarballs, then publishes those tarballs with latest npm CLI and
|
|
238
|
+
`--provenance`. Bun pack resolves internal `workspace:*` dependency ranges to real versions in the tarball manifest;
|
|
239
|
+
smoo fails before publish if a packed manifest still contains `workspace:`.
|
|
240
|
+
- npm CLI owns publish authentication. When trusted publishing is configured, npm uses GitHub Actions OIDC from the
|
|
241
|
+
workflow's `id-token: write` permission. Before trusted publishing exists, the managed workflow passes
|
|
242
|
+
`secrets.NPM_TOKEN` as `NODE_AUTH_TOKEN` and smoo writes a temporary npm user config for bootstrap publishing.
|
|
243
|
+
- `smoo release trust-publisher` configures npm trusted publishing for every `npm:public` package. It uses the root
|
|
244
|
+
`package.json` `repository.url` as the GitHub `owner/repo`, uses `publish.yml` as the trusted workflow, and runs
|
|
245
|
+
`npm trust` through `nix shell nixpkgs#nodejs_latest` because the Lambda-pinned Node 24/npm toolchain may lag the npm
|
|
246
|
+
CLI feature. By default it runs `npm login --auth-type=web` first so npm can open a browser login; pass `--skip-login`
|
|
247
|
+
when the current npm session is already authenticated. Packages must already exist on npm before trust can be
|
|
248
|
+
configured. npm may still require operation-level 2FA for `npm trust`; smoo prompts for a hidden OTP per package, or
|
|
249
|
+
you can pass `--otp <code>` for non-interactive use.
|
|
250
|
+
|
|
251
|
+
GitHub Releases:
|
|
252
|
+
|
|
253
|
+
- `smoo release github-release` creates or updates releases for the selected tags.
|
|
254
|
+
- `latest` status follows the derived npm dist-tag.
|
|
255
|
+
|
|
256
|
+
The release flow is designed to be rerun after partial failure. Nx owns local version/tag behavior, `smoo` owns the
|
|
257
|
+
guarded atomic git push, and npm registry state makes publishing idempotent across retries.
|
|
258
|
+
|
|
259
|
+
## Why This Shape
|
|
260
|
+
|
|
261
|
+
The important design goal is one source of truth per convention:
|
|
262
|
+
|
|
263
|
+
- Nx `npm:public` tags decide what is publishable.
|
|
264
|
+
- Managed files decide what generated CI and hooks should look like.
|
|
265
|
+
- Root package metadata provides defaults for public packages.
|
|
266
|
+
- Actual workspace package names decide which dependency ranges become `workspace:*`.
|
|
267
|
+
- `sherif` handles broad package hygiene.
|
|
268
|
+
- `publint` and `attw` validate real packed artifacts.
|
|
269
|
+
|
|
270
|
+
This keeps `smoo` small where external tools already do the job, but keeps SmoothBricks-specific policy native where
|
|
271
|
+
generic tools do not know the repo contract. In particular, `sherif` is useful for package hygiene, but it does not know
|
|
272
|
+
SmoothBricks publish metadata, release tags, generated workflow files, or Nx release policy. Those remain `smoo`
|
|
273
|
+
conventions.
|
|
274
|
+
|
|
275
|
+
## Local Verification
|
|
276
|
+
|
|
277
|
+
Typical verification after changing `smoo`:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
nx typecheck @smoothbricks/cli
|
|
281
|
+
nx lint @smoothbricks/cli
|
|
282
|
+
smoo monorepo validate
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
If validating packages with Zig build steps from outside the devenv shell, add Zig explicitly:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
nix shell nixpkgs#zig -c nx run-many -t build --projects=<public-projects>
|
|
289
|
+
```
|
package/bin/smoo
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +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"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Command, CommanderError } from 'commander';
|
|
2
|
+
import { findRepoRoot } from './lib/run.js';
|
|
3
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
4
|
+
const program = buildProgram();
|
|
5
|
+
try {
|
|
6
|
+
await program.parseAsync(argv, { from: 'user' });
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (error instanceof CommanderError) {
|
|
10
|
+
if (error.code !== 'commander.helpDisplayed') {
|
|
11
|
+
process.exitCode = error.exitCode;
|
|
12
|
+
}
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
console.error(message);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function buildProgram() {
|
|
21
|
+
const program = new Command();
|
|
22
|
+
program.name('smoo').description('SmoothBricks monorepo tooling').exitOverride().showHelpAfterError();
|
|
23
|
+
const monorepo = program.command('monorepo').description('Manage SmoothBricks-style monorepos');
|
|
24
|
+
monorepo
|
|
25
|
+
.command('init')
|
|
26
|
+
.option('--runtime-only', 'only sync root Bun/Node runtime metadata')
|
|
27
|
+
.option('--sync-runtime', 'sync root Bun/Node runtime metadata outside devenv')
|
|
28
|
+
.action(async (options) => {
|
|
29
|
+
const { initMonorepo } = await import('./monorepo/index.js');
|
|
30
|
+
await initMonorepo(await findRepoRoot(), options);
|
|
31
|
+
});
|
|
32
|
+
monorepo
|
|
33
|
+
.command('validate')
|
|
34
|
+
.option('--fail-fast', 'stop after the first failing validation pack')
|
|
35
|
+
.action(async (options) => {
|
|
36
|
+
const { validateMonorepo } = await import('./monorepo/index.js');
|
|
37
|
+
await validateMonorepo(await findRepoRoot(), options);
|
|
38
|
+
});
|
|
39
|
+
monorepo.command('update').action(async () => {
|
|
40
|
+
const { updateManagedFiles } = await import('./monorepo/index.js');
|
|
41
|
+
updateManagedFiles(await findRepoRoot());
|
|
42
|
+
});
|
|
43
|
+
monorepo.command('check').action(async () => {
|
|
44
|
+
const { checkManagedFiles } = await import('./monorepo/index.js');
|
|
45
|
+
checkManagedFiles(await findRepoRoot());
|
|
46
|
+
});
|
|
47
|
+
monorepo.command('diff').action(async () => {
|
|
48
|
+
const { diffManagedFiles } = await import('./monorepo/index.js');
|
|
49
|
+
diffManagedFiles(await findRepoRoot());
|
|
50
|
+
});
|
|
51
|
+
monorepo.command('validate-commit-msg <commitMsgFile>').action(async (commitMsgFile) => {
|
|
52
|
+
const { validateCommitMessageFile } = await import('./monorepo/index.js');
|
|
53
|
+
validateCommitMessageFile(commitMsgFile);
|
|
54
|
+
});
|
|
55
|
+
monorepo.command('sync-bun-lockfile-versions').action(async () => {
|
|
56
|
+
const { syncBunLockfileVersions } = await import('./monorepo/index.js');
|
|
57
|
+
syncBunLockfileVersions(await findRepoRoot());
|
|
58
|
+
});
|
|
59
|
+
monorepo.command('list-public-projects').action(async () => {
|
|
60
|
+
const { listPublicProjects } = await import('./monorepo/index.js');
|
|
61
|
+
console.log(listPublicProjects(await findRepoRoot()));
|
|
62
|
+
});
|
|
63
|
+
monorepo.command('validate-public-tags').action(async () => {
|
|
64
|
+
const { validatePublicPackageTags } = await import('./monorepo/index.js');
|
|
65
|
+
validatePublicPackageTags(await findRepoRoot());
|
|
66
|
+
});
|
|
67
|
+
monorepo.command('release-state').action(async () => {
|
|
68
|
+
const { printReleaseState } = await import('./release/index.js');
|
|
69
|
+
await printReleaseState(await findRepoRoot());
|
|
70
|
+
});
|
|
71
|
+
const release = program.command('release').description('Version, publish, and create GitHub Releases');
|
|
72
|
+
release
|
|
73
|
+
.command('version')
|
|
74
|
+
.option('--bump <bump>', 'auto, patch, minor, major, or prerelease', 'auto')
|
|
75
|
+
.option('--dry-run [dryRun]', 'run without writing versions, tags, or pushes')
|
|
76
|
+
.action(async (options) => {
|
|
77
|
+
const { releaseVersion } = await import('./release/index.js');
|
|
78
|
+
await releaseVersion(await findRepoRoot(), { bump: options.bump, dryRun: booleanOption(options.dryRun) });
|
|
79
|
+
});
|
|
80
|
+
release
|
|
81
|
+
.command('publish')
|
|
82
|
+
.option('--bump <bump>', 'auto, patch, minor, major, or prerelease', 'auto')
|
|
83
|
+
.option('--tag <tag>', 'explicit npm dist-tag; must match bump-derived tag')
|
|
84
|
+
.option('--npm-tag <npmTag>', 'explicit npm dist-tag; must match bump-derived tag')
|
|
85
|
+
.option('--dry-run [dryRun]', 'run without publishing')
|
|
86
|
+
.action(async (options) => {
|
|
87
|
+
const { releasePublish } = await import('./release/index.js');
|
|
88
|
+
await releasePublish(await findRepoRoot(), { ...options, dryRun: booleanOption(options.dryRun) });
|
|
89
|
+
});
|
|
90
|
+
release
|
|
91
|
+
.command('github-release')
|
|
92
|
+
.option('--tags <tags>', 'space-separated release tags')
|
|
93
|
+
.option('--bump <bump>', 'auto, patch, minor, major, or prerelease', 'auto')
|
|
94
|
+
.option('--tag <tag>', 'explicit npm dist-tag; must match bump-derived tag')
|
|
95
|
+
.option('--npm-tag <npmTag>', 'explicit npm dist-tag; must match bump-derived tag')
|
|
96
|
+
.option('--dry-run [dryRun]', 'skip GitHub Release writes')
|
|
97
|
+
.action(async (options) => {
|
|
98
|
+
const { releaseGithubRelease } = await import('./release/index.js');
|
|
99
|
+
await releaseGithubRelease(await findRepoRoot(), { ...options, dryRun: booleanOption(options.dryRun) });
|
|
100
|
+
});
|
|
101
|
+
release
|
|
102
|
+
.command('trust-publisher')
|
|
103
|
+
.description('Configure npm trusted publishing for public packages')
|
|
104
|
+
.option('--dry-run [dryRun]', 'show npm trust changes without saving them')
|
|
105
|
+
.option('--otp <otp>', 'npm one-time password for trust operations')
|
|
106
|
+
.option('--skip-login', 'skip npm browser login before configuring trust')
|
|
107
|
+
.action(async (options) => {
|
|
108
|
+
const { releaseTrustPublisher } = await import('./release/index.js');
|
|
109
|
+
await releaseTrustPublisher(await findRepoRoot(), {
|
|
110
|
+
dryRun: booleanOption(options.dryRun),
|
|
111
|
+
otp: options.otp,
|
|
112
|
+
skipLogin: options.skipLogin === true,
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
const githubCi = program.command('github-ci').description('GitHub Actions helpers');
|
|
116
|
+
githubCi.command('cleanup-cache').action(async () => {
|
|
117
|
+
const { cleanupGithubCiCache } = await import('./github-ci/index.js');
|
|
118
|
+
await cleanupGithubCiCache(await findRepoRoot());
|
|
119
|
+
});
|
|
120
|
+
githubCi
|
|
121
|
+
.command('nx-smart')
|
|
122
|
+
.requiredOption('--target <target>')
|
|
123
|
+
.option('--name <name>')
|
|
124
|
+
.option('--step <step>')
|
|
125
|
+
.action(async (options) => {
|
|
126
|
+
const { githubCiNxSmart } = await import('./github-ci/index.js');
|
|
127
|
+
await githubCiNxSmart(await findRepoRoot(), options);
|
|
128
|
+
});
|
|
129
|
+
githubCi
|
|
130
|
+
.command('nx-run-many')
|
|
131
|
+
.requiredOption('--targets <targets>')
|
|
132
|
+
.option('--projects <projects>')
|
|
133
|
+
.action(async (options) => {
|
|
134
|
+
const { githubCiNxRunMany } = await import('./github-ci/index.js');
|
|
135
|
+
await githubCiNxRunMany(await findRepoRoot(), options);
|
|
136
|
+
});
|
|
137
|
+
return program;
|
|
138
|
+
}
|
|
139
|
+
function booleanOption(value) {
|
|
140
|
+
return value === true || value === 'true';
|
|
141
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function cleanupGithubCiCache(root: string): Promise<void>;
|
|
2
|
+
export declare function githubCiNxSmart(root: string, options: {
|
|
3
|
+
target: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
step?: string;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function githubCiNxRunMany(root: string, options: {
|
|
8
|
+
targets: string;
|
|
9
|
+
projects?: string;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/github-ci/index.ts"],"names":[],"mappings":"AAMA,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBtE;AAmFD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAMpH"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { appendFile, mkdtemp, realpath, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { $ } from 'bun';
|
|
5
|
+
import { decode, run, runStatus } from '../lib/run.js';
|
|
6
|
+
export async function cleanupGithubCiCache(root) {
|
|
7
|
+
const githubOutput = process.env.GITHUB_OUTPUT;
|
|
8
|
+
const markCacheReady = async (ready) => {
|
|
9
|
+
if (githubOutput) {
|
|
10
|
+
await appendFile(githubOutput, `cache-ready=${ready ? 'true' : 'false'}\n`);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const nar = process.env.NIX_STORE_NAR;
|
|
14
|
+
if (!nar) {
|
|
15
|
+
console.warn('NIX_STORE_NAR is not set; skipping Nix cache save.');
|
|
16
|
+
await markCacheReady(false);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const nixStore = '/nix/var/nix/profiles/default/bin/nix-store';
|
|
20
|
+
const devenvProfile = `${root}/tooling/direnv/.devenv/profile`;
|
|
21
|
+
if (!existsSync(devenvProfile)) {
|
|
22
|
+
console.warn(`${devenvProfile} is missing; skipping Nix cache save.`);
|
|
23
|
+
await markCacheReady(false);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await runStatus(nixStore, ['--verify', '--check-contents', '--repair'], root);
|
|
27
|
+
await exportNixStoreCache(root, nar, nixStore, devenvProfile);
|
|
28
|
+
await markCacheReady(true);
|
|
29
|
+
}
|
|
30
|
+
async function exportNixStoreCache(root, nar, nixStore, devenvProfile) {
|
|
31
|
+
const gcRootDir = '/nix/var/nix/gcroots/smoothbricks-cache-roots';
|
|
32
|
+
const tmpDir = await mkdtemp(join(dirname(nar), '.smoo-nix-cache-'));
|
|
33
|
+
const tmpNar = join(tmpDir, 'nix-store.nar');
|
|
34
|
+
const roots = new Set();
|
|
35
|
+
try {
|
|
36
|
+
await $ `rm -f ${nar}`.cwd(root);
|
|
37
|
+
await $ `sudo rm -rf ${gcRootDir}`.cwd(root);
|
|
38
|
+
await $ `sudo mkdir -p ${gcRootDir}`.cwd(root);
|
|
39
|
+
// The Nix cache must include every live store path referenced by the
|
|
40
|
+
// restored shell state, not just the devenv profile. .direnv stores paths to
|
|
41
|
+
// derivations like devenv-shell.drv; omitting those makes a cache hit restore
|
|
42
|
+
// metadata that points at missing store paths.
|
|
43
|
+
await addRoot(roots, devenvProfile);
|
|
44
|
+
const home = process.env.HOME;
|
|
45
|
+
if (home) {
|
|
46
|
+
const nixProfile = join(home, '.nix-profile');
|
|
47
|
+
await addRoot(roots, nixProfile);
|
|
48
|
+
await addReferencesFrom(roots, nixProfile, root);
|
|
49
|
+
}
|
|
50
|
+
await addReferencesFrom(roots, join(root, 'tooling/direnv/.devenv'), root);
|
|
51
|
+
await addReferencesFrom(roots, join(root, 'tooling/direnv/.direnv'), root);
|
|
52
|
+
const rootLinks = [];
|
|
53
|
+
let index = 0;
|
|
54
|
+
for (const target of roots) {
|
|
55
|
+
const link = `${gcRootDir}/root-${index}`;
|
|
56
|
+
await $ `sudo ln -s ${target} ${link}`.cwd(root);
|
|
57
|
+
rootLinks.push(link);
|
|
58
|
+
index += 1;
|
|
59
|
+
}
|
|
60
|
+
if (rootLinks.length === 0) {
|
|
61
|
+
throw new Error('No live Nix store roots found; skipping Nix cache save.');
|
|
62
|
+
}
|
|
63
|
+
await $ `nix-collect-garbage --quiet`.cwd(root);
|
|
64
|
+
const closureOutput = await $ `sudo ${nixStore} -qR ${rootLinks}`.cwd(root).quiet();
|
|
65
|
+
const closure = decode(closureOutput.stdout)
|
|
66
|
+
.split('\n')
|
|
67
|
+
.map((line) => line.trim())
|
|
68
|
+
.filter(Boolean);
|
|
69
|
+
if (closure.length === 0) {
|
|
70
|
+
throw new Error('No Nix store closure paths found; skipping Nix cache save.');
|
|
71
|
+
}
|
|
72
|
+
await $ `sudo ${nixStore} --export --quiet ${closure} > ${tmpNar}`.cwd(root);
|
|
73
|
+
await $ `test -s ${tmpNar}`.cwd(root);
|
|
74
|
+
await rename(tmpNar, nar);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await $ `sudo rm -rf ${gcRootDir}`.cwd(root).nothrow();
|
|
78
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function addRoot(roots, candidate) {
|
|
82
|
+
if (!existsSync(candidate)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const target = await realpath(candidate);
|
|
86
|
+
if (existsSync(target)) {
|
|
87
|
+
roots.add(target);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function addReferencesFrom(roots, path, cwd) {
|
|
91
|
+
if (!existsSync(path)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const storePathPattern = '/nix/store/[a-z0-9]{32}-[A-Za-z0-9+._?=-]+';
|
|
95
|
+
const result = await $ `grep -rahoE ${storePathPattern} ${path}`.cwd(cwd).quiet().nothrow();
|
|
96
|
+
for (const line of decode(result.stdout).split('\n')) {
|
|
97
|
+
const candidate = line.trim();
|
|
98
|
+
if (candidate && existsSync(candidate)) {
|
|
99
|
+
roots.add(candidate);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export async function githubCiNxSmart(root, options) {
|
|
104
|
+
const name = options.name ?? options.target;
|
|
105
|
+
const step = options.step ?? '';
|
|
106
|
+
await createGithubStatus(name, step);
|
|
107
|
+
const mode = process.env.GITHUB_EVENT_NAME === 'push' && process.env.GITHUB_REF_NAME === 'main' ? 'run-many' : 'affected';
|
|
108
|
+
const nxArgs = mode === 'run-many'
|
|
109
|
+
? ['run-many', '-t', options.target, '--parallel=5']
|
|
110
|
+
: ['affected', '-t', options.target, '--parallel=5'];
|
|
111
|
+
const status = await runStatus('nx', nxArgs, root);
|
|
112
|
+
await updateGithubStatus(name, status === 0 ? 'success' : 'failure', step);
|
|
113
|
+
if (status !== 0) {
|
|
114
|
+
throw new Error(`nx ${nxArgs.join(' ')} failed with exit code ${status}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function githubCiNxRunMany(root, options) {
|
|
118
|
+
const nxArgs = ['run-many', '-t', options.targets, '--parallel=5'];
|
|
119
|
+
if (options.projects) {
|
|
120
|
+
nxArgs.push(`--projects=${options.projects}`);
|
|
121
|
+
}
|
|
122
|
+
await run('nx', nxArgs, root);
|
|
123
|
+
}
|
|
124
|
+
async function createGithubStatus(name, step) {
|
|
125
|
+
await postGithubStatus(name, 'pending', `Running ${name}...`, step);
|
|
126
|
+
}
|
|
127
|
+
async function updateGithubStatus(name, state, step) {
|
|
128
|
+
const suffix = state === 'success' ? 'passed' : state === 'failure' ? 'failed' : 'errored';
|
|
129
|
+
await postGithubStatus(name, state, `${name} ${suffix}`, step);
|
|
130
|
+
}
|
|
131
|
+
async function postGithubStatus(name, state, description, step) {
|
|
132
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
133
|
+
const sha = process.env.GITHUB_SHA;
|
|
134
|
+
if (!repository || !sha) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const targetUrl = await getGithubStepUrl(step);
|
|
138
|
+
const args = [
|
|
139
|
+
'api',
|
|
140
|
+
'--method',
|
|
141
|
+
'POST',
|
|
142
|
+
'-H',
|
|
143
|
+
'Accept: application/vnd.github+json',
|
|
144
|
+
`/repos/${repository}/statuses/${sha}`,
|
|
145
|
+
'-f',
|
|
146
|
+
`state=${state}`,
|
|
147
|
+
'-f',
|
|
148
|
+
`context=${name}`,
|
|
149
|
+
'-f',
|
|
150
|
+
`description=${description}`,
|
|
151
|
+
];
|
|
152
|
+
if (targetUrl) {
|
|
153
|
+
args.push('-f', `target_url=${targetUrl}`);
|
|
154
|
+
}
|
|
155
|
+
await run('gh', args, process.cwd());
|
|
156
|
+
}
|
|
157
|
+
async function getGithubStepUrl(step) {
|
|
158
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
159
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
160
|
+
const job = process.env.GITHUB_JOB;
|
|
161
|
+
if (!repository || !runId || !job) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const result = await $ `gh api -H ${'Accept: application/vnd.github+json'} ${`/repos/${repository}/actions/runs/${runId}/jobs`} --jq ${`.jobs[] | select(.name == "${job}") | .id`}`
|
|
165
|
+
.quiet()
|
|
166
|
+
.nothrow();
|
|
167
|
+
const jobId = decode(result.stdout).trim();
|
|
168
|
+
if (!jobId) {
|
|
169
|
+
return `https://github.com/${repository}/actions/runs/${runId}`;
|
|
170
|
+
}
|
|
171
|
+
return step
|
|
172
|
+
? `https://github.com/${repository}/actions/runs/${runId}/job/${jobId}#step:${step}:1`
|
|
173
|
+
: `https://github.com/${repository}/actions/runs/${runId}/job/${jobId}`;
|
|
174
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runCli } from './cli.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { hasOwn, hasOwnString, isRecord } from '@smoothbricks/validation';
|
|
2
|
+
export { hasOwn, hasOwnString, isRecord };
|
|
3
|
+
export declare function stringProperty(record: Record<string, unknown>, key: string): string | null;
|
|
4
|
+
export declare function recordProperty(record: Record<string, unknown>, key: string): Record<string, unknown> | null;
|
|
5
|
+
export declare function getOrCreateRecord(record: Record<string, unknown>, key: string): Record<string, unknown>;
|
|
6
|
+
export declare function setStringProperty(record: Record<string, unknown>, key: string, value: string): boolean;
|
|
7
|
+
export declare function setMissingStringProperty(record: Record<string, unknown>, key: string, value: string): boolean;
|
|
8
|
+
export declare function requiredJsonObject(path: string): Record<string, unknown>;
|
|
9
|
+
export declare function readJsonObject(path: string): Record<string, unknown> | null;
|
|
10
|
+
export declare function writeJsonObject(path: string, value: Record<string, unknown>): void;
|
|
11
|
+
export declare function jsonObjectText(value: Record<string, unknown>): string;
|
|
12
|
+
export declare function readJson(path: string): unknown;
|
|
13
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/lib/json.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;AAE1C,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG1F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAG3G;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQvG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAMtG;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAM7G;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMxE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAG3E;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAElF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAErE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAK9C"}
|