@grest-ts/cli 0.0.27 → 0.0.31

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 (3) hide show
  1. package/README.md +11 -21
  2. package/index.mjs +78 -14
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -5,31 +5,21 @@
5
5
 
6
6
  # @grest-ts/cli
7
7
 
8
- CLI for managing grest-ts in a project.
9
-
10
- ## Why
11
-
12
- grest-ts is single-version: every `@grest-ts/*` package in a project must be at the same version. Inter-package peer dependencies are pinned exactly, so bumping one package forces bumping all of them — and once a `package-lock.json` is in play, npm's resolver can't reconcile the cross-package pins through a partial update.
13
-
14
- This CLI does the bump atomically.
8
+ CLI for keeping every `@grest-ts/*` package in your project on the same version. Full guide: [Guide → CLI](@guide/cli).
15
9
 
16
10
  ## Usage
17
11
 
18
- Run from your project root (single-package or npm workspaces monorepo):
19
-
20
12
  ```bash
21
- npx @grest-ts/cli upgrade # bumps to "latest" dist-tag
22
- npx @grest-ts/cli upgrade 0.0.27 # bumps to a specific version
23
- npx @grest-ts/cli upgrade next # bumps to the "next" dist-tag
24
- npx @grest-ts/cli upgrade --dry-run # preview without writing
13
+ npx @grest-ts/cli update # bump to "latest"
14
+ npx @grest-ts/cli update 0.0.30 # bump to a specific version
15
+ npx @grest-ts/cli update next # bump to a dist-tag
16
+ npx @grest-ts/cli update --dry-run # preview only
25
17
  ```
26
18
 
27
- ## What it does
19
+ ## Install (optional)
28
20
 
29
- 1. Discovers every `package.json` (root + workspaces).
30
- 2. Collects every distinct `@grest-ts/*` dep referenced in `dependencies` and `devDependencies`.
31
- 3. Resolves the target version (concrete version or dist-tag).
32
- 4. Pre-flight: verifies every collected package is published at the target version. Fails fast if any are missing.
33
- 5. Rewrites every `@grest-ts/*` entry to `^<target-version>` (canonical form — overwrites any pre-existing range).
34
- 6. Deletes `package-lock.json`.
35
- 7. Runs `npm install`.
21
+ `npx` works without installing anything. To pin a CLI version per-project:
22
+
23
+ ```bash
24
+ npm install -D @grest-ts/cli
25
+ ```
package/index.mjs CHANGED
@@ -5,9 +5,10 @@ import { join, resolve, dirname } from "path"
5
5
  import { spawnSync } from "child_process"
6
6
 
7
7
  const GREST_PREFIX = "@grest-ts/"
8
+ const DEP_BLOCKS = ["dependencies", "devDependencies", "peerDependencies"]
8
9
 
9
10
  const COMMANDS = {
10
- upgrade: runUpgrade,
11
+ update: runUpdate,
11
12
  help: runHelp,
12
13
  }
13
14
 
@@ -36,16 +37,18 @@ Usage:
36
37
  grest-ts <command> [options]
37
38
 
38
39
  Commands:
39
- upgrade [version] Atomically update all @grest-ts/* deps to a single version.
40
- Version can be a semver (e.g. 0.0.27) or a dist-tag
41
- (latest, next). Defaults to "latest".
40
+ update [version] Atomically update all @grest-ts/* deps to a single version.
41
+ Version can be a semver (e.g. 0.0.30) or a dist-tag
42
+ (latest, next). Defaults to "latest". Also walks the
43
+ published peer-dep graph and silently adds any missing
44
+ @grest-ts/* peers to your dependencies.
42
45
  Flags:
43
46
  --dry-run Show what would change without writing.
44
47
  help Show this message.
45
48
  `)
46
49
  }
47
50
 
48
- async function runUpgrade(args) {
51
+ async function runUpdate(args) {
49
52
  const dryRun = args.includes("--dry-run") || args.includes("--dry")
50
53
  const positional = args.filter(a => !a.startsWith("--"))
51
54
  const versionArg = positional[0] || "latest"
@@ -65,7 +68,7 @@ async function runUpgrade(args) {
65
68
  const grestDeps = new Set()
66
69
  for (const p of allPkgPaths) {
67
70
  const pkg = readJson(p)
68
- for (const block of ["dependencies", "devDependencies"]) {
71
+ for (const block of DEP_BLOCKS) {
69
72
  if (!pkg[block]) continue
70
73
  for (const key of Object.keys(pkg[block])) {
71
74
  if (key.startsWith(GREST_PREFIX)) grestDeps.add(key)
@@ -79,10 +82,12 @@ async function runUpgrade(args) {
79
82
  }
80
83
  console.log(`Found ${grestDeps.size} unique @grest-ts/* dependency name(s).`)
81
84
 
82
- // 3. Resolve target version (handle dist-tags)
85
+ // 3. Resolve target version (handle dist-tags). Pin exactly — grest-ts
86
+ // publishes all packages in lockstep with each other's peers declared
87
+ // at the exact version, so the consumer must match exactly.
83
88
  const targetVersion = await resolveTargetVersion(versionArg)
84
- const canonicalRange = `^${targetVersion}`
85
- console.log(`Target version: ${targetVersion} (writing as "${canonicalRange}")`)
89
+ const canonicalRange = targetVersion
90
+ console.log(`Target version: ${targetVersion}`)
86
91
 
87
92
  // 4. Pre-flight: every grest-ts package must be published at target version.
88
93
  // grest-ts is single-version across all packages — partial publishes would
@@ -101,14 +106,66 @@ async function runUpgrade(args) {
101
106
  )
102
107
  }
103
108
 
104
- // 5. Rewrite every package.json normalize every @grest-ts/* range to canonical form.
109
+ // 5. Walk the peer-dep graph at the target version and silently add any
110
+ // missing @grest-ts/* peers to the root project. Each grest-ts package
111
+ // declares its internal cross-deps as peerDependencies — when a consumer
112
+ // adds @grest-ts/http but forgets @grest-ts/schema, npm warns and the
113
+ // framework's runtime dedup check has nothing to dedup against. Better
114
+ // to fix it here.
115
+ console.log("Walking peer-dep graph to discover missing peers...")
116
+ const peerCache = new Map()
117
+ const expandedDeps = new Set(grestDeps)
118
+ const queue = [...grestDeps]
119
+ while (queue.length > 0) {
120
+ const name = queue.shift()
121
+ let manifest
122
+ try {
123
+ manifest = await fetchJson(`https://registry.npmjs.org/${name}/${targetVersion}`)
124
+ } catch (e) {
125
+ throw new Error(`Failed to fetch ${name}@${targetVersion} manifest: ${e.message}`)
126
+ }
127
+ const peers = manifest.peerDependencies || {}
128
+ peerCache.set(name, peers)
129
+ for (const peerName of Object.keys(peers)) {
130
+ if (peerName.startsWith(GREST_PREFIX) && !expandedDeps.has(peerName)) {
131
+ expandedDeps.add(peerName)
132
+ queue.push(peerName)
133
+ }
134
+ }
135
+ }
136
+
137
+ // Anything in expandedDeps not already in the root package.json gets added
138
+ // to its dependencies block at the target version.
139
+ const autoAdded = []
140
+ for (const name of expandedDeps) {
141
+ if (!isInAnyBlock(rootPkg, name)) {
142
+ autoAdded.push(name)
143
+ }
144
+ }
145
+ if (autoAdded.length > 0) {
146
+ console.log(`Adding ${autoAdded.length} missing peer(s) to ${relativeTo(cwd, rootPkgPath)}:`)
147
+ for (const name of autoAdded) console.log(` + ${name}`)
148
+ }
149
+
150
+ // 6. Rewrite every package.json — normalize every @grest-ts/* range to canonical form.
151
+ // For the root, also insert any auto-discovered missing peers into "dependencies".
105
152
  let totalChanged = 0
106
153
  let filesChanged = 0
107
154
  for (const p of allPkgPaths) {
108
155
  const raw = readFileSync(p, "utf-8")
109
156
  const pkg = JSON.parse(raw)
110
157
  let fileChanged = false
111
- for (const block of ["dependencies", "devDependencies"]) {
158
+
159
+ if (p === rootPkgPath && autoAdded.length > 0) {
160
+ pkg.dependencies = pkg.dependencies || {}
161
+ for (const name of autoAdded) {
162
+ pkg.dependencies[name] = canonicalRange
163
+ }
164
+ fileChanged = true
165
+ totalChanged += autoAdded.length
166
+ }
167
+
168
+ for (const block of DEP_BLOCKS) {
112
169
  if (!pkg[block]) continue
113
170
  for (const key of Object.keys(pkg[block])) {
114
171
  if (!key.startsWith(GREST_PREFIX)) continue
@@ -138,7 +195,7 @@ async function runUpgrade(args) {
138
195
  return
139
196
  }
140
197
 
141
- // 6. Clear lockfile + installed @grest-ts/* state.
198
+ // 7. Clear lockfile + installed @grest-ts/* state.
142
199
  // Three things hold npm to old grest-ts versions:
143
200
  // - package-lock.json (the visible lockfile)
144
201
  // - node_modules/.package-lock.json (npm's hidden install-state lockfile)
@@ -161,7 +218,7 @@ async function runUpgrade(args) {
161
218
  rmSync(grestNodeModulesPath, { recursive: true, force: true })
162
219
  }
163
220
 
164
- // 7. Reinstall. Pass as a single shell command string — Node refuses to spawn
221
+ // 8. Reinstall. Pass as a single shell command string — Node refuses to spawn
165
222
  // .cmd files directly on Windows (CVE-2024-27980), and passing arg arrays
166
223
  // with shell:true triggers DEP0190.
167
224
  console.log("Running npm install...")
@@ -170,7 +227,7 @@ async function runUpgrade(args) {
170
227
  throw new Error(`npm install exited with code ${result.status}`)
171
228
  }
172
229
 
173
- console.log(`\n✓ Upgraded all @grest-ts/* to ${canonicalRange}`)
230
+ console.log(`\n✓ Updated all @grest-ts/* to ${canonicalRange}`)
174
231
  }
175
232
 
176
233
  // ---- helpers ----
@@ -179,6 +236,13 @@ function readJson(path) {
179
236
  return JSON.parse(readFileSync(path, "utf-8"))
180
237
  }
181
238
 
239
+ function isInAnyBlock(pkg, depName) {
240
+ for (const block of DEP_BLOCKS) {
241
+ if (pkg[block] && Object.hasOwn(pkg[block], depName)) return true
242
+ }
243
+ return false
244
+ }
245
+
182
246
  function relativeTo(base, path) {
183
247
  if (path.startsWith(base)) {
184
248
  const rel = path.slice(base.length).replace(/^[\\/]+/, "")
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "//": "THIS FILE IS GENERATED - DO NOT EDIT",
3
3
  "name": "@grest-ts/cli",
4
- "version": "0.0.27",
4
+ "version": "0.0.31",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "description": "CLI for managing grest-ts in a project (atomic version upgrades, etc.)",
7
+ "description": "CLI for managing grest-ts in a project (atomic version updates, etc.)",
8
8
  "files": [
9
9
  "index.mjs"
10
10
  ],
@@ -29,7 +29,7 @@
29
29
  "testing",
30
30
  "grest-ts",
31
31
  "cli",
32
- "upgrade",
32
+ "update",
33
33
  "tooling"
34
34
  ],
35
35
  "engines": {