@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakery-framework/core",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Bakery framework core: handlers, router, config, session, caches, logger, compiler.",
5
5
  "keywords": [
6
6
  "bakery",
@@ -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 files = (project.files ?? [])
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
- return left ?? []
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
  }
@@ -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. */