create-bakery 2.0.0-alpha.9 → 2.0.0-rc.1

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 CHANGED
@@ -25,7 +25,7 @@ bun run dev
25
25
  | `-h`, `--help` | Usage |
26
26
 
27
27
  Use `.` as the directory to scaffold in place. The positional argument is a
28
- *path*, so its basename becomes the package name — scoped names come from
28
+ *path*, so its basename becomes the package name: scoped names come from
29
29
  `--name`.
30
30
 
31
31
  It refuses to scaffold into a directory that already has files in it, since that
@@ -34,16 +34,16 @@ is not undoable. A bare `.git` directory is ignored, so
34
34
 
35
35
  ## Notes
36
36
 
37
- - **Bun only** — the generated app depends on Bun APIs throughout.
37
+ - **Bun only**: the generated app depends on Bun APIs throughout.
38
38
  - This package has **no dependencies**, not even on Bakery. It writes files.
39
39
 
40
40
  ## License
41
41
 
42
- MIT with the Commons Clause v1.0 — see [LICENSE](./LICENSE).
42
+ MIT with the Commons Clause v1.0. See [LICENSE](./LICENSE).
43
43
 
44
- **Not an OSI-approved licence.** The Commons Clause removes the right to *sell*
45
- the software — meaning to charge for a product or service whose value derives
44
+ **Not an OSI-approved license.** The Commons Clause removes the right to *sell*
45
+ the software: meaning to charge for a product or service whose value derives
46
46
  substantially from it, hosting and support included. Everything else the MIT
47
- licence grants is unchanged: use it, modify it, ship it inside your own product.
48
- If your organisation only permits OSI-approved dependencies, this will not pass
47
+ license grants is unchanged: use it, modify it, ship it inside your own product.
48
+ If your organization only permits OSI-approved dependencies, this will not pass
49
49
  that check.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bakery",
3
- "version": "2.0.0-alpha.9",
3
+ "version": "2.0.0-rc.1",
4
4
  "description": "Scaffold a Bakery app: bun create bakery <directory>",
5
5
  "keywords": [
6
6
  "bakery",
@@ -39,6 +39,6 @@
39
39
  "!src/tests"
40
40
  ],
41
41
  "engines": {
42
- "bun": ">=1.3.14"
42
+ "bun": ">=1.4.0"
43
43
  }
44
44
  }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  dependencyRange,
8
8
  isValidAppName,
9
9
  PLUGIN_IDS,
10
+ PLUGINS_NEEDING_ORM,
10
11
  type PluginId,
11
12
  type TemplateFile,
12
13
  templateFiles,
@@ -22,7 +23,7 @@ import {
22
23
  *
23
24
  * Deliberately dependency-free. `bun create` downloads this package on its own,
24
25
  * so anything it depends on is a download the user waits through before seeing
25
- * a single file — and the framework it scaffolds is the last thing it should
26
+ * a single file, and the framework it scaffolds is the last thing it should
26
27
  * drag along.
27
28
  */
28
29
 
@@ -60,7 +61,7 @@ type Options = {
60
61
  dir: string
61
62
  name: string
62
63
  install: boolean
63
- /** `null` means "not specified" — ask, or fall back to the default. */
64
+ /** `null` means "not specified". Ask, or fall back to the default. */
64
65
  orm: boolean | null
65
66
  plugins: PluginId[] | null
66
67
  yes: boolean
@@ -70,14 +71,14 @@ type Options = {
70
71
  * Parse one `--plugins` value into the ids it names.
71
72
  *
72
73
  * Split out of `parseArgs` because it is the only flag that validates rather
73
- * than assigns, and inlining it put the loop over the complexity limit — which
74
+ * than assigns, and inlining it put the loop over the complexity limit, which
74
75
  * is the rule doing its job: a `for` over argv should read as a dispatch table.
75
76
  */
76
77
  function parsePlugins(
77
78
  value: string,
78
79
  ): { ok: true; plugins: PluginId[] } | { ok: false; message: string } {
79
80
  // `none` rather than an empty string, so "I want no plugins" is something you
80
- // can state — an empty `--plugins=` reads like a mistake and is treated as one
81
+ // can state: an empty `--plugins=` reads like a mistake and is treated as one
81
82
  // by the caller, which rejects an empty value before reaching here.
82
83
  if (value === 'none') return { ok: true, plugins: [] }
83
84
 
@@ -108,7 +109,7 @@ function parsePlugins(
108
109
  * user typed, and answering it with a stack trace teaches nothing. Throwing is
109
110
  * reserved for a failure of the scaffolding itself.
110
111
  */
111
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: argv dispatcher — one branch per flag
112
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: argv dispatcher, one branch per flag
112
113
  export function parseArgs(
113
114
  argv: string[],
114
115
  ): { ok: true; options: Options } | { ok: false; message: string } {
@@ -173,7 +174,7 @@ export function parseArgs(
173
174
  if (dir === null) return { ok: false, message: HELP }
174
175
 
175
176
  // `.` is the documented way to scaffold in place, and `basename(resolve('.'))`
176
- // is the containing folder's name — which is the name the user means.
177
+ // is the containing folder's name, which is the name the user means.
177
178
  const resolved = resolve(dir)
178
179
  const appName = name ?? basename(resolved)
179
180
 
@@ -197,8 +198,7 @@ export function parseArgs(
197
198
  * Fill in whatever the flags left unspecified.
198
199
  *
199
200
  * Asks only when there is a terminal on both ends and `--yes` was not passed.
200
- * A pipe, a CI runner or a `--yes` takes the defaults — ORM in, no plugins —
201
- * which is what `bun create bakery my-app` has always produced, so adding the
201
+ * A pipe, a CI runner or a `--yes` takes the defaults (ORM in, no plugins), * which is what `bun create bakery my-app` has always produced, so adding the
202
202
  * prompts changed no existing invocation.
203
203
  *
204
204
  * Returns `null` when the user cancels, which is a distinct outcome from
@@ -227,12 +227,22 @@ export async function resolveChoices(
227
227
  { id: 'vue', label: 'vue', hint: 'single-file components' },
228
228
  { id: 'analytics', label: 'analytics', hint: 'request metrics' },
229
229
  { id: 'dashboard', label: 'dashboard', hint: 'admin console' },
230
+ { id: 'db-explorer', label: 'db-explorer', hint: 'browse and edit rows' },
230
231
  ])
231
232
  if (chosen === null) return null
232
233
  plugins = PLUGIN_IDS.filter(id => chosen.includes(id))
233
234
  }
234
235
  }
235
236
 
237
+ // The explorer browses and edits whatever the ORM is connected to, so
238
+ // scaffolding it without `orm/` produces an app whose headline feature has
239
+ // nothing to show. Turned on rather than refused: the two are asked for
240
+ // separately, and a generated app that boots is better than a prompt that
241
+ // argues with the answer it was just given.
242
+ if (!orm && plugins.some(id => PLUGINS_NEEDING_ORM.includes(id))) {
243
+ orm = true
244
+ }
245
+
236
246
  return { orm, plugins }
237
247
  }
238
248
 
@@ -241,7 +251,7 @@ export async function resolveChoices(
241
251
  * overwritten.
242
252
  *
243
253
  * Scaffolding is the one operation where "the directory already had something
244
- * in it" is almost always a mistake, and it is not undoable — so this refuses
254
+ * in it" is almost always a mistake, and it is not undoable, so this refuses
245
255
  * rather than merges or prompts. `.git` and the editor droppings people
246
256
  * routinely create a directory with are ignored, because refusing on those
247
257
  * makes `git init && bun create bakery .` fail for no reason.
@@ -254,7 +264,7 @@ export async function isScaffoldable(dir: string): Promise<boolean> {
254
264
  entries = await readdir(dir)
255
265
  } catch {
256
266
  // Does not exist, which is the common case and the good one. A permission
257
- // error also lands here and is caught properly by the write that follows —
267
+ // error also lands here and is caught properly by the write that follows:
258
268
  // reporting it as "not empty" would be a worse message than the real one.
259
269
  return true
260
270
  }
@@ -305,7 +315,7 @@ async function main(): Promise<number> {
305
315
  if (!(await isScaffoldable(dir))) {
306
316
  console.log(
307
317
  `${dir} already has files in it. Bakery will not scaffold over an ` +
308
- 'existing directory — pick an empty one, or empty this one first.',
318
+ 'existing directory. Pick an empty one, or empty this one first.',
309
319
  )
310
320
  return 1
311
321
  }
@@ -340,7 +350,7 @@ async function main(): Promise<number> {
340
350
  const code = await proc.exited
341
351
  if (code !== 0) {
342
352
  console.log(
343
- '\nbun install failed. The app is written — run it again in ' +
353
+ '\nbun install failed. The app is written. Run it again in ' +
344
354
  `${dir} once the problem is fixed.`,
345
355
  )
346
356
  return code
package/src/prompt.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * The interactive half of `bun create bakery`, written from scratch.
3
3
  *
4
4
  * Every prompt library worth using is a dependency, and this package declares
5
- * **none** on purpose — `bun create` downloads it standalone, so anything it
5
+ * **none** on purpose: `bun create` downloads it standalone, so anything it
6
6
  * pulls in is a download the user waits through before seeing a single file.
7
7
  * Two prompts is less code than justifying the exception.
8
8
  *
@@ -185,7 +185,7 @@ export async function confirm(
185
185
  return null
186
186
  }
187
187
 
188
- /** Returns the chosen ids, or `null` if the user cancelled. */
188
+ /** Returns the chosen ids, or `null` if the user canceled. */
189
189
  export async function multiselect(
190
190
  question: string,
191
191
  choices: Choice[],
package/src/template.ts CHANGED
@@ -6,12 +6,12 @@
6
6
  * checkable: **every `@bakery-framework/*` specifier below resolves through an
7
7
  * *enumerated* export, never the `"./*"` wildcard.** That wildcard is a
8
8
  * deprecation ramp with one release to live (MONOREPO.md), so a template that
9
- * leaned on it would generate apps that break on its removal — and it would
9
+ * leaned on it would generate apps that break on its removal, and it would
10
10
  * break them silently, because the wildcard resolves fine today.
11
11
  *
12
12
  * Derived from `apps/starter`, which is the honest reference: written against
13
13
  * public entry points only and booted in CI. The differences are the ones that
14
- * have to differ — real dependency ranges instead of `workspace:*`, the
14
+ * have to differ: real dependency ranges instead of `workspace:*`, the
15
15
  * `bakery` bin instead of a relative path into the repo, and a `.gitignore`.
16
16
  */
17
17
 
@@ -22,7 +22,12 @@ export type TemplateFile = {
22
22
  }
23
23
 
24
24
  /** The plugins `--plugins` accepts, in the order they are registered. */
25
- export const PLUGIN_IDS = ['vue', 'analytics', 'dashboard'] as const
25
+ export const PLUGIN_IDS = [
26
+ 'vue',
27
+ 'analytics',
28
+ 'dashboard',
29
+ 'db-explorer',
30
+ ] as const
26
31
  export type PluginId = (typeof PLUGIN_IDS)[number]
27
32
 
28
33
  export type TemplateOptions = {
@@ -38,7 +43,7 @@ export type TemplateOptions = {
38
43
  * `@vue/compiler-sfc` as **peer** dependencies, so scaffolding the plugin
39
44
  * without them produces an app that installs cleanly and then fails the first
40
45
  * time it compiles an SFC. They go into the generated `dependencies`, because a
41
- * scaffolded app is an application — it should pin what it needs rather than
46
+ * scaffolded app is an application: it should pin what it needs rather than
42
47
  * inherit an unmet peer warning.
43
48
  */
44
49
  const PLUGINS: Record<
@@ -65,8 +70,32 @@ const PLUGINS: Record<
65
70
  // because it is a local demo; a generated app is not.
66
71
  call: 'dashboardPlugin()',
67
72
  },
73
+ 'db-explorer': {
74
+ pkg: '@bakery-framework/plugin-db-explorer',
75
+ import: 'dbExplorerPlugin',
76
+ // Same rule as the dashboard and for a stronger reason: this one edits
77
+ // rows. With no `users` and no `authorize` the explorer allows loopback in
78
+ // development and denies in production, so a generated app is never born
79
+ // able to write to its database from a browser on the internet.
80
+ //
81
+ // It does not share the dashboard's door. Access here is a *level* per
82
+ // caller rather than a yes, which is why configuring one grants nothing in
83
+ // the other. See `docs/plugins/db-explorer.md`.
84
+ call: 'dbExplorerPlugin()',
85
+ },
68
86
  }
69
87
 
88
+ /**
89
+ * Plugins that cannot work without the ORM.
90
+ *
91
+ * `db-explorer` browses and edits whatever the ORM is connected to, so
92
+ * scaffolding it without `orm/` produces an app whose headline feature has
93
+ * nothing to show. The scaffolder turns the ORM on rather than refusing: the
94
+ * two are asked for separately and a generated app that boots is better than
95
+ * a prompt that argues.
96
+ */
97
+ export const PLUGINS_NEEDING_ORM: readonly PluginId[] = ['db-explorer']
98
+
70
99
  /**
71
100
  * The version range the generated `package.json` asks for.
72
101
  *
@@ -82,14 +111,14 @@ export function dependencyRange(ownVersion: string): string {
82
111
  /**
83
112
  * A valid npm package name, scoped or not.
84
113
  *
85
- * Stricter than npm on the parts that are worth being strict about — no
86
- * uppercase, no leading dot or dash — because the generated `name` field is
114
+ * Stricter than npm on the parts that are worth being strict about (no
115
+ * uppercase, no leading dot or dash), because the generated `name` field is
87
116
  * the only place this lands, and a name npm would reject surfaces as a
88
117
  * confusing `bun install` failure several steps after the mistake.
89
118
  *
90
119
  * Scopes are accepted, and are reachable only through `--name`. The positional
91
120
  * argument is a *directory path*, so `bun create bakery @co/app` means a nested
92
- * directory whose basename is `app` — that is what a path argument means, and
121
+ * directory whose basename is `app`: that is what a path argument means, and
93
122
  * quietly treating it as a scoped package name instead would be a guess.
94
123
  */
95
124
  export function isValidAppName(name: string): boolean {
@@ -116,7 +145,7 @@ export default defineConfig({
116
145
 
117
146
  const calls = plugins.map(id => ` ${PLUGINS[id].call},\n`).join('')
118
147
  const dashboardNote = plugins.includes('dashboard')
119
- ? ` // The dashboard authenticates nobody itself — the app does, because it is\n` +
148
+ ? ` // The dashboard authenticates nobody itself. The app does, because it is\n` +
120
149
  ` // the thing that knows who its users are. With no \`authorize\`, access is\n` +
121
150
  ` // loopback-only in development and denied in production:\n` +
122
151
  ` //\n` +
@@ -142,7 +171,7 @@ const INDEX_PAGE = `export default function Home() {
142
171
  </head>
143
172
  <body>
144
173
  <h1>{{name}}</h1>
145
- <p>Edit <code>src/index.tsx</code> and save — the page reloads itself.</p>
174
+ <p>Edit <code>src/index.tsx</code> and save, the page reloads itself.</p>
146
175
  <p id="count">loading…</p>
147
176
  <script src="/script.js" type="module"></script>
148
177
  </body>
@@ -162,12 +191,12 @@ import DB from '@bakery-framework/orm'
162
191
  //
163
192
  // export default defineRoute({ body: mySchema }, async (req, body) => …)
164
193
  //
165
- // The body option takes a Standard Schema (zod, valibot, arktype — Bakery
194
+ // The body option takes a Standard Schema (zod, valibot, arktype, Bakery
166
195
  // bundles none of them), or a plain function returning the parsed value or
167
196
  // throwing. A rejection answers 400 through the same JSON envelope as
168
197
  // everything else.
169
198
  // posts.authorId is a foreign key into users, so a post cannot be inserted
170
- // before its author exists — the database refuses a dangling id, which is the
199
+ // before its author exists: the database refuses a dangling id, which is the
171
200
  // entire point of the constraint. A real app takes the author from the session;
172
201
  // this creates one on demand so the route works on a freshly synced database.
173
202
  async function authorId(): Promise<number> {
@@ -203,14 +232,14 @@ export default defineRoute<{ title: string; slug: string; body: string }>(
203
232
  /**
204
233
  * The same route without a database.
205
234
  *
206
- * Kept to the same shape — `defineRoute`, a typed body, one JSON envelope, and
207
- * `data` as an array — so the client script below is identical either way and
235
+ * Kept to the same shape (`defineRoute`, a typed body, one JSON envelope, and
236
+ * `data` as an array), so the client script below is identical either way and
208
237
  * the two templates do not drift into demonstrating different things.
209
238
  */
210
239
  const API_ROUTE_NO_ORM = `import { defineRoute, response } from '@bakery-framework/core'
211
240
 
212
241
  // In memory, and therefore per process: a cluster (\`--threads N\`) gives each
213
- // worker its own copy. That is the point at which you want the ORM — scaffold
242
+ // worker its own copy. That is the point at which you want the ORM: scaffold
214
243
  // with it, or add @bakery-framework/orm later.
215
244
  const posts: { title: string }[] = []
216
245
 
@@ -266,7 +295,7 @@ export const postsByAuthor = Field.Index(posts.authorId)
266
295
  /**
267
296
  * The `declare module` block is the whole point of this file: it is what makes
268
297
  * the ORM typed. Without it everything still runs and typechecks, the columns
269
- * are just permissive `any` — which is a quiet enough failure that it is worth
298
+ * are just permissive `any`, which is a quiet enough failure that it is worth
270
299
  * generating rather than documenting.
271
300
  */
272
301
 
@@ -330,7 +359,7 @@ function gitignore(orm: boolean): string {
330
359
 
331
360
  return `node_modules
332
361
 
333
- # Disposable — the framework deletes it wholesale on every version bump and
362
+ # Disposable: the framework deletes it wholesale on every version bump and
334
363
  # dev<->prod switch.
335
364
  .cache
336
365
  ${data}`
@@ -343,7 +372,7 @@ function readme(name: string, orm: boolean, plugins: PluginId[]): string {
343
372
 
344
373
  const ormRows = orm
345
374
  ? '| `orm/tables.ts` | Table definitions. Run `bun run db:sync` after editing. |\n' +
346
- '| `orm/views.ts` | View definitions — stored SELECTs, read-only. |\n' +
375
+ '| `orm/views.ts` | View definitions: stored SELECTs, read-only. |\n' +
347
376
  "| `orm/index.ts` | Registers the schema with the ORM's types. Without its `declare module` block the ORM still works, untyped. |\n"
348
377
  : ''
349
378
 
@@ -354,6 +383,11 @@ function readme(name: string, orm: boolean, plugins: PluginId[]): string {
354
383
  ? '\n\nThe dashboard is loopback-only in development and denied in production ' +
355
384
  'until you give it an `authorize` predicate.'
356
385
  : '') +
386
+ (plugins.includes('db-explorer')
387
+ ? '\n\nThe database explorer is served at `/_db` and follows the same rule, ' +
388
+ 'with its own access model: a level per caller rather than a yes. ' +
389
+ 'Configuring the dashboard grants nothing there.'
390
+ : '') +
357
391
  (plugins.includes('vue')
358
392
  ? '\n\n`vue` and `@vue/compiler-sfc` are direct dependencies rather than ' +
359
393
  'unmet peers, so `.vue` pages compile straight after install.'
@@ -364,7 +398,7 @@ function readme(name: string, orm: boolean, plugins: PluginId[]): string {
364
398
  const noOrm = orm
365
399
  ? ''
366
400
  : '\nScaffolded without the ORM. `src/api/notes.ts` keeps its posts in memory, ' +
367
- 'which is per process — add `@bakery-framework/orm` when you want them to outlive a ' +
401
+ 'which is per process. Add `@bakery-framework/orm` when you want them to outlive a ' +
368
402
  'restart or survive `--threads`.\n'
369
403
 
370
404
  return `# ${name}
@@ -382,7 +416,7 @@ ${noOrm}
382
416
 
383
417
  | Path | What it is |
384
418
  | --- | --- |
385
- | \`src/\` | Served. Every file is a route — \`src/index.tsx\` is \`/\`, \`src/api/notes.ts\` is \`/api/notes\`. |
419
+ | \`src/\` | Served. Every file is a route, \`src/index.tsx\` is \`/\`, \`src/api/notes.ts\` is \`/api/notes\`. |
386
420
  ${ormRows}| \`server.config.ts\` | Port, root directory, plugins. |
387
421
  ${pluginSection}
388
422
  \`bun run start\` serves in production mode; add \`--threads N\` to fork a cluster.
@@ -394,7 +428,7 @@ ${pluginSection}
394
428
  * every page in the generated app.
395
429
  *
396
430
  * `@bakery-framework/core/tsconfig.server.json` already sets them, and `tsc` picks them up
397
- * from there — but **Bun's runtime does not follow `extends` into a package
431
+ * from there, but **Bun's runtime does not follow `extends` into a package
398
432
  * specifier**, only a relative path. So at runtime the app is transpiled with
399
433
  * Bun's default automatic JSX runtime instead of Bakery's classic
400
434
  * `createElement`, and every `.tsx` route fails with `Cannot find module
@@ -414,7 +448,7 @@ function tsconfig(orm: boolean): string {
414
448
  .join(',\n')
415
449
 
416
450
  return `{
417
- "$comment": "The three jsx* options are also set by @bakery-framework/core/tsconfig.server.json, and tsc reads them from there — but Bun's runtime does not follow 'extends' into a package specifier, only a relative path. Without them here, every .tsx page fails at runtime with \\"Cannot find module 'react/jsx-dev-runtime'\\" while typecheck stays clean. Keep them.",
451
+ "$comment": "The three jsx* options are also set by @bakery-framework/core/tsconfig.server.json, and tsc reads them from there, but Bun's runtime does not follow 'extends' into a package specifier, only a relative path. Without them here, every .tsx page fails at runtime with \\"Cannot find module 'react/jsx-dev-runtime'\\" while typecheck stays clean. Keep them.",
418
452
  "extends": "@bakery-framework/core/tsconfig.server.json",
419
453
  "compilerOptions": {
420
454
  "jsx": "react",
@@ -431,7 +465,7 @@ ${include}
431
465
  /**
432
466
  * Build the file list for an app named `name`.
433
467
  *
434
- * `range` is threaded in rather than read from disk so this stays pure — the
468
+ * `range` is threaded in rather than read from disk so this stays pure: the
435
469
  * caller resolves it from the running package's own version.
436
470
  */
437
471
  export function templateFiles(
@@ -460,7 +494,16 @@ export function templateFiles(
460
494
  dev: 'bakery --dev',
461
495
  start: 'bakery',
462
496
  typecheck: 'tsc --noEmit',
463
- ...(orm ? { 'db:sync': 'bun run scripts/db-sync.ts' } : {}),
497
+ ...(orm
498
+ ? {
499
+ 'db:sync': 'bun run scripts/db-sync.ts',
500
+ // The codegen entry point: write the schema *from* the database,
501
+ // touching no tables. `--migrate` is the sync engine's adoption
502
+ // flag; naming it here is what makes generation discoverable as a
503
+ // command rather than folklore about a flag.
504
+ 'db:codegen': 'bun run scripts/db-sync.ts --migrate',
505
+ }
506
+ : {}),
464
507
  },
465
508
  // Sorted, because the key order here is otherwise "whichever plugin was
466
509
  // listed first", and a generated file that differs run to run is a diff
@@ -470,21 +513,21 @@ export function templateFiles(
470
513
  ),
471
514
  // `bun-types` is not optional, and its absence is invisible until you run
472
515
  // tsc. `@bakery-framework/core/tsconfig.server.json` sets
473
- // `"types": ["bun-types"]` — it has to, that is what makes `Bun.*` a type
474
- // error in browser code — but core declares no dependencies at all, so
516
+ // `"types": ["bun-types"]`. It has to, that is what makes `Bun.*` a type
517
+ // error in browser code, but core declares no dependencies at all, so
475
518
  // nothing installs the package the setting names. In this repo it resolves
476
519
  // from a *root* devDependency, which is why the gap was invisible from
477
520
  // inside the workspace: a generated app typechecked here and died with
478
521
  // `TS2688: Cannot find type definition file for 'bun-types'` anywhere else.
479
522
  //
480
- // An app owning its own toolchain is the right shape regardless — the
523
+ // An app owning its own toolchain is the right shape regardless: the
481
524
  // alternative is core taking a dependency, and having none is a property
482
525
  // worth more than this convenience.
483
526
  devDependencies: {
484
- 'bun-types': '^1.3.14',
527
+ 'bun-types': '^1.4.0',
485
528
  // **`^5`, not `^7`, and the reason is `vue-tsc`.** TypeScript 7 is the
486
529
  // native port and does not ship `typescript/lib/tsc`, which `vue-tsc`
487
- // requires to patch — so a Vue app that installs TS 7 gets
530
+ // requires to patch, so a Vue app that installs TS 7 gets
488
531
  // `Cannot find module 'typescript/lib/tsc'` the first time it typechecks
489
532
  // an SFC. Measured on a real app: 7.0.2 crashes, 5.9.3 runs.
490
533
  //