@bakery-framework/core 2.0.1 → 2.0.3
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/package.json +1 -1
- package/src/compiler/tsconfig-sync.ts +304 -48
- package/src/core/cache-version.ts +7 -6
- package/src/logger/serve-log.ts +10 -2
package/package.json
CHANGED
|
@@ -54,25 +54,41 @@ function buildPaths(): MapOf<string[]> {
|
|
|
54
54
|
const PROJECT_DIR = fs.resolve(APP_DIR, '.cache/tsconfig')
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* The
|
|
57
|
+
* The three projects core always generates.
|
|
58
58
|
*
|
|
59
|
-
* The split is the whole point:
|
|
60
|
-
* in a file bound for the browser is a type
|
|
61
|
-
* Before this existed, one config covered
|
|
62
|
-
* client file typechecked clean and failed
|
|
59
|
+
* The split is the whole point: `server` and `api` carry `bun-types` and
|
|
60
|
+
* `client` does not, so `Bun.*` in a file bound for the browser is a type
|
|
61
|
+
* error rather than a runtime one. Before this existed, one config covered
|
|
62
|
+
* everything and `Bun.hash()` in a client file typechecked clean and failed
|
|
63
|
+
* in the browser.
|
|
63
64
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
65
|
+
* **Between them they claim every app file once**, because the app's root
|
|
66
|
+
* tsconfig claims none: it reaches these projects through
|
|
67
|
+
* `tsconfig.bakery.json` (see {@link syncTSConfigProjects}), and an editor
|
|
68
|
+
* gives a file to the first project that claims it. A file nobody claims
|
|
69
|
+
* falls into an inferred project with default options and no `bun-types`,
|
|
70
|
+
* which is how every `api/` file in a real app reported "Cannot find name
|
|
71
|
+
* 'Bun'". So `server` is defined by what it is not: everything outside the
|
|
72
|
+
* serve root (config, schema, `orm/`, `scripts/`, `tests/`, migrations) plus
|
|
73
|
+
* every `.tsx` page, which is server-rendered. `api` is its own project only
|
|
74
|
+
* because a glob cannot say "`src/**` except `api/`" in either direction.
|
|
75
|
+
*
|
|
76
|
+
* Naming an `exclude` drops TypeScript's default one, so `node_modules` is
|
|
77
|
+
* named again, and so are both runtime directories, taken from their one
|
|
78
|
+
* writer (`Bakery.cacheDir`, `Bakery.dataDir`). Neither holds source and both
|
|
79
|
+
* hold `.ts` files: the cache holds compiled server modules, and the data
|
|
80
|
+
* directory holds the ORM's `schema.pre-migrate.*.ts` backups, each of which
|
|
81
|
+
* redeclares the schema registry. Claimed, three of those failed a real
|
|
82
|
+
* app's `tsc -b` with TS2717.
|
|
70
83
|
*
|
|
71
84
|
* Globs are app-relative here and rewritten to be relative to the generated
|
|
72
85
|
* file, which sits two levels down.
|
|
73
86
|
*/
|
|
74
87
|
export function coreProjects(): PluginTsProject[] {
|
|
75
88
|
const root = Bakery.config.root ?? 'src'
|
|
89
|
+
const runtime = [Bakery.cacheDir, Bakery.dataDir].map(dir =>
|
|
90
|
+
fs.relative(APP_DIR, dir).replace(/\\/g, '/'),
|
|
91
|
+
)
|
|
76
92
|
|
|
77
93
|
return [
|
|
78
94
|
{
|
|
@@ -86,13 +102,14 @@ export function coreProjects(): PluginTsProject[] {
|
|
|
86
102
|
jsxFactory: 'createElement',
|
|
87
103
|
jsxFragmentFactory: 'Fragment',
|
|
88
104
|
},
|
|
89
|
-
include: [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
105
|
+
include: ['**/*.ts', '**/*.tsx'],
|
|
106
|
+
exclude: [`${root}/**/*.ts`, 'node_modules', ...runtime],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'api',
|
|
110
|
+
server: true,
|
|
111
|
+
extends: '@bakery-framework/core/tsconfig.server.json',
|
|
112
|
+
include: [`${root}/**/api/**/*.ts`],
|
|
96
113
|
},
|
|
97
114
|
{
|
|
98
115
|
name: 'client',
|
|
@@ -365,9 +382,15 @@ function isGeneratedReference(entry: unknown): boolean {
|
|
|
365
382
|
* `allowImportingTsExtensions`, so they are unbuildable by design. Making them
|
|
366
383
|
* `composite` instead would trade the errors above for a `tsc -b` build-order
|
|
367
384
|
* requirement no consumer runs, and TS6305 would still fire for any root file
|
|
368
|
-
* importing into one while unbuilt.
|
|
369
|
-
*
|
|
370
|
-
*
|
|
385
|
+
* importing into one while unbuilt.
|
|
386
|
+
*
|
|
387
|
+
* **A root that has files of its own tolerates no reference into them,
|
|
388
|
+
* direct or chained.** Routed through a `composite`, `files: []` middle
|
|
389
|
+
* config, TS6306 and TS6310 go away and TS6305 does not: measured on a real
|
|
390
|
+
* app whose root claimed `src/**`, 57 of them. What works is the other
|
|
391
|
+
* direction, a root that claims nothing and reaches the projects through
|
|
392
|
+
* `tsconfig.bakery.json` (see {@link wireRoot}). This strips the direct
|
|
393
|
+
* entries previous releases wrote, which is still a repair worth making.
|
|
371
394
|
*
|
|
372
395
|
* Everything the developer owns is preserved: only entries into
|
|
373
396
|
* `.cache/tsconfig/` are removed, a real project reference (say `../shared`)
|
|
@@ -394,6 +417,224 @@ export function stripGeneratedReferences(
|
|
|
394
417
|
return repaired
|
|
395
418
|
}
|
|
396
419
|
|
|
420
|
+
/** The generated file the root config references, and how it is spelled there. */
|
|
421
|
+
const CHAIN_FILE = 'tsconfig.bakery.json'
|
|
422
|
+
const CHAIN_PATH = fs.resolve(APP_DIR, CHAIN_FILE)
|
|
423
|
+
const CHAIN_REF = `./${CHAIN_FILE}`
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* `tsconfig.bakery.json`: every generated project, listed for an editor.
|
|
427
|
+
*
|
|
428
|
+
* **The middle layer is what makes the chain legal.** The root references
|
|
429
|
+
* this file, and this file references `.cache/tsconfig/*`. A root referencing
|
|
430
|
+
* those projects directly gets TS6306 and TS6310 for pointing at
|
|
431
|
+
* non-composite, `noEmit` projects. A `composite` config with `files: []` in
|
|
432
|
+
* between is a valid target, and tsserver follows references through it.
|
|
433
|
+
* Measured on a real app with TypeScript 5.9.3: every `api/` file moved from
|
|
434
|
+
* an inferred project with no `bun-types` to the server-side project, and
|
|
435
|
+
* every other `.ts` file to `client`.
|
|
436
|
+
*
|
|
437
|
+
* The server owns this file. A plugin contributes a project through
|
|
438
|
+
* `tsconfig.project` and the generator lists it here, so a plugin never writes
|
|
439
|
+
* it, and a project that failed to write is not listed.
|
|
440
|
+
*/
|
|
441
|
+
export function chainConfig(names: string[]): Record<string, unknown> {
|
|
442
|
+
return {
|
|
443
|
+
$comment:
|
|
444
|
+
'GENERATED by Bakery on dev boot and by `bakery --types`. Edits are lost. tsconfig.json references this file, and it references each project in .cache/tsconfig/, which is how an editor gives a file its scope.',
|
|
445
|
+
compilerOptions: { composite: true },
|
|
446
|
+
files: [],
|
|
447
|
+
references: names.map(name => ({ path: `./.cache/tsconfig/${name}.json` })),
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Write the chain file when its content changes, and say whether it did.
|
|
453
|
+
*
|
|
454
|
+
* A boot that rewrites an unchanged file dirties git every time and trains
|
|
455
|
+
* people to ignore the diff. The answer also decides when the advice for a
|
|
456
|
+
* root that claims files is worth giving (see {@link syncTSConfigProjects}).
|
|
457
|
+
*/
|
|
458
|
+
async function writeChain(names: string[]): Promise<boolean> {
|
|
459
|
+
const next = `${JSON.stringify(chainConfig(names), null, 2)}\n`
|
|
460
|
+
const current = fs.exists(CHAIN_PATH)
|
|
461
|
+
? await Bun.file(CHAIN_PATH).text()
|
|
462
|
+
: null
|
|
463
|
+
if (current === next) return false
|
|
464
|
+
await Bun.write(CHAIN_PATH, next)
|
|
465
|
+
return true
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** Whether a root config already references the chain file, however spelled. */
|
|
469
|
+
export function referencesChain(root: Record<string, unknown>): boolean {
|
|
470
|
+
const refs = root.references
|
|
471
|
+
if (!Array.isArray(refs)) return false
|
|
472
|
+
return refs.some(entry => {
|
|
473
|
+
const path = (entry as { path?: unknown } | null)?.path
|
|
474
|
+
return (
|
|
475
|
+
typeof path === 'string' &&
|
|
476
|
+
path.replace(/\\/g, '/').replace(RE_LEADING_SLASHES, '') === CHAIN_FILE
|
|
477
|
+
)
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* The config a root `extends`, read one level deep, or `null`.
|
|
483
|
+
*
|
|
484
|
+
* Only for {@link claimsAppFiles}: a root with no `files` or `include` of its
|
|
485
|
+
* own inherits the base's, and core's server config lists the three ambient
|
|
486
|
+
* declarations, which is what keeps such a root from claiming its whole
|
|
487
|
+
* directory.
|
|
488
|
+
*/
|
|
489
|
+
function baseConfigOf(
|
|
490
|
+
root: Record<string, unknown>,
|
|
491
|
+
): Record<string, unknown> | null {
|
|
492
|
+
if (typeof root.extends !== 'string') return null
|
|
493
|
+
try {
|
|
494
|
+
const base = Bun.resolveSync(root.extends, APP_DIR)
|
|
495
|
+
return parseJSONC(readFileSync(base, 'utf8'))
|
|
496
|
+
} catch {
|
|
497
|
+
// Unreadable reads as "no base", which `claimsAppFiles` takes as
|
|
498
|
+
// TypeScript's default of claiming everything: the root is then left
|
|
499
|
+
// unwired, which is the direction that cannot break `tsc -p`.
|
|
500
|
+
return null
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Whether a root config claims app files for itself.
|
|
506
|
+
*
|
|
507
|
+
* **A root that does cannot be wired to the chain.** Each file it shares with
|
|
508
|
+
* a generated project is redirected to that project's never-built output, and
|
|
509
|
+
* `tsc -p` fails with TS6305 once per file: 57 on the app this was measured
|
|
510
|
+
* on. Declaration files are not redirected, so a root claiming only ambient
|
|
511
|
+
* `.d.ts` files, which is what it inherits from core's server config, claims
|
|
512
|
+
* nothing that matters.
|
|
513
|
+
*
|
|
514
|
+
* TypeScript's rules, in order: the root's own `include` and `files` win over
|
|
515
|
+
* the base's; `files` without `include` claims only those files; and neither,
|
|
516
|
+
* anywhere, claims every file under the directory.
|
|
517
|
+
*/
|
|
518
|
+
export function claimsAppFiles(
|
|
519
|
+
root: Record<string, unknown>,
|
|
520
|
+
base: Record<string, unknown> | null,
|
|
521
|
+
): boolean {
|
|
522
|
+
const pick = (key: 'include' | 'files') =>
|
|
523
|
+
key in root ? root[key] : base?.[key]
|
|
524
|
+
const include = pick('include')
|
|
525
|
+
const files = pick('files')
|
|
526
|
+
|
|
527
|
+
if (Array.isArray(include) && include.length > 0) return true
|
|
528
|
+
if (
|
|
529
|
+
Array.isArray(files) &&
|
|
530
|
+
files.some(f => typeof f === 'string' && !f.endsWith('.d.ts'))
|
|
531
|
+
) {
|
|
532
|
+
return true
|
|
533
|
+
}
|
|
534
|
+
return include === undefined && files === undefined
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Add the chain reference to a root with no `references` key, as text.
|
|
539
|
+
*
|
|
540
|
+
* **Textual, so the developer's comments survive.** `tsconfig.json` is JSONC
|
|
541
|
+
* and theirs, and a parse-and-stringify rewrite drops every comment in it.
|
|
542
|
+
* This finds the last member of the top-level object, skipping strings and
|
|
543
|
+
* comments, and adds one member after it, with a comma unless one is already
|
|
544
|
+
* there. `null` when the text has no top-level object to extend.
|
|
545
|
+
*/
|
|
546
|
+
export function insertChainReference(text: string): string | null {
|
|
547
|
+
const eol = text.includes('\r\n') ? '\r\n' : '\n'
|
|
548
|
+
let depth = 0
|
|
549
|
+
let inString = false
|
|
550
|
+
let escaped = false
|
|
551
|
+
let last = -1
|
|
552
|
+
let close = -1
|
|
553
|
+
|
|
554
|
+
for (let i = 0; i < text.length; i++) {
|
|
555
|
+
const c = text[i]
|
|
556
|
+
if (inString) {
|
|
557
|
+
if (escaped) escaped = false
|
|
558
|
+
else if (c === '\\') escaped = true
|
|
559
|
+
else if (c === '"') inString = false
|
|
560
|
+
last = i
|
|
561
|
+
continue
|
|
562
|
+
}
|
|
563
|
+
if (c === '/' && text[i + 1] === '/') {
|
|
564
|
+
const end = text.indexOf('\n', i)
|
|
565
|
+
i = end === -1 ? text.length : end
|
|
566
|
+
continue
|
|
567
|
+
}
|
|
568
|
+
if (c === '/' && text[i + 1] === '*') {
|
|
569
|
+
const end = text.indexOf('*/', i + 2)
|
|
570
|
+
i = end === -1 ? text.length : end + 1
|
|
571
|
+
continue
|
|
572
|
+
}
|
|
573
|
+
if (c === '"') {
|
|
574
|
+
inString = true
|
|
575
|
+
last = i
|
|
576
|
+
continue
|
|
577
|
+
}
|
|
578
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') continue
|
|
579
|
+
if (c === '{' || c === '[') depth++
|
|
580
|
+
else if (c === '}' || c === ']') {
|
|
581
|
+
depth--
|
|
582
|
+
if (depth === 0 && c === '}') {
|
|
583
|
+
close = i
|
|
584
|
+
break
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
last = i
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (close === -1 || last === -1) return null
|
|
591
|
+
|
|
592
|
+
const comma = text[last] === '{' || text[last] === ',' ? '' : ','
|
|
593
|
+
const rest = text.slice(last + 1)
|
|
594
|
+
const gap = rest.startsWith('\n') || rest.startsWith('\r\n') ? '' : eol
|
|
595
|
+
return `${text.slice(0, last + 1)}${comma}${eol} "references": [{ "path": "${CHAIN_REF}" }]${gap}${rest}`
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* What the root config should become, as text, or `null` for no write.
|
|
600
|
+
*
|
|
601
|
+
* Two repairs, in order. The direct references earlier releases wrote are
|
|
602
|
+
* stripped ({@link stripGeneratedReferences}). Then the chain reference is
|
|
603
|
+
* added, unless the root already has it or claims app files of its own, in
|
|
604
|
+
* which case adding it would break `tsc -p` ({@link claimsAppFiles}) and the
|
|
605
|
+
* caller says so instead.
|
|
606
|
+
*
|
|
607
|
+
* Pure, and the place the rule "a root never references `.cache/tsconfig/`"
|
|
608
|
+
* is held: the only entry it ever adds is the chain file.
|
|
609
|
+
*/
|
|
610
|
+
export function wireRoot(
|
|
611
|
+
text: string,
|
|
612
|
+
base: Record<string, unknown> | null,
|
|
613
|
+
): { text: string | null; stripped: boolean; wired: boolean; claims: boolean } {
|
|
614
|
+
let root = parseJSONC(text) as Record<string, unknown>
|
|
615
|
+
let out: string | null = null
|
|
616
|
+
|
|
617
|
+
const repaired = stripGeneratedReferences(root)
|
|
618
|
+
if (repaired) {
|
|
619
|
+
root = repaired
|
|
620
|
+
out = `${JSON.stringify(repaired, null, 2)}\n`
|
|
621
|
+
}
|
|
622
|
+
const stripped = repaired !== null
|
|
623
|
+
|
|
624
|
+
if (referencesChain(root)) {
|
|
625
|
+
return { text: out, stripped, wired: false, claims: false }
|
|
626
|
+
}
|
|
627
|
+
if (claimsAppFiles(root, base)) {
|
|
628
|
+
return { text: out, stripped, wired: false, claims: true }
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const refs = Array.isArray(root.references) ? root.references : null
|
|
632
|
+
const wired =
|
|
633
|
+
(refs ? null : insertChainReference(out ?? text)) ??
|
|
634
|
+
`${JSON.stringify({ ...root, references: [...(refs ?? []), { path: CHAIN_REF }] }, null, 2)}\n`
|
|
635
|
+
return { text: wired, stripped, wired: true, claims: false }
|
|
636
|
+
}
|
|
637
|
+
|
|
397
638
|
/**
|
|
398
639
|
* The root config written when the app has none at all.
|
|
399
640
|
*
|
|
@@ -402,6 +643,10 @@ export function stripGeneratedReferences(
|
|
|
402
643
|
* only into a relative path, never a package specifier, so the file this
|
|
403
644
|
* writes has to carry the JSX options itself, inline, exactly as the
|
|
404
645
|
* scaffolder spells them.
|
|
646
|
+
*
|
|
647
|
+
* It claims no app files: it inherits only core's ambient declarations, and
|
|
648
|
+
* reaches everything else through the chain, which is the one shape the
|
|
649
|
+
* chain is legal in (see {@link claimsAppFiles}).
|
|
405
650
|
*/
|
|
406
651
|
export function defaultRootConfig(): Record<string, unknown> {
|
|
407
652
|
return {
|
|
@@ -411,50 +656,59 @@ export function defaultRootConfig(): Record<string, unknown> {
|
|
|
411
656
|
jsxFactory: 'createElement',
|
|
412
657
|
jsxFragmentFactory: 'Fragment',
|
|
413
658
|
},
|
|
659
|
+
references: [{ path: CHAIN_REF }],
|
|
414
660
|
}
|
|
415
661
|
}
|
|
416
662
|
|
|
417
663
|
/**
|
|
418
|
-
* Generate the
|
|
419
|
-
*
|
|
420
|
-
* the `references` a previous release wrote into it.
|
|
664
|
+
* Generate the scope projects, list them in `tsconfig.bakery.json`, and wire
|
|
665
|
+
* the app's root tsconfig to that file.
|
|
421
666
|
*
|
|
422
667
|
* Separate from `syncTSConfigPaths` because an app can reasonably want one and
|
|
423
668
|
* not the other: the paths sync has existed for a long time and rewrites a file
|
|
424
669
|
* people keep in git, while this owns a directory nobody edits.
|
|
425
670
|
*
|
|
426
|
-
* **
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
671
|
+
* **Who owns what.** `.cache/tsconfig/*.json` and `tsconfig.bakery.json` are
|
|
672
|
+
* generated, and edits to them are lost. `tsconfig.json` is the developer's:
|
|
673
|
+
* the only change made to it is the one `references` entry for the chain,
|
|
674
|
+
* inserted as text so its comments survive, plus the one-time strip of the
|
|
675
|
+
* direct references earlier releases wrote.
|
|
676
|
+
*
|
|
677
|
+
* **Why the chain.** An editor gives a file to the project that claims it.
|
|
678
|
+
* With the projects standing alone, nothing pointed an editor at them, so a
|
|
679
|
+
* root that claimed nothing left every app file in an inferred project, and
|
|
680
|
+
* `Bun` was unknown in every `api/` file. Through the chain each file gets its
|
|
681
|
+
* scope: `api/` and pages to server-side projects, other `.ts` to `client`, an
|
|
682
|
+
* SFC to plugin-vue's project. `tsc -b` checks all of them at once.
|
|
683
|
+
*
|
|
684
|
+
* **Only for a root that claims no app files.** One that does fails
|
|
685
|
+
* `tsc -p` with TS6305 once per shared file when chained, so it is left as it
|
|
686
|
+
* is, with a line saying how to move ({@link claimsAppFiles}). Said when the
|
|
687
|
+
* chain file changes rather than on every boot, since keeping the old shape
|
|
688
|
+
* can be deliberate.
|
|
689
|
+
*
|
|
690
|
+
* **A fresh clone has none of these files** until something generates them,
|
|
691
|
+
* and `tsc` then stops at TS6053 on the chain's references. `bakery --types`
|
|
692
|
+
* generates them without starting a server, so a clone, a CI job or a
|
|
693
|
+
* `typecheck` script can run it first. The cache wipe keeps
|
|
694
|
+
* `.cache/tsconfig/` for the same reason (`WIPE_KEEP` in `cache-version.ts`).
|
|
439
695
|
*
|
|
440
696
|
* Two earlier lessons still bind the root-config half. It used to be
|
|
441
697
|
* *replaced* with a references-only stub, which silently broke every `.tsx`
|
|
442
698
|
* page: Bun's runtime reads `compilerOptions.jsx*` from the root and does not
|
|
443
699
|
* follow `references`, so pages transpiled against the automatic JSX runtime
|
|
444
700
|
* and `GET /` answered 200 with a JSON-encoded React element tree. Hence
|
|
445
|
-
* {@link defaultRootConfig} when no root exists, and surgical
|
|
701
|
+
* {@link defaultRootConfig} when no root exists, and surgical edits (never a
|
|
446
702
|
* wholesale rewrite) when one does. And a boot that dirties git every time
|
|
447
|
-
* trains people to ignore the diff, so
|
|
703
|
+
* trains people to ignore the diff, so a root already in shape is not
|
|
704
|
+
* rewritten, and neither is an unchanged chain file.
|
|
448
705
|
*/
|
|
449
706
|
export async function syncTSConfigProjects(): Promise<void> {
|
|
450
707
|
try {
|
|
451
708
|
const written = await writeProjects(buildPaths())
|
|
709
|
+
const chainChanged = await writeChain(written)
|
|
452
710
|
|
|
453
|
-
|
|
454
|
-
? parseJSONC(await Bun.file(APP_CONFIG_PATH).text())
|
|
455
|
-
: null
|
|
456
|
-
|
|
457
|
-
if (!current) {
|
|
711
|
+
if (!fs.exists(APP_CONFIG_PATH)) {
|
|
458
712
|
await Bun.write(
|
|
459
713
|
APP_CONFIG_PATH,
|
|
460
714
|
`${JSON.stringify(defaultRootConfig(), null, 2)}\n`,
|
|
@@ -463,11 +717,13 @@ export async function syncTSConfigProjects(): Promise<void> {
|
|
|
463
717
|
return
|
|
464
718
|
}
|
|
465
719
|
|
|
466
|
-
const
|
|
467
|
-
|
|
720
|
+
const text = await Bun.file(APP_CONFIG_PATH).text()
|
|
721
|
+
const plan = wireRoot(text, baseConfigOf(parseJSONC(text)))
|
|
468
722
|
|
|
469
|
-
await Bun.write(APP_CONFIG_PATH,
|
|
470
|
-
serveLog.TSCONFIG_REFERENCES_REMOVED()
|
|
723
|
+
if (plan.text !== null) await Bun.write(APP_CONFIG_PATH, plan.text)
|
|
724
|
+
if (plan.stripped) serveLog.TSCONFIG_REFERENCES_REMOVED()
|
|
725
|
+
if (plan.wired) serveLog.TSCONFIG_CHAIN_WIRED()
|
|
726
|
+
if (plan.claims && chainChanged) serveLog.TSCONFIG_ROOT_CLAIMS_FILES()
|
|
471
727
|
} catch (err: any) {
|
|
472
728
|
serveLog.UNHANDLED_ERR({
|
|
473
729
|
error: `TSConfig project sync error: ${errorMsg(err)}`,
|
|
@@ -153,12 +153,13 @@ export const __wipeCacheDir = wipe
|
|
|
153
153
|
/**
|
|
154
154
|
* The one thing in `.cache/` the wipe must not take.
|
|
155
155
|
*
|
|
156
|
-
* **The app's
|
|
157
|
-
* so deleting them breaks the editor for the whole
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* inferred one with no ambients:
|
|
161
|
-
* the app's own schema
|
|
156
|
+
* **The app's `tsconfig.json` reaches `.cache/tsconfig/*.json` through
|
|
157
|
+
* `tsconfig.bakery.json`, so deleting them breaks the editor for the whole
|
|
158
|
+
* project**, not one setting, everything. TypeScript reports
|
|
159
|
+
* `TS6053: File '…/server.json' not found` for each reference, has no project
|
|
160
|
+
* left to put a file in, and falls back to an inferred one with no ambients:
|
|
161
|
+
* `Bun`, `req.session`, `Bakery`, the JSX namespace and the app's own schema
|
|
162
|
+
* types all stop resolving at once.
|
|
162
163
|
*
|
|
163
164
|
* That happens on every framework upgrade, because the version wipe is keyed on
|
|
164
165
|
* the framework version among others. The developer sees their editor lose every
|
package/src/logger/serve-log.ts
CHANGED
|
@@ -26,13 +26,21 @@ const serveMsgs = {
|
|
|
26
26
|
TSCONFIG_SYNCED:
|
|
27
27
|
'I Synced %ytsconfig.json%* paths with %yserver.config.ts%*!',
|
|
28
28
|
TSCONFIG_PROJECTS_WRITTEN:
|
|
29
|
-
'I Wrote %y{count}%* tsconfig project(s) to %y.cache/tsconfig
|
|
29
|
+
'I Wrote %y{count}%* tsconfig project(s) to %y.cache/tsconfig/%*, listed in %ytsconfig.bakery.json%*, and a %ytsconfig.json%* that references it',
|
|
30
30
|
// One-time repair. Previous releases wired the generated projects into the
|
|
31
31
|
// app's tsconfig.json as `references`, which broke `tsc -p <app>`
|
|
32
32
|
// (TS6305/6306/6310). Named so the rewrite of a tracked file comes with a
|
|
33
33
|
// line saying why it happened.
|
|
34
34
|
TSCONFIG_REFERENCES_REMOVED:
|
|
35
|
-
'I Removed
|
|
35
|
+
'I Removed the direct %y.cache/tsconfig/%* entries from %yreferences%* in %ytsconfig.json%*: referencing those projects directly broke %ytsc -p%*, and %ytsconfig.bakery.json%* reaches them instead',
|
|
36
|
+
// The one edit made to a developer's tsconfig.json, named for the same
|
|
37
|
+
// reason as the repair above: a tracked file changed on boot.
|
|
38
|
+
TSCONFIG_CHAIN_WIRED:
|
|
39
|
+
'I Added %y./tsconfig.bakery.json%* to %yreferences%* in %ytsconfig.json%*: an editor now gives each file its scope project',
|
|
40
|
+
// Said when tsconfig.bakery.json is new or changed, not on every boot: a root
|
|
41
|
+
// that claims files may be kept that way on purpose, and it still works.
|
|
42
|
+
TSCONFIG_ROOT_CLAIMS_FILES:
|
|
43
|
+
'W %ytsconfig.json%* claims app files, so it is not wired to %ytsconfig.bakery.json%*: chained, it would fail %ytsc -p%* with TS6305. To give each file its scope, remove its %yinclude%* and %yfiles%*, reference %y./tsconfig.bakery.json%*, and typecheck with %ytsc -b%*',
|
|
36
44
|
// A plugin asking for a project name that is taken. Named rather than
|
|
37
45
|
// silent: the symptom otherwise is one plugin's types quietly not applying,
|
|
38
46
|
// discovered much later and blamed on the wrong thing.
|