@bakery-framework/core 1.2.1 → 1.2.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 +99 -1
- package/src/core/cache-version.ts +28 -1
- package/src/plugins/types.ts +16 -0
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
1
2
|
import { Bakery } from '../core/bakery'
|
|
2
3
|
import { errorMsg, serveLog } from '../logger'
|
|
3
4
|
import type { PluginTsProject } from '../plugins/types'
|
|
@@ -69,6 +70,7 @@ export function coreProjects(): PluginTsProject[] {
|
|
|
69
70
|
return [
|
|
70
71
|
{
|
|
71
72
|
name: 'server',
|
|
73
|
+
server: true,
|
|
72
74
|
extends: '@bakery-framework/core/tsconfig.server.json',
|
|
73
75
|
// Repeated rather than inherited: Bun's runtime does not follow
|
|
74
76
|
// `extends` into a package specifier, only a relative path.
|
|
@@ -138,6 +140,77 @@ function resolveFilesEntry(entry: string): string | null {
|
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
/**
|
|
144
|
+
* The `files` the extended base config declares, resolved for the generated one.
|
|
145
|
+
*
|
|
146
|
+
* **TypeScript's rule is that a child's `files` *replaces* the parent's, and
|
|
147
|
+
* that rule silently disarmed every project a plugin contributes.**
|
|
148
|
+
* `tsconfig.vue.json` lists core's three ambient declarations — `global.d.ts`,
|
|
149
|
+
* `shared.d.ts`, `types.d.ts` — which is where `Bakery`, `AppConfig`, the JSX
|
|
150
|
+
* namespace and `Request.session` come from. `@bakery-framework/plugin-vue`
|
|
151
|
+
* declares one `files` entry of its own for `vue.d.ts`, and that one entry
|
|
152
|
+
* replaced all three: measured on a real app, the generated `vue` project loaded
|
|
153
|
+
* **zero** of them.
|
|
154
|
+
*
|
|
155
|
+
* It hid because `vue.d.ts` happens to declare `req` and `body` itself, so the
|
|
156
|
+
* globals an SFC reaches for most still resolved. Everything else — `Bakery`,
|
|
157
|
+
* `MapOf`, the JSX namespace — was quietly missing.
|
|
158
|
+
*
|
|
159
|
+
* So the base's list is read and merged rather than inherited. Paths inside it
|
|
160
|
+
* are relative to *that* file, which is the property the whole arrangement rests
|
|
161
|
+
* on and the reason they cannot simply be copied across.
|
|
162
|
+
*/
|
|
163
|
+
function readBase(extendsSpecifier: string): string[] {
|
|
164
|
+
try {
|
|
165
|
+
const base = Bun.resolveSync(extendsSpecifier, APP_DIR)
|
|
166
|
+
const parsed = parseJSONC(readFileSync(base, 'utf8'))
|
|
167
|
+
const list: string[] = Array.isArray(parsed?.files) ? parsed.files : []
|
|
168
|
+
const baseDir = fs.dirname(base)
|
|
169
|
+
|
|
170
|
+
return list.map(entry => {
|
|
171
|
+
const abs = fs.resolve(baseDir, entry)
|
|
172
|
+
const rel = fs.relative(PROJECT_DIR, abs).replace(/\\/g, '/')
|
|
173
|
+
return RE_RELATIVE.test(rel) ? rel : `./${rel}`
|
|
174
|
+
})
|
|
175
|
+
} catch {
|
|
176
|
+
// A base that cannot be read is not fatal: the project still compiles, it
|
|
177
|
+
// just loses the ambients — which is the status quo this repairs, not a
|
|
178
|
+
// regression. Assume client-side, which is the conservative half.
|
|
179
|
+
return []
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The app file carrying `declare module '@bakery-framework/orm/schema-registry'`.
|
|
185
|
+
*
|
|
186
|
+
* Declaration merging only happens if the declaring file is in the program, and
|
|
187
|
+
* it reached exactly one project: `server`, because that is the only one whose
|
|
188
|
+
* `include` covers `orm/**`. Everywhere else `SchemaRegistry` stayed empty,
|
|
189
|
+
* `Registered` resolved to `never`, and every table fell back to
|
|
190
|
+
* `MapOf<MapOf<any>>` — the ORM's documented untyped mode, arrived at by
|
|
191
|
+
* accident. It does not error; it just stops checking.
|
|
192
|
+
*
|
|
193
|
+
* **Server-side projects only.** The client project deliberately does not get
|
|
194
|
+
* it: the ORM is server-only, so a browser file importing `DB` should fail to
|
|
195
|
+
* typecheck rather than be helpfully typed. That is not only a preference —
|
|
196
|
+
* `@bakery-framework/orm` ships TypeScript source that calls `Bun.*`, so pulling
|
|
197
|
+
* it into a config without `bun-types` produces errors from inside the package
|
|
198
|
+
* rather than types for the app. Measured when this was applied to every
|
|
199
|
+
* project: 187 new errors in `client`.
|
|
200
|
+
*/
|
|
201
|
+
function schemaRegistrationFile(): string | null {
|
|
202
|
+
const configured = Bakery.config.schema
|
|
203
|
+
const candidates = configured
|
|
204
|
+
? [configured, `${configured}/index.ts`]
|
|
205
|
+
: ['orm/index.ts', 'schema.ts']
|
|
206
|
+
|
|
207
|
+
for (const rel of candidates) {
|
|
208
|
+
const abs = fs.resolve(APP_DIR, rel)
|
|
209
|
+
if (fs.isFileSync(abs)) return abs
|
|
210
|
+
}
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
|
|
141
214
|
/** Every project: core's two, plus whatever the loaded plugins contribute. */
|
|
142
215
|
function allProjects(): PluginTsProject[] {
|
|
143
216
|
const projects = coreProjects()
|
|
@@ -174,9 +247,16 @@ function allProjects(): PluginTsProject[] {
|
|
|
174
247
|
*/
|
|
175
248
|
export async function writeProjects(paths: MapOf<string[]>): Promise<string[]> {
|
|
176
249
|
const written: string[] = []
|
|
250
|
+
const found = schemaRegistrationFile()
|
|
251
|
+
const registrationFile = found
|
|
252
|
+
? (() => {
|
|
253
|
+
const rel = fs.relative(PROJECT_DIR, found).replace(/\\/g, '/')
|
|
254
|
+
return RE_RELATIVE.test(rel) ? rel : `./${rel}`
|
|
255
|
+
})()
|
|
256
|
+
: null
|
|
177
257
|
|
|
178
258
|
for (const project of allProjects()) {
|
|
179
|
-
const
|
|
259
|
+
const own = (project.files ?? [])
|
|
180
260
|
.map(entry => {
|
|
181
261
|
const resolved = resolveFilesEntry(entry)
|
|
182
262
|
if (!resolved) {
|
|
@@ -186,6 +266,24 @@ export async function writeProjects(paths: MapOf<string[]>): Promise<string[]> {
|
|
|
186
266
|
})
|
|
187
267
|
.filter((f): f is string => f !== null)
|
|
188
268
|
|
|
269
|
+
const baseFiles = readBase(project.extends)
|
|
270
|
+
|
|
271
|
+
// The schema registration goes to every server-side project, so an SFC's
|
|
272
|
+
// `<script>` gets the app's real tables rather than the `any` fallback. The
|
|
273
|
+
// server project already reaches it through `include: ['orm/**']`; adding it
|
|
274
|
+
// to `files` there is a harmless duplicate and keeps the rule in one place.
|
|
275
|
+
const registration =
|
|
276
|
+
project.server && registrationFile ? [registrationFile] : []
|
|
277
|
+
|
|
278
|
+
// The base's own `files` are merged back in whenever this project declares
|
|
279
|
+
// any of its own, because a child's `files` *replaces* the parent's — see
|
|
280
|
+
// `readBase`. Left entirely empty, TypeScript inherits correctly and there
|
|
281
|
+
// is nothing to repair.
|
|
282
|
+
const declared = [...own, ...registration]
|
|
283
|
+
const files = declared.length
|
|
284
|
+
? [...new Set([...baseFiles, ...declared])]
|
|
285
|
+
: []
|
|
286
|
+
|
|
189
287
|
const config: Record<string, unknown> = {
|
|
190
288
|
$comment:
|
|
191
289
|
'GENERATED by Bakery on dev boot. Edits are lost; change the plugin or server.config.ts instead.',
|
|
@@ -100,12 +100,37 @@ async function run(): Promise<void> {
|
|
|
100
100
|
*/
|
|
101
101
|
export const __wipeCacheDir = wipe
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* The one thing in `.cache/` the wipe must not take.
|
|
105
|
+
*
|
|
106
|
+
* **The app's committed `tsconfig.json` *references* `.cache/tsconfig/*.json`,
|
|
107
|
+
* so deleting them breaks the editor for the whole project** — not one setting,
|
|
108
|
+
* everything. TypeScript reports `TS6053: File '…/server.json' not found` for
|
|
109
|
+
* each reference, has no project left to put a file in, and falls back to an
|
|
110
|
+
* inferred one with no ambients: `req.session`, `Bakery`, the JSX namespace and
|
|
111
|
+
* the app's own schema types all stop resolving at once.
|
|
112
|
+
*
|
|
113
|
+
* That happens on every framework upgrade, because the version wipe is keyed on
|
|
114
|
+
* the framework version among others. The developer sees their editor lose every
|
|
115
|
+
* type the moment they bump a patch, and nothing says why — the files come back
|
|
116
|
+
* only on the next `bun run dev`, which is not an obvious remedy for "my types
|
|
117
|
+
* vanished".
|
|
118
|
+
*
|
|
119
|
+
* Keeping them is safe in the direction that matters. A stale project is
|
|
120
|
+
* regenerated on the next dev boot and is, in the meantime, *approximately
|
|
121
|
+
* right* — while a missing one is catastrophically wrong. Nothing is executed
|
|
122
|
+
* from these files either: they configure a typechecker, so the "never read a
|
|
123
|
+
* cache an older framework wrote" rule the wipe exists to enforce does not apply.
|
|
124
|
+
*/
|
|
125
|
+
const WIPE_KEEP = new Set(['tsconfig'])
|
|
126
|
+
|
|
103
127
|
async function wipe(dir: string): Promise<string[]> {
|
|
104
128
|
if (!fs.exists(dir)) return []
|
|
105
129
|
const [readErr, entries] = await Try.catch(() => readdir(dir))
|
|
106
130
|
if (readErr || !entries) return ['<unreadable>']
|
|
107
131
|
|
|
108
132
|
for (const entry of entries) {
|
|
133
|
+
if (WIPE_KEEP.has(entry)) continue
|
|
109
134
|
// Errors are deliberately not swallowed *silently* here — each failure is
|
|
110
135
|
// collected and reported by the caller.
|
|
111
136
|
await Try.catch(() =>
|
|
@@ -115,5 +140,7 @@ async function wipe(dir: string): Promise<string[]> {
|
|
|
115
140
|
|
|
116
141
|
const [rereadErr, left] = await Try.catch(() => readdir(dir))
|
|
117
142
|
if (rereadErr) return ['<unreadable>']
|
|
118
|
-
|
|
143
|
+
// Kept entries are not survivors of a failed delete, and reporting them as
|
|
144
|
+
// such would make the caller withhold the "cache is current" marker forever.
|
|
145
|
+
return (left ?? []).filter(entry => !WIPE_KEEP.has(entry))
|
|
119
146
|
}
|
package/src/plugins/types.ts
CHANGED
|
@@ -43,6 +43,22 @@ export interface PluginTsProject {
|
|
|
43
43
|
* A plugin whose project compiles browser code should set it.
|
|
44
44
|
*/
|
|
45
45
|
importMapPaths?: boolean
|
|
46
|
+
/**
|
|
47
|
+
* Does code in this project run on the server?
|
|
48
|
+
*
|
|
49
|
+
* Declared, not inferred. The first version read `bun-types` out of the base
|
|
50
|
+
* config and treated that as the marker — true today, and an inference about
|
|
51
|
+
* intent drawn from a detail that exists for another reason. A plugin saying
|
|
52
|
+
* what its project *is* cannot drift out of step with itself.
|
|
53
|
+
*
|
|
54
|
+
* What it controls: the app's schema registration, so the ORM's tables are the
|
|
55
|
+
* app's own rather than the permissive `any` fallback. Off for browser code on
|
|
56
|
+
* purpose — `@bakery-framework/orm` is server-only, so a `.ts` bound for the
|
|
57
|
+
* browser importing `DB` should fail to typecheck rather than be helpfully
|
|
58
|
+
* typed, and the package ships TypeScript source calling `Bun.*`, which a
|
|
59
|
+
* client config cannot compile anyway.
|
|
60
|
+
*/
|
|
61
|
+
server?: boolean
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
/** What a plugin contributes to the generated tsconfig projects. */
|