@michaelthielemann/kestrel 4.0.0 → 4.0.2

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.
@@ -1,3 +1,4 @@
1
+ import { toRaw } from 'vue'
1
2
  import type { FieldDef, SerializedField } from '@michaelthielemann/kestrel-core'
2
3
  import { resolveFieldEmpty } from '../../../ui/app/utils/field-empty'
3
4
 
@@ -42,8 +43,12 @@ export function readFetchError(e: unknown): FetchErrorInfo {
42
43
  }
43
44
  }
44
45
 
46
+ // `field.default` on a block-picker's field comes from `useBlocks()`'s `useState`-backed list — a Vue
47
+ // reactive Proxy for any object/array value. `structuredClone` cannot clone a Proxy (`DataCloneError`,
48
+ // deterministic, not a race) — `toRaw` unwraps it first, same fix `cloneBlockTree` reaches for via a JSON
49
+ // round-trip; `toRaw` + `structuredClone` keeps real Date/Map values a JSON round-trip would mangle.
45
50
  function cloneDefault(value: unknown): unknown {
46
- return value !== null && typeof value === 'object' ? structuredClone(value) : value
51
+ return value !== null && typeof value === 'object' ? structuredClone(toRaw(value)) : value
47
52
  }
48
53
 
49
54
  function emptyForField(field: SerializedField): unknown {
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { addComponentsDir, addTemplate, addTypeTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
4
- import { collectBlockSfcs, collectDefinitions, collectManifestFiles, renderPackageConcatRegistry, renderPackageMergedRegistry, renderRegistry } from './scan'
4
+ import { collectBlockSfcs, collectDefinitions, collectManifestFiles, renderPackageConcatRegistry, renderPackageMergedRegistry, renderRegistry, resolvePackageEntry } from './scan'
5
5
  import { renderBlockRegistry } from './extract-block'
6
6
  import { offerableLayouts, renderLayoutRegistry } from '../../app/utils/layouts'
7
7
  import { PACKAGE_COLLECTIONS, PACKAGE_MANIFESTS, PACKAGE_SCHEMA_TABLES } from './package-registry'
@@ -38,9 +38,10 @@ export default defineNuxtModule({
38
38
  nitro.virtual ||= {}
39
39
  // `@michaelthielemann/kestrel-fields`'s built-in descriptors (text/richtext/number/…) seed `core`'s field-type registry
40
40
  // as a side effect of importing the package itself — core cannot import fields, so this is the one
41
- // place that import happens. A bare package specifier: no filesystem path-guessing across
42
- // `nuxt.options._layers` for a `layers/fields` root the package IS the seed's real, resolvable
43
- // location, so there is nothing left to guess. Prepended so it always runs, even with zero consumer
41
+ // place that import happens. Resolved to a real file path (`resolvePackageEntry`), not left as the
42
+ // bare specifier: a Nitro virtual module has no real file of its own for a bundler to resolve a bare
43
+ // import FROM, so it falls back to the project root where, under a real consumer's pnpm install,
44
+ // this package isn't visible (only the engine directly depends on it). Prepended so it always runs, even with zero consumer
44
45
  // field types (an empty `collectDefinitions` result would otherwise generate `export default []`,
45
46
  // importing nothing). Consumer field types register as a side effect on import too, and the schema
46
47
  // engine builds a table the moment a collection/block module loads — so the collections/blocks/
@@ -48,7 +49,7 @@ export default defineNuxtModule({
48
49
  // now reach a package's `buildCollection()` call transitively via `kestrelDiscovery`, per ADR-0029 —
49
50
  // an ESM barrel is an eager, whole-module-graph load — so all four need the same guard, not only the
50
51
  // two that directly build collections themselves).
51
- const importFieldTypesSeed = `import { fieldTypes as __kestrelSeed } from '@michaelthielemann/kestrel-fields'\n`
52
+ const importFieldTypesSeed = `import { fieldTypes as __kestrelSeed } from ${JSON.stringify(resolvePackageEntry('@michaelthielemann/kestrel-fields'))}\n`
52
53
  + `if (!__kestrelSeed || typeof __kestrelSeed !== 'object') throw new Error('[kestrel] built-in field types failed to seed')\n`
53
54
  // A real runtime check on the imported binding, not a bare `import "path"` or a discarded `void x`
54
55
  // — Nitro's dev build tree-shakes an import whose binding has no provable effect, silently dropping
@@ -68,7 +69,7 @@ export default defineNuxtModule({
68
69
 
69
70
  nitro.virtual['#kestrel/collections'] = () =>
70
71
  renderPackageMergedRegistry({
71
- packages: PACKAGE_COLLECTIONS,
72
+ packages: PACKAGE_COLLECTIONS.map(resolvePackageEntry),
72
73
  property: 'collections',
73
74
  consumerFiles: collectDefinitions(roots, 'server/collections'),
74
75
  nameOfExpr: '(x) => x.name',
@@ -87,7 +88,7 @@ export default defineNuxtModule({
87
88
  // hardcoding upper-layer tables.
88
89
  nitro.virtual['#kestrel/schema-tables'] = () =>
89
90
  renderPackageMergedRegistry({
90
- packages: PACKAGE_SCHEMA_TABLES,
91
+ packages: PACKAGE_SCHEMA_TABLES.map(resolvePackageEntry),
91
92
  property: 'schemaTables',
92
93
  consumerFiles: collectDefinitions(roots, 'server/schema-tables'),
93
94
  nameOfExpr: '(x) => __kestrelTableName(x)',
@@ -101,7 +102,7 @@ export default defineNuxtModule({
101
102
  // distinct module (see `collectManifestFiles`'s own TSDoc).
102
103
  nitro.virtual['#kestrel/module-manifests'] = () =>
103
104
  renderPackageConcatRegistry({
104
- packages: PACKAGE_MANIFESTS,
105
+ packages: PACKAGE_MANIFESTS.map(resolvePackageEntry),
105
106
  property: 'manifest',
106
107
  consumerFiles: collectManifestFiles(roots),
107
108
  preamble: importFieldTypesVirtual,
@@ -1,9 +1,24 @@
1
1
  import { readdirSync } from 'node:fs'
2
2
  import { basename, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
3
4
  import { blockNameFromFile } from './extract-block'
4
5
 
5
6
  type Lister = (dir: string) => string[]
6
7
 
8
+ /**
9
+ * Resolves a bare `@michaelthielemann/kestrel-*` package specifier to its real entry FILE PATH, via Node's
10
+ * own ESM resolution from THIS module's own location — not the bare specifier itself, which a Nitro
11
+ * virtual module (no real file path of its own) cannot resolve reliably: Rollup falls back to resolving a
12
+ * virtual's imports from the project root, and under pnpm's isolated `node_modules` a package the
13
+ * CONSUMER never declared a direct dependency on (only the engine did, transitively) is invisible there —
14
+ * observed as `Cannot find package` at runtime, npm's flat hoisting having accidentally masked it in
15
+ * testing. This module's own file, once installed, sits inside the engine's package tree, so resolving
16
+ * from here walks the engine's OWN nested `node_modules`, where pnpm DID link the real dependency.
17
+ */
18
+ export function resolvePackageEntry(spec: string): string {
19
+ return fileURLToPath(import.meta.resolve(spec))
20
+ }
21
+
7
22
  const isDefinition = (name: string) =>
8
23
  name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts')
9
24
 
@@ -118,7 +133,7 @@ export function renderPackageMergedRegistry(opts: {
118
133
  return [
119
134
  opts.preamble ?? '',
120
135
  opts.extraImports ?? '',
121
- `import { mergeKestrelDiscovered } from '@michaelthielemann/kestrel-core'`,
136
+ `import { mergeKestrelDiscovered } from ${JSON.stringify(resolvePackageEntry('@michaelthielemann/kestrel-core'))}`,
122
137
  ...pkgImports,
123
138
  ...consumerImports,
124
139
  `export default mergeKestrelDiscovered([${pkgSpread}], [${consumerArr}], ${opts.nameOfExpr})`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaelthielemann/kestrel",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "A slim, collection-driven Nuxt 4 CMS meta-layer with a runtime schema engine. Add `extends: ['@michaelthielemann/kestrel']`, define collections, and the database migrates itself.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Michael Thielemann <283621694+MichaelThielemann@users.noreply.github.com>",
@@ -77,15 +77,15 @@
77
77
  "typescript": "^6.0.3",
78
78
  "zod": "^4.4.3",
79
79
  "@michaelthielemann/kestrel-access": "0.1.0",
80
- "@michaelthielemann/kestrel-auth": "0.1.0",
81
- "@michaelthielemann/kestrel-core": "0.1.0",
82
- "@michaelthielemann/kestrel-contracts": "0.1.0",
83
80
  "@michaelthielemann/kestrel-collections": "0.1.0",
81
+ "@michaelthielemann/kestrel-contracts": "0.1.0",
82
+ "@michaelthielemann/kestrel-core": "0.1.0",
83
+ "@michaelthielemann/kestrel-auth": "0.1.0",
84
84
  "@michaelthielemann/kestrel-delivery-live": "0.1.0",
85
- "@michaelthielemann/kestrel-delivery-static": "0.1.0",
86
85
  "@michaelthielemann/kestrel-fields": "0.1.0",
87
86
  "@michaelthielemann/kestrel-media": "0.1.0",
88
- "@michaelthielemann/kestrel-publishing": "0.1.0"
87
+ "@michaelthielemann/kestrel-publishing": "0.1.0",
88
+ "@michaelthielemann/kestrel-delivery-static": "0.1.0"
89
89
  },
90
90
  "//peers": "Framework singletons the layer sources import by bare specifier. Peers, not dependencies: a second copy breaks identity (two Vue instances; an h3 v1 `createError` handed to an h3 v2 error handler, which surfaces our 400/404/409 as bare 500s). All optional, because kestrel's own `nuxt` dependency already supplies them transitively — the entry pins identity where a copy exists rather than demanding one, and a required peer would ERESOLVE-fail npm installs on any Nuxt whose bundled range we did not anticipate. Each is also a devDependency so this repo resolves it without auto-install-peers writing a phantom root dependency into the lockfile.",
91
91
  "peerDependencies": {
@@ -27,8 +27,11 @@ log(`work dir: ${work}`)
27
27
 
28
28
  let server
29
29
  try {
30
+ // `pnpm --filter "@michaelthielemann/kestrel-*" -r build` does NOT reliably respect the dependency
31
+ // graph (observed: kestrel-access started before kestrel-core/kestrel-auth finished) — `build:packages`
32
+ // builds in explicit topological order instead.
30
33
  log('building @michaelthielemann/kestrel-* packages (dist/ must exist before pnpm pack)')
31
- run('pnpm', ['--filter', '@michaelthielemann/kestrel-*', '-r', 'build'], { cwd: root })
34
+ run('pnpm', ['build:packages'], { cwd: root })
32
35
 
33
36
  log('packing the engine + every @michaelthielemann/kestrel-* package as npm publish would')
34
37
  const tarball = {}