@zenbujs/core 0.0.5 → 0.0.8

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.
Files changed (72) hide show
  1. package/dist/{advice-config-QYB2qEd_.mjs → advice-config-DXSIo0sg.mjs} +40 -39
  2. package/dist/advice.d.mts +8 -8
  3. package/dist/advice.mjs +2 -2
  4. package/dist/{base-window-BbFRRhKP.mjs → base-window-BxBZ2md_.mjs} +51 -7
  5. package/dist/{transforms-CuTODvDx.d.mts → build-config-Dzg2frpk.d.mts} +98 -28
  6. package/dist/build-config-pWdmLnrk.mjs +53 -0
  7. package/dist/{build-electron-CNJ0dLND.mjs → build-electron-Dsbb1EMl.mjs} +308 -120
  8. package/dist/{build-source-C2puqEVr.mjs → build-source-d1J3shV8.mjs} +62 -27
  9. package/dist/cli/bin.mjs +7 -7
  10. package/dist/cli/build.d.mts +2 -2
  11. package/dist/cli/build.mjs +2 -3
  12. package/dist/cli/resolve-config.mjs +1 -1
  13. package/dist/{cli-C3R1LBMY.mjs → cli-kL6mPgBE.mjs} +2 -2
  14. package/dist/config.d.mts +3 -3
  15. package/dist/config.mjs +2 -3
  16. package/dist/{db-xjvahRFJ.mjs → db-Bc292RYo.mjs} +2 -2
  17. package/dist/db.d.mts +1 -1
  18. package/dist/dev-B2emj0HZ.mjs +301 -0
  19. package/dist/env-bootstrap.d.mts +1 -1
  20. package/dist/events.d.mts +19 -0
  21. package/dist/events.mjs +1 -0
  22. package/dist/host-version-BIrF8tX7.mjs +65 -0
  23. package/dist/index-w5QyDjuf.d.mts +780 -0
  24. package/dist/index.d.mts +5 -6
  25. package/dist/index.mjs +2 -2
  26. package/dist/installing-preload.cjs +60 -0
  27. package/dist/launcher.mjs +2615 -122
  28. package/dist/{link-c0_aLWQ3.mjs → link-glX89NV5.mjs} +215 -89
  29. package/dist/{load-config-xMf2wxH8.mjs → load-config-C4Oe2qZO.mjs} +5 -1
  30. package/dist/loaders/zenbu.mjs +102 -0
  31. package/dist/node-loader.mjs +1 -1
  32. package/dist/{publish-source-Dill72NS.mjs → publish-source-Dq2c0iOw.mjs} +2 -2
  33. package/dist/react.d.mts +55 -6
  34. package/dist/react.mjs +116 -5
  35. package/dist/registry-CMp8FYgS.d.mts +47 -0
  36. package/dist/registry-generated.d.mts +26 -0
  37. package/dist/registry-generated.mjs +1 -0
  38. package/dist/registry.d.mts +2 -2
  39. package/dist/{reloader-DzEO8kJr.mjs → reloader-B22UiNA2.mjs} +2 -4
  40. package/dist/{renderer-host-Cau9JK0v.mjs → renderer-host-DD16MXhI.mjs} +152 -43
  41. package/dist/{rpc-JfGv-Wuw.mjs → rpc-C4_NQmpT.mjs} +5 -4
  42. package/dist/{runtime-pCeVzj--.d.mts → runtime-BQWntcOb.d.mts} +85 -48
  43. package/dist/runtime.d.mts +2 -2
  44. package/dist/runtime.mjs +139 -83
  45. package/dist/{schema-Dl85YjXW.d.mts → schema-CjrMVk36.d.mts} +3 -3
  46. package/dist/schema.d.mts +1 -1
  47. package/dist/schema.mjs +1 -1
  48. package/dist/{server-y3PPbh3l.mjs → server-CZLMF8Dj.mjs} +1 -3
  49. package/dist/services/default.d.mts +3 -3
  50. package/dist/services/default.mjs +14 -13
  51. package/dist/services/index.d.mts +2 -280
  52. package/dist/services/index.mjs +8 -7
  53. package/dist/setup-gate.d.mts +1 -1
  54. package/dist/setup-gate.mjs +117 -24
  55. package/dist/{transform-CmFYPmt8.mjs → transform-BzrwkEdf.mjs} +22 -916
  56. package/dist/updater-DCkz9M1c.mjs +1008 -0
  57. package/dist/{vite-plugins-Do7liKi_.mjs → vite-plugins-tt6KAtyE.mjs} +26 -25
  58. package/dist/vite.d.mts +3 -3
  59. package/dist/vite.mjs +1 -1
  60. package/dist/{window-o2NGUsIb.mjs → window-YFKvAM0l.mjs} +30 -16
  61. package/package.json +15 -2
  62. package/dist/build-config-C3a-o3_B.mjs +0 -23
  63. package/dist/dev-Dazhu66l.mjs +0 -85
  64. package/dist/registry-eX6e2oql.d.mts +0 -61
  65. package/dist/transforms-htxfTwsY.mjs +0 -47
  66. /package/dist/{config-DXRCDUxG.mjs → config-BK78JDRI.mjs} +0 -0
  67. /package/dist/{env-bootstrap-DW2hVhSO.d.mts → env-bootstrap-rTs8KR3-.d.mts} +0 -0
  68. /package/dist/{index-M_lSNBrq.d.mts → index-DeDxePAa.d.mts} +0 -0
  69. /package/dist/{mirror-sync-PDzxhf1w.mjs → mirror-sync-pYU6f3-c.mjs} +0 -0
  70. /package/dist/{monorepo-3avKJwzJ.mjs → monorepo-Dct-kkbQ.mjs} +0 -0
  71. /package/dist/{node-_8xShqxr.mjs → node-BhfLKYCi.mjs} +0 -0
  72. /package/dist/{setup-gate-Dcy8gGPJ.d.mts → setup-gate-BQq0QgZH.d.mts} +0 -0
@@ -1,7 +1,12 @@
1
- import { t as loadConfig } from "./load-config-xMf2wxH8.mjs";
1
+ import { n as __exportAll } from "./chunk-DsiFFCwN.mjs";
2
+ import { t as loadConfig } from "./load-config-C4Oe2qZO.mjs";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  //#region src/cli/commands/link.ts
6
+ var link_exports = /* @__PURE__ */ __exportAll({
7
+ linkProject: () => linkProject,
8
+ runLink: () => runLink
9
+ });
5
10
  const CONFIG_NAMES = [
6
11
  "zenbu.config.ts",
7
12
  "zenbu.config.mts",
@@ -79,13 +84,7 @@ function resolveRegistryDir(opts) {
79
84
  if (opts.manifest.devAppPath) return path.resolve(manifestDir, opts.manifest.devAppPath, "types");
80
85
  const appRoot = findAppRoot(manifestDir);
81
86
  if (appRoot) return path.join(appRoot, "types");
82
- console.error(`zen link: could not determine target types directory for ${opts.manifestPath}.`);
83
- console.error(` Try one of:`);
84
- console.error(` - run from inside an app dir (with a zenbu.config.ts),`);
85
- console.error(` - add a "devAppPath" field to ${path.basename(opts.manifestPath)} pointing at the host app,`);
86
- console.error(` - create a tsconfig.local.json with a "#registry/*" path mapping,`);
87
- console.error(` - or pass --registry <dir>.`);
88
- process.exit(1);
87
+ throw new Error(`zen link: could not determine target types directory for ${opts.manifestPath}.\n Try one of:\n - run from inside an app dir (with a zenbu.config.ts),\n - add a "devAppPath" field to ${path.basename(opts.manifestPath)} pointing at the host app,\n - create a tsconfig.local.json with a "#registry/*" path mapping,\n - or pass --registry <dir>.`);
89
88
  }
90
89
  function expandGlob(baseDir, pattern) {
91
90
  if (!pattern.includes("*")) {
@@ -103,15 +102,12 @@ function expandGlob(baseDir, pattern) {
103
102
  }
104
103
  function discoverServices(baseDir, serviceGlobs) {
105
104
  const entries = [];
106
- const classRe = /export\s+class\s+(\w+)\s+extends\s+(?:Service\b|serviceWithDeps\s*\()/;
107
- const keyRe = /static\s+key\s*=\s*["']([^"']+)["']/;
105
+ const classKeyRe = /export\s+class\s+(\w+)\s+extends\s+Service\.create\s*\(\s*\{[\s\S]*?\bkey\s*:\s*["']([^"']+)["']/;
108
106
  for (const glob of serviceGlobs) for (const filePath of expandGlob(baseDir, glob)) {
109
- const content = fs.readFileSync(filePath, "utf8");
110
- const classMatch = content.match(classRe);
111
- const keyMatch = content.match(keyRe);
112
- if (classMatch && keyMatch) entries.push({
113
- className: classMatch[1],
114
- key: keyMatch[1],
107
+ const match = fs.readFileSync(filePath, "utf8").match(classKeyRe);
108
+ if (match) entries.push({
109
+ className: match[1],
110
+ key: match[2],
115
111
  filePath
116
112
  });
117
113
  }
@@ -150,7 +146,7 @@ function generateServicesFile(registryDir, allServices) {
150
146
  return [
151
147
  "// Generated by: zen link",
152
148
  "",
153
- `import type { CoreServiceRouter } from "@zenbujs/core/registry"`,
149
+ `import type { CoreServiceRouter } from "@zenbujs/core/registry-generated"`,
154
150
  ...imports,
155
151
  "",
156
152
  `type ServiceBase =\n${SERVICE_BASE_LITERAL}`,
@@ -190,7 +186,7 @@ function generatePreloadsFile(registryDir, allPreloads) {
190
186
  return [
191
187
  "// Generated by: zen link",
192
188
  "",
193
- `import type { CorePreloads } from "@zenbujs/core/registry"`,
189
+ `import type { CorePreloads } from "@zenbujs/core/registry-generated"`,
194
190
  ...imports,
195
191
  "",
196
192
  "export type PluginPreloads = {",
@@ -220,7 +216,7 @@ function generateEventsFile(registryDir, allEvents) {
220
216
  return [
221
217
  "// Generated by: zen link",
222
218
  "",
223
- `import type { CoreEvents } from "@zenbujs/core/registry"`,
219
+ `import type { CoreEvents } from "@zenbujs/core/registry-generated"`,
224
220
  ...imports,
225
221
  "",
226
222
  "/**",
@@ -245,6 +241,7 @@ function generateRegisterFile() {
245
241
  return [
246
242
  "// Generated by: zen link",
247
243
  "",
244
+ `import type {} from "@zenbujs/core/registry"`,
248
245
  `import type { DbRoot } from "./db-sections"`,
249
246
  `import type { ServiceRouter } from "./services"`,
250
247
  `import type { PluginEvents } from "./events"`,
@@ -280,7 +277,7 @@ function generateDbSectionsFile(registryDir, allSchemas) {
280
277
  return [
281
278
  "// Generated by: zen link",
282
279
  "",
283
- `import type { CoreDbSections } from "@zenbujs/core/registry"`,
280
+ `import type { CoreDbSections } from "@zenbujs/core/registry-generated"`,
284
281
  ...imports,
285
282
  "",
286
283
  "export type PluginDbSections = {",
@@ -367,18 +364,26 @@ function parseLinkArgs(argv) {
367
364
  let manifestArg = null;
368
365
  let typesConfigArg = null;
369
366
  let registryOverride = null;
367
+ let surfaceOutArg = null;
368
+ let augmentOutArg = null;
370
369
  for (let i = 0; i < argv.length; i++) {
371
370
  const arg = argv[i];
372
371
  if (arg === "--registry" && i + 1 < argv.length) registryOverride = argv[++i];
373
372
  else if (arg.startsWith("--registry=")) registryOverride = arg.slice(11);
374
373
  else if (arg === "--types-config" && i + 1 < argv.length) typesConfigArg = argv[++i];
375
374
  else if (arg.startsWith("--types-config=")) typesConfigArg = arg.slice(15);
375
+ else if (arg === "--out" && i + 1 < argv.length) surfaceOutArg = argv[++i];
376
+ else if (arg.startsWith("--out=")) surfaceOutArg = arg.slice(6);
377
+ else if (arg === "--augment-out" && i + 1 < argv.length) augmentOutArg = argv[++i];
378
+ else if (arg.startsWith("--augment-out=")) augmentOutArg = arg.slice(14);
376
379
  else if (!arg.startsWith("-") && !manifestArg) manifestArg = arg;
377
380
  }
378
381
  return {
379
382
  manifestArg,
380
383
  typesConfigArg,
381
- registryOverride
384
+ registryOverride,
385
+ surfaceOutArg,
386
+ augmentOutArg
382
387
  };
383
388
  }
384
389
  /**
@@ -386,26 +391,27 @@ function parseLinkArgs(argv) {
386
391
  * export is a `definePlugin({...})`). Resolved upstream by `loadConfig` so
387
392
  * we just consume the resolved record here.
388
393
  */
389
- function linkResolvedPlugin(plugin, existing) {
390
- console.log(`Linking types "${plugin.name}" from ${plugin.dir}`);
394
+ function linkResolvedPlugin(plugin, existing, opts) {
395
+ const log = opts.quiet ? () => {} : (msg) => console.log(msg);
396
+ log(`Linking types "${plugin.name}" from ${plugin.dir}`);
391
397
  const serviceGlobs = plugin.services.map((abs) => path.relative(plugin.dir, abs).split(path.sep).join("/"));
392
398
  const serviceEntries = discoverServices(plugin.dir, serviceGlobs);
393
- console.log(` Found ${serviceEntries.length} service(s)`);
399
+ log(` Found ${serviceEntries.length} service(s)`);
394
400
  const schemaEntry = plugin.schemaPath ? {
395
401
  name: plugin.name,
396
402
  schemaPath: plugin.schemaPath
397
403
  } : null;
398
- if (schemaEntry) console.log(` Schema: ${schemaEntry.schemaPath}`);
404
+ if (schemaEntry) log(` Schema: ${schemaEntry.schemaPath}`);
399
405
  const preloadEntry = plugin.preloadPath ? {
400
406
  name: plugin.name,
401
407
  preloadPath: plugin.preloadPath
402
408
  } : null;
403
- if (preloadEntry) console.log(` Preload: ${preloadEntry.preloadPath}`);
409
+ if (preloadEntry) log(` Preload: ${preloadEntry.preloadPath}`);
404
410
  const eventsEntry = plugin.eventsPath ? {
405
411
  name: plugin.name,
406
412
  eventsPath: plugin.eventsPath
407
413
  } : null;
408
- if (eventsEntry) console.log(` Events: ${eventsEntry.eventsPath}`);
414
+ if (eventsEntry) log(` Events: ${eventsEntry.eventsPath}`);
409
415
  existing.services.set(plugin.name, serviceEntries);
410
416
  if (schemaEntry) existing.schemas.set(plugin.name, schemaEntry);
411
417
  if (preloadEntry) existing.preloads.set(plugin.name, preloadEntry);
@@ -414,39 +420,116 @@ function linkResolvedPlugin(plugin, existing) {
414
420
  else existing.events.delete(plugin.name);
415
421
  }
416
422
  /**
417
- * Variant for the framework-internal `--types-config <path>` flow where the
418
- * input is a small JSON file (e.g. `packages/core/zenbu-types.config.json`).
419
- * Used by `pnpm link:types` inside core to bake its own services/registry
420
- * types without going through a `zenbu.config.ts`.
423
+ * Generator: `<core>/src/registry-generated.ts` the publishable
424
+ * type surface that downstream apps' `zen link`-generated files
425
+ * import from `@zenbujs/core/registry-generated`.
426
+ *
427
+ * Imports use public package exports (`@zenbujs/core/services`,
428
+ * `/events`, `/schema`) so the file resolves in any consumer
429
+ * regardless of how `@zenbujs/core` is installed.
421
430
  */
422
- function linkTypesConfig(jsonPath, existing) {
423
- const manifest = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
424
- const pluginName = manifest.name;
425
- const baseDir = path.dirname(jsonPath);
426
- console.log(`Linking types "${pluginName}" from ${baseDir}`);
427
- const serviceEntries = discoverServices(baseDir, manifest.services ?? []);
428
- console.log(` Found ${serviceEntries.length} service(s)`);
429
- const schemaEntry = manifest.schema ? {
430
- name: pluginName,
431
- schemaPath: path.resolve(baseDir, manifest.schema)
432
- } : null;
433
- if (schemaEntry) console.log(` Schema: ${schemaEntry.schemaPath}`);
434
- const preloadEntry = manifest.preload ? {
435
- name: pluginName,
436
- preloadPath: path.resolve(baseDir, manifest.preload)
437
- } : null;
438
- if (preloadEntry) console.log(` Preload: ${preloadEntry.preloadPath}`);
439
- const eventsEntry = manifest.events ? {
440
- name: pluginName,
441
- eventsPath: path.resolve(baseDir, manifest.events)
442
- } : null;
443
- if (eventsEntry) console.log(` Events: ${eventsEntry.eventsPath}`);
444
- existing.services.set(pluginName, serviceEntries);
445
- if (schemaEntry) existing.schemas.set(pluginName, schemaEntry);
446
- if (preloadEntry) existing.preloads.set(pluginName, preloadEntry);
447
- else existing.preloads.delete(pluginName);
448
- if (eventsEntry) existing.events.set(pluginName, eventsEntry);
449
- else existing.events.delete(pluginName);
431
+ function generateCoreSurfaceFile(args) {
432
+ const sorted = [...args.services].sort((a, b) => a.className.localeCompare(b.className));
433
+ const importedNames = sorted.map((s) => ` ${s.className},`).join("\n");
434
+ const routerEntries = sorted.map((s) => {
435
+ return ` ${/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(s.key) ? s.key : `"${s.key}"`}: ExtractRpcMethods<${s.className}>;`;
436
+ }).join("\n");
437
+ const lines = [
438
+ "// Generated by: pnpm link:types",
439
+ "// DO NOT EDIT. Regenerated automatically (also wired into `prebuild`).",
440
+ ""
441
+ ];
442
+ if (sorted.length > 0) {
443
+ lines.push("import type {");
444
+ lines.push(importedNames);
445
+ lines.push(`} from "@zenbujs/core/services"`);
446
+ }
447
+ if (args.hasEvents) lines.push(`import type { Events as Events_core } from "@zenbujs/core/events"`);
448
+ if (args.hasSchema) lines.push(`import type { SchemaRoot as SchemaRoot_core } from "@zenbujs/core/schema"`);
449
+ lines.push("");
450
+ lines.push(`type ServiceBase =\n${SERVICE_BASE_LITERAL}`);
451
+ lines.push("");
452
+ lines.push("type ExtractRpcMethods<T> = {");
453
+ lines.push(" [K in Exclude<keyof T, ServiceBase | `_${string}`> as T[K] extends (");
454
+ lines.push(" ...args: any[]");
455
+ lines.push(" ) => any");
456
+ lines.push(" ? K");
457
+ lines.push(" : never]: T[K]");
458
+ lines.push("}");
459
+ lines.push("");
460
+ lines.push("export type CoreServiceRouter = {");
461
+ if (routerEntries.length > 0) lines.push(routerEntries);
462
+ lines.push("}");
463
+ lines.push("");
464
+ lines.push(`export type CoreEvents = ${args.hasEvents ? "Events_core" : "{}"}`);
465
+ lines.push("");
466
+ lines.push("export type CoreDbSections = {");
467
+ if (args.hasSchema) lines.push(" core: SchemaRoot_core;");
468
+ lines.push("}");
469
+ lines.push("");
470
+ lines.push("export type CorePreloads = {}");
471
+ lines.push("");
472
+ return lines.join("\n");
473
+ }
474
+ /**
475
+ * Generator: `<core>/types/zenbu-register.ts` — local-only
476
+ * augmentation that drives core's OWN typecheck. Pulls
477
+ * `Core*` types from the relative path `../src/registry-generated`
478
+ * (so it works without depending on package self-resolution) and
479
+ * declares the `ZenbuRegister` augmentation. NOT in
480
+ * `package.json#files`; never reaches downstream consumers (where
481
+ * it would conflict with their own augmentation).
482
+ */
483
+ function generateCoreAugmentFile() {
484
+ return [
485
+ "// Generated by: pnpm link:types",
486
+ "// DO NOT EDIT. Local-only augmentation (NOT shipped via package.json#files).",
487
+ "// Drives core's own typecheck so useRpc()/useEvents()/useDb() resolve",
488
+ "// to the registered surface inside packages/core/src/.",
489
+ "",
490
+ `import type {} from "@zenbujs/core/registry"`,
491
+ `import type {`,
492
+ ` CoreServiceRouter,`,
493
+ ` CoreEvents,`,
494
+ ` CoreDbSections,`,
495
+ `} from "../src/registry-generated"`,
496
+ "",
497
+ `declare module "@zenbujs/core/registry" {`,
498
+ " interface ZenbuRegister {",
499
+ " rpc: CoreServiceRouter",
500
+ " events: CoreEvents",
501
+ " db: { plugin: CoreDbSections }",
502
+ " }",
503
+ "}",
504
+ "",
505
+ "export {}",
506
+ ""
507
+ ].join("\n");
508
+ }
509
+ /**
510
+ * Two-file write for the `--types-config` flow: the publishable
511
+ * surface (`registry-generated.ts`) plus the local-only
512
+ * augmentation (`zenbu-register.ts`). Skips writes when content is
513
+ * identical so downstream watchers (Vite, tsc --watch) don't see
514
+ * spurious mtime bumps.
515
+ */
516
+ function writeCoreSurfaceFiles(args) {
517
+ const surface = generateCoreSurfaceFile({
518
+ services: args.services,
519
+ hasEvents: args.hasEvents,
520
+ hasSchema: args.hasSchema
521
+ });
522
+ const augment = generateCoreAugmentFile();
523
+ for (const [target, body] of [[args.surfaceOut, surface], [args.augmentOut, augment]]) {
524
+ fs.mkdirSync(path.dirname(target), { recursive: true });
525
+ let prev = null;
526
+ try {
527
+ prev = fs.readFileSync(target, "utf8");
528
+ } catch {}
529
+ if (prev === body) continue;
530
+ fs.writeFileSync(target, body);
531
+ if (!args.quiet) console.log(` Wrote ${target}`);
532
+ }
450
533
  }
451
534
  /**
452
535
  * Per-install bootstrap for a plugin. The plugin ships a portable
@@ -472,7 +555,7 @@ function linkTypesConfig(jsonPath, existing) {
472
555
  * `"extends": "./tsconfig.local.json"`. The generated file then carries
473
556
  * `paths` + `include`, which TS merges into the IDE's resolved program.
474
557
  */
475
- function writePluginTsconfigLocal(pluginDir, registryDir) {
558
+ function writePluginTsconfigLocal(pluginDir, registryDir, opts) {
476
559
  const ownTsconfig = path.join(pluginDir, "tsconfig.json");
477
560
  if (!fs.existsSync(ownTsconfig)) return;
478
561
  for (const name of CONFIG_NAMES) if (fs.existsSync(path.join(pluginDir, name))) return;
@@ -490,23 +573,67 @@ function writePluginTsconfigLocal(pluginDir, registryDir) {
490
573
  } catch {}
491
574
  if (prev === next) return;
492
575
  fs.writeFileSync(target, next);
493
- console.log(` Wrote ${target}`);
576
+ if (!opts.quiet) console.log(` Wrote ${target}`);
577
+ }
578
+ /**
579
+ * Programmatic variant of `zen link` for the host-project flow. Throws on
580
+ * any error instead of `process.exit`. Used by:
581
+ * - `runLink` (CLI entry)
582
+ * - `link-watcher.ts` (file watcher used by `zen dev`)
583
+ *
584
+ * `quiet: true` suppresses the per-step console output the CLI emits — the
585
+ * watcher uses this so the dev terminal stays clean across rapid edits.
586
+ */
587
+ async function linkProject(projectDir, opts = {}) {
588
+ const log = opts.quiet ? () => {} : (msg) => console.log(msg);
589
+ const { resolved, pluginSourceFiles } = await loadConfig(projectDir);
590
+ const registryDir = resolveRegistryDir({
591
+ manifestPath: resolved.configPath,
592
+ manifest: {},
593
+ registryOverride: opts.registryOverride ?? null
594
+ });
595
+ log(`Registry: ${registryDir}`);
596
+ fs.mkdirSync(registryDir, { recursive: true });
597
+ const existing = readExistingRegistry(registryDir);
598
+ for (const plugin of resolved.plugins) linkResolvedPlugin(plugin, existing, { quiet: !!opts.quiet });
599
+ writeRegistryFiles(registryDir, existing, { quiet: !!opts.quiet });
600
+ for (const plugin of resolved.plugins) writePluginTsconfigLocal(plugin.dir, registryDir, { quiet: !!opts.quiet });
601
+ return {
602
+ registryDir,
603
+ resolvedConfigPath: resolved.configPath,
604
+ pluginSourceFiles,
605
+ resolved
606
+ };
494
607
  }
495
608
  async function runLink(argv) {
496
- const { manifestArg, typesConfigArg, registryOverride } = parseLinkArgs(argv);
609
+ const { manifestArg, typesConfigArg, registryOverride, surfaceOutArg, augmentOutArg } = parseLinkArgs(argv);
497
610
  if (typesConfigArg) {
498
611
  const typeConfigPath = path.resolve(typesConfigArg);
499
- const registryDir = resolveRegistryDir({
500
- manifestPath: typeConfigPath,
501
- manifest: JSON.parse(fs.readFileSync(typeConfigPath, "utf8")),
502
- registryOverride
503
- });
504
- console.log(`Registry: ${registryDir}`);
505
- fs.mkdirSync(registryDir, { recursive: true });
506
- const existing = readExistingRegistry(registryDir);
507
- linkTypesConfig(typeConfigPath, existing);
508
- writeRegistryFiles(registryDir, existing);
509
- console.log("Done.");
612
+ const rootManifest = JSON.parse(fs.readFileSync(typeConfigPath, "utf8"));
613
+ const baseDir = path.dirname(typeConfigPath);
614
+ const surfaceOut = surfaceOutArg ? path.resolve(surfaceOutArg) : path.join(baseDir, "src", "registry-generated.ts");
615
+ const augmentOut = augmentOutArg ? path.resolve(augmentOutArg) : path.join(baseDir, "types", "zenbu-register.ts");
616
+ try {
617
+ console.log(`Linking core types from ${baseDir}`);
618
+ const services = discoverServices(baseDir, rootManifest.services ?? []);
619
+ console.log(` Found ${services.length} service(s)`);
620
+ const hasEvents = !!rootManifest.events;
621
+ const hasSchema = !!rootManifest.schema;
622
+ if (rootManifest.events) console.log(` Events: ${path.resolve(baseDir, rootManifest.events)}`);
623
+ if (rootManifest.schema) console.log(` Schema: ${path.resolve(baseDir, rootManifest.schema)}`);
624
+ writeCoreSurfaceFiles({
625
+ services,
626
+ hasEvents,
627
+ hasSchema,
628
+ surfaceOut,
629
+ augmentOut,
630
+ quiet: false
631
+ });
632
+ console.log("Done.");
633
+ } catch (err) {
634
+ console.error(err instanceof Error ? err.message : err);
635
+ process.exit(1);
636
+ }
510
637
  return;
511
638
  }
512
639
  const projectDir = manifestArg ? path.resolve(manifestArg) : findProjectDir(process.cwd());
@@ -515,21 +642,15 @@ async function runLink(argv) {
515
642
  console.error(" For internal framework types, pass --types-config <path>.");
516
643
  process.exit(1);
517
644
  }
518
- const { resolved } = await loadConfig(projectDir);
519
- const registryDir = resolveRegistryDir({
520
- manifestPath: resolved.configPath,
521
- manifest: {},
522
- registryOverride
523
- });
524
- console.log(`Registry: ${registryDir}`);
525
- fs.mkdirSync(registryDir, { recursive: true });
526
- const existing = readExistingRegistry(registryDir);
527
- for (const plugin of resolved.plugins) linkResolvedPlugin(plugin, existing);
528
- writeRegistryFiles(registryDir, existing);
529
- for (const plugin of resolved.plugins) writePluginTsconfigLocal(plugin.dir, registryDir);
530
- console.log("Done.");
645
+ try {
646
+ await linkProject(projectDir, { registryOverride });
647
+ console.log("Done.");
648
+ } catch (err) {
649
+ console.error(err instanceof Error ? err.message : err);
650
+ process.exit(1);
651
+ }
531
652
  }
532
- function writeRegistryFiles(registryDir, existing) {
653
+ function writeRegistryFiles(registryDir, existing, opts) {
533
654
  const writes = [
534
655
  ["services.ts", generateServicesFile(registryDir, existing.services)],
535
656
  ["db-sections.ts", generateDbSectionsFile(registryDir, existing.schemas)],
@@ -539,9 +660,14 @@ function writeRegistryFiles(registryDir, existing) {
539
660
  ];
540
661
  for (const [name, body] of writes) {
541
662
  const target = path.join(registryDir, name);
663
+ let prev = null;
664
+ try {
665
+ prev = fs.readFileSync(target, "utf8");
666
+ } catch {}
667
+ if (prev === body) continue;
542
668
  fs.writeFileSync(target, body);
543
- console.log(` Wrote ${target}`);
669
+ if (!opts.quiet) console.log(` Wrote ${target}`);
544
670
  }
545
671
  }
546
672
  //#endregion
547
- export { runLink };
673
+ export { link_exports as n, linkProject as t };
@@ -1,5 +1,5 @@
1
1
  import { n as __exportAll } from "./chunk-DsiFFCwN.mjs";
2
- import { i as resolveBuildConfig } from "./build-config-C3a-o3_B.mjs";
2
+ import { i as resolveBuildConfig } from "./build-config-pWdmLnrk.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
@@ -114,6 +114,8 @@ async function loadConfig(projectDir) {
114
114
  })()?.isDirectory()) throw new Error(`${configPath}: uiEntrypoint must point at a directory; got ${config.uiEntrypoint}.`);
115
115
  const splashPath = path.join(uiEntrypointPath, "splash.html");
116
116
  if (!fs.existsSync(splashPath)) throw new Error(`${configPath}: uiEntrypoint directory ${config.uiEntrypoint} is missing required \`splash.html\`. The splash file is shown raw (no Vite) during the brief window between Electron startup and the app's first paint.`);
117
+ const installingCandidate = path.join(uiEntrypointPath, "installing.html");
118
+ const installingPath = fs.existsSync(installingCandidate) ? installingCandidate : void 0;
117
119
  const plugins = [];
118
120
  const pluginSourceFiles = [];
119
121
  for (const entry of config.plugins) {
@@ -128,8 +130,10 @@ async function loadConfig(projectDir) {
128
130
  dbPath,
129
131
  uiEntrypointPath,
130
132
  splashPath,
133
+ installingPath,
131
134
  plugins,
132
135
  build: resolveBuildConfig(config.build ?? {
136
+ hostVersion: "0.0.0",
133
137
  source: ".",
134
138
  include: ["**/*"]
135
139
  })
@@ -15,6 +15,13 @@ const stats = {
15
15
  let resolvedPayload = null;
16
16
  let resolvedPluginSourceFiles = [];
17
17
  /**
18
+ * Set of absolute service file paths derived from the resolved payload.
19
+ * Used by the `file://` branch in `load()` to decide which user files
20
+ * should have `runtime.register(...)` auto-injected. Refreshed alongside
21
+ * `resolvedPayload` whenever the loader picks up a new config snapshot.
22
+ */
23
+ let serviceFileSet = /* @__PURE__ */ new Set();
24
+ /**
18
25
  * Number of times we've materialized the plugin root since boot.
19
26
  */
20
27
  let pluginsRootInvocations = 0;
@@ -22,6 +29,7 @@ function initialize(data) {
22
29
  if (data?.payload) {
23
30
  resolvedPayload = data.payload;
24
31
  resolvedPluginSourceFiles = data.pluginSourceFiles ?? [];
32
+ serviceFileSet = collectServiceFiles(data.payload);
25
33
  }
26
34
  if (data?.tracePort) {
27
35
  tracePort = data.tracePort;
@@ -54,6 +62,86 @@ function buildSource(imports) {
54
62
  return imports.map((specifier) => `import ${JSON.stringify(specifier)}\n`).join("");
55
63
  }
56
64
  /**
65
+ * Matches the canonical service declaration shape after the upstream HMR
66
+ * loader (dynohot) has transformed the module. Dynohot strips the
67
+ * `export` keyword and minifies the source, so the regex must not
68
+ * require `export` and must tolerate a non-whitespace separator (e.g.
69
+ * `;class Foo extends Service.create(...)`). The `\b` boundary still
70
+ * rejects `subclass Foo` matches inside identifiers/comments.
71
+ *
72
+ * export class FooService extends Service.create({ ... }) { ... }
73
+ * ;class FooService extends Service.create({...}) {...} // post-dynohot
74
+ *
75
+ * `zen link` runs on the original on-disk source (pre-dynohot), so it
76
+ * keeps the `export` requirement in `discoverServices` to avoid matching
77
+ * non-exported helper classes.
78
+ */
79
+ const SERVICE_CLASS_RE = /\bclass\s+(\w+)\s+extends\s+Service\.create\s*\(/g;
80
+ /**
81
+ * Decode the loader's `source` payload (which Node permits as string,
82
+ * Buffer, ArrayBuffer, or any TypedArray) into a plain UTF-8 string.
83
+ */
84
+ function decodeSource(source) {
85
+ if (typeof source === "string") return source;
86
+ if (source instanceof Uint8Array) return Buffer.from(source).toString("utf8");
87
+ if (source instanceof ArrayBuffer) return Buffer.from(source).toString("utf8");
88
+ return String(source);
89
+ }
90
+ /**
91
+ * Tail-append a `runtime.register(<Class>, import.meta)` call to the
92
+ * loader result so user service files don't have to carry the boilerplate.
93
+ *
94
+ * - Idempotent: if the source already contains `runtime.register(`
95
+ * (manual migration leftover, or a user choosing to register
96
+ * explicitly), we leave it untouched.
97
+ * - Errors loudly when zero or multiple service classes are found, since
98
+ * the runtime's HMR model is "one slot per import.meta".
99
+ * - The runtime singleton imported here resolves through the loader's
100
+ * own `@zenbujs/core` short-circuit in `resolve()`, so the registered
101
+ * class lands in the same registry the rest of the framework reads.
102
+ */
103
+ function appendAutoRegister(downstream, filePath) {
104
+ if (!downstream || typeof downstream !== "object") return downstream;
105
+ const result = downstream;
106
+ if (result.format !== "module") return downstream;
107
+ if (result.source == null) return downstream;
108
+ const source = decodeSource(result.source);
109
+ if (/\bruntime\s*\.\s*register\s*\(/.test(source)) return {
110
+ ...result,
111
+ source
112
+ };
113
+ SERVICE_CLASS_RE.lastIndex = 0;
114
+ const matches = [...source.matchAll(SERVICE_CLASS_RE)];
115
+ if (matches.length === 0) throw new Error(`[zenbu-loader] auto-register: no \`class <Name> extends Service.create({ ... })\` found in ${filePath}.\n Either rewrite the class to use Service.create or add an explicit \`runtime.register(<Class>, import.meta)\`.`);
116
+ if (matches.length > 1) {
117
+ const names = matches.map((m) => m[1]).join(", ");
118
+ throw new Error(`[zenbu-loader] auto-register: ${matches.length} service classes (${names}) in ${filePath}.\n The HMR model is one service per file; split into separate files.`);
119
+ }
120
+ const tail = `\n;import { runtime as __zenbu_runtime__ } from "@zenbujs/core/runtime";\n__zenbu_runtime__.register(${matches[0][1]}, import.meta);\n`;
121
+ return {
122
+ ...result,
123
+ source: source + tail
124
+ };
125
+ }
126
+ /**
127
+ * Expand each plugin's `services` glob list into a flat set of absolute
128
+ * file paths. Consulted by the `file://` branch in `load()` to decide
129
+ * whether to auto-inject `runtime.register(...)` for the loaded module.
130
+ *
131
+ * Glob expansion uses the same `expandGlob` (and therefore the same
132
+ * `fs.readdirSync` snapshot) that `buildPluginBarrel` uses, so the set
133
+ * stays in lockstep with what the barrel actually imports.
134
+ */
135
+ function collectServiceFiles(payload) {
136
+ const set = /* @__PURE__ */ new Set();
137
+ for (const plugin of payload.plugins) for (const entry of plugin.services) {
138
+ const resolved = path.isAbsolute(entry) ? entry : path.resolve(plugin.dir, entry);
139
+ if (resolved.includes("*")) for (const f of expandGlob(resolved)) set.add(f);
140
+ else set.add(resolved);
141
+ }
142
+ return set;
143
+ }
144
+ /**
57
145
  * Read the resolved config from the process global. setup-gate populates
58
146
  * this before registering our loader, so by the time `load()` is invoked
59
147
  * the snapshot is always present.
@@ -99,6 +187,7 @@ function getResolvedConfig(configPath) {
99
187
  const fresh = resolveConfigViaSubprocess(path.dirname(configPath));
100
188
  resolvedPayload = fresh.payload;
101
189
  resolvedPluginSourceFiles = fresh.pluginSourceFiles;
190
+ serviceFileSet = collectServiceFiles(fresh.payload);
102
191
  return fresh;
103
192
  }
104
193
  /**
@@ -243,6 +332,19 @@ function loadImpl(url, context, nextLoad) {
243
332
  shortCircuit: true
244
333
  };
245
334
  }
335
+ if (url.startsWith("file://")) {
336
+ let filePath;
337
+ try {
338
+ filePath = fileURLToPath(url);
339
+ } catch {
340
+ filePath = "";
341
+ }
342
+ if (filePath && serviceFileSet.has(filePath)) {
343
+ const downstream = nextLoad(url, context);
344
+ if (downstream && typeof downstream.then === "function") return Promise.resolve(downstream).then((r) => appendAutoRegister(r, filePath));
345
+ return appendAutoRegister(downstream, filePath);
346
+ }
347
+ }
246
348
  if (url.startsWith("zenbu:barrel?")) {
247
349
  const params = new URL(url).searchParams;
248
350
  const pluginRaw = decodeURIComponent(params.get("plugin") ?? "");
@@ -1,4 +1,4 @@
1
- import { n as require_lib, t as zenbuAdviceTransform } from "./transform-CmFYPmt8.mjs";
1
+ import { n as require_lib, t as zenbuAdviceTransform } from "./transform-BzrwkEdf.mjs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  //#region ../advice/src/node-loader.ts
4
4
  var import_lib = require_lib();
@@ -1,5 +1,5 @@
1
- import { t as loadConfig } from "./load-config-xMf2wxH8.mjs";
2
- import { n as init, r as push } from "./mirror-sync-PDzxhf1w.mjs";
1
+ import { t as loadConfig } from "./load-config-C4Oe2qZO.mjs";
2
+ import { n as init, r as push } from "./mirror-sync-pYU6f3-c.mjs";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { execFileSync } from "node:child_process";