@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,5 +1,5 @@
1
- import { t as loadConfig } from "./load-config-xMf2wxH8.mjs";
2
- import { t as hashDir } from "./mirror-sync-PDzxhf1w.mjs";
1
+ import { t as loadConfig } from "./load-config-C4Oe2qZO.mjs";
2
+ import { t as hashDir } 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";
@@ -59,24 +59,41 @@ async function collectFiles(sourceDir, config) {
59
59
  }
60
60
  return [...seen].sort();
61
61
  }
62
- async function applyTransforms(input, transforms) {
63
- let code = input.code;
64
- let drop = false;
65
- for (const transform of transforms) {
66
- const result = await transform({
67
- path: input.path,
68
- code
69
- });
70
- if (!result) continue;
71
- if (result.drop) {
72
- drop = true;
73
- break;
74
- }
75
- if (typeof result.code === "string") code = result.code;
62
+ /**
63
+ * Run every plugin's `transform` in declaration order. A `null` return drops
64
+ * the file and short-circuits remaining plugins. A string replaces the
65
+ * working contents and feeds the next plugin. `void` leaves contents alone.
66
+ */
67
+ async function runTransform(filePath, initialContents, plugins, ctx) {
68
+ let contents = initialContents;
69
+ for (const plugin of plugins) {
70
+ if (!plugin.transform) continue;
71
+ const result = await plugin.transform({
72
+ path: filePath,
73
+ contents
74
+ }, ctx);
75
+ if (result === null) return {
76
+ contents,
77
+ drop: true
78
+ };
79
+ if (typeof result === "string") contents = result;
76
80
  }
77
81
  return {
78
- code,
79
- drop
82
+ contents,
83
+ drop: false
84
+ };
85
+ }
86
+ function makeEmit(outDir, recordWrite) {
87
+ return (relPath, contents) => {
88
+ if (typeof relPath !== "string" || relPath.length === 0) throw new Error(`emit: relPath must be a non-empty string, got ${typeof relPath}`);
89
+ if (path.isAbsolute(relPath)) throw new Error(`emit: relPath must be relative, got absolute path "${relPath}"`);
90
+ const normalized = path.posix.normalize(relPath.split(path.sep).join("/"));
91
+ if (normalized.startsWith("../") || normalized === "..") throw new Error(`emit: relPath must not escape outDir, got "${relPath}"`);
92
+ const dst = path.join(outDir, normalized);
93
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
94
+ if (typeof contents === "string") fs.writeFileSync(dst, contents);
95
+ else fs.writeFileSync(dst, contents);
96
+ recordWrite(normalized);
80
97
  };
81
98
  }
82
99
  async function runBuildSource(argv) {
@@ -101,40 +118,58 @@ async function runBuildSource(argv) {
101
118
  console.error("zen build:source: no files matched the include/ignore globs");
102
119
  process.exit(1);
103
120
  }
121
+ const sourceSha = resolveSourceHead(projectDir);
122
+ const ctx = {
123
+ sourceDir,
124
+ outDir,
125
+ sourceSha,
126
+ configPath: resolved.configPath
127
+ };
104
128
  let written = 0;
105
129
  let dropped = 0;
130
+ let emitted = 0;
106
131
  for (const rel of files) {
107
132
  const src = path.join(sourceDir, rel);
108
133
  const dst = path.join(outDir, rel);
109
134
  const isText = isLikelyText(src);
110
- if (config.transforms.length === 0 || !isText) {
135
+ if (config.plugins.length === 0 || !isText) {
111
136
  await fsp.mkdir(path.dirname(dst), { recursive: true });
112
137
  await fsp.copyFile(src, dst);
113
138
  written += 1;
114
139
  continue;
115
140
  }
116
- const { code: nextCode, drop } = await applyTransforms({
117
- path: rel,
118
- code: await fsp.readFile(src, "utf8")
119
- }, config.transforms);
141
+ const { contents, drop } = await runTransform(rel, await fsp.readFile(src, "utf8"), config.plugins, ctx);
120
142
  if (drop) {
121
143
  dropped += 1;
122
144
  continue;
123
145
  }
124
146
  await fsp.mkdir(path.dirname(dst), { recursive: true });
125
- await fsp.writeFile(dst, nextCode);
147
+ await fsp.writeFile(dst, contents);
126
148
  written += 1;
127
149
  }
128
- const sourceSha = resolveSourceHead(projectDir);
150
+ if (config.plugins.length > 0) {
151
+ const emitCtx = {
152
+ ...ctx,
153
+ emit: makeEmit(outDir, () => {
154
+ emitted += 1;
155
+ })
156
+ };
157
+ for (const plugin of config.plugins) {
158
+ if (!plugin.done) continue;
159
+ await plugin.done(emitCtx);
160
+ }
161
+ }
129
162
  const meta = {
130
163
  sourceSha,
131
164
  contentHash: await hashDir(outDir),
132
165
  builtAt: (/* @__PURE__ */ new Date()).toISOString(),
133
166
  files: written,
134
- dropped
167
+ dropped,
168
+ emitted
135
169
  };
136
170
  await fsp.writeFile(path.join(outDir, ".sha"), JSON.stringify(meta, null, 2) + "\n");
137
- console.log(`\n ✓ ${written} file(s) written, ${dropped} dropped by transforms`);
171
+ const emittedSuffix = emitted > 0 ? `, ${emitted} emitted by plugins` : "";
172
+ console.log(`\n ✓ ${written} file(s) written, ${dropped} dropped${emittedSuffix}`);
138
173
  console.log(` source HEAD: ${sourceSha === "uncommitted" ? sourceSha : sourceSha.slice(0, 7)}\n`);
139
174
  }
140
175
  const TEXT_EXTENSIONS = new Set([
package/dist/cli/bin.mjs CHANGED
@@ -41,37 +41,37 @@ async function main() {
41
41
  const rest = argv.slice(1);
42
42
  switch (first) {
43
43
  case "dev": {
44
- const { runDev } = await import("../dev-Dazhu66l.mjs");
44
+ const { runDev } = await import("../dev-B2emj0HZ.mjs");
45
45
  await runDev(rest);
46
46
  return;
47
47
  }
48
48
  case "build:source": {
49
- const { runBuildSource } = await import("../build-source-C2puqEVr.mjs");
49
+ const { runBuildSource } = await import("../build-source-d1J3shV8.mjs");
50
50
  await runBuildSource(rest);
51
51
  return;
52
52
  }
53
53
  case "build:electron": {
54
- const { runBuildElectron } = await import("../build-electron-CNJ0dLND.mjs");
54
+ const { runBuildElectron } = await import("../build-electron-Dsbb1EMl.mjs");
55
55
  await runBuildElectron(rest);
56
56
  return;
57
57
  }
58
58
  case "publish:source": {
59
- const { runPublishSource } = await import("../publish-source-Dill72NS.mjs");
59
+ const { runPublishSource } = await import("../publish-source-Dq2c0iOw.mjs");
60
60
  await runPublishSource(rest);
61
61
  return;
62
62
  }
63
63
  case "link": {
64
- const { runLink } = await import("../link-c0_aLWQ3.mjs");
64
+ const { runLink } = await import("../link-glX89NV5.mjs").then((n) => n.n);
65
65
  await runLink(rest);
66
66
  return;
67
67
  }
68
68
  case "monorepo": {
69
- const { runMonorepo } = await import("../monorepo-3avKJwzJ.mjs");
69
+ const { runMonorepo } = await import("../monorepo-Dct-kkbQ.mjs");
70
70
  await runMonorepo(rest);
71
71
  return;
72
72
  }
73
73
  case "db": {
74
- const { runDb } = await import("../db-xjvahRFJ.mjs");
74
+ const { runDb } = await import("../db-Bc292RYo.mjs");
75
75
  await runDb(rest);
76
76
  return;
77
77
  }
@@ -1,2 +1,2 @@
1
- import { _ as resolveBuildConfig, c as ResolvedBuildConfig, d as Transform, f as TransformInput, i as BundleConfig, m as defineBuildConfig, n as stripIfDisabled, o as MirrorConfig, p as TransformOutput, r as BuildConfig, t as dropFiles } from "../transforms-CuTODvDx.mjs";
2
- export { type BuildConfig, type BundleConfig, type MirrorConfig, type ResolvedBuildConfig, type Transform, type TransformInput, type TransformOutput, defineBuildConfig, dropFiles, resolveBuildConfig, stripIfDisabled };
1
+ import { g as resolveBuildConfig, i as BundleConfig, n as BuildContext, o as EmitContext, p as defineBuildConfig, r as BuildPlugin, s as MirrorConfig, t as BuildConfig, u as ResolvedBuildConfig } from "../build-config-Dzg2frpk.mjs";
2
+ export { type BuildConfig, type BuildContext, type BuildPlugin, type BundleConfig, type EmitContext, type MirrorConfig, type ResolvedBuildConfig, defineBuildConfig, resolveBuildConfig };
@@ -1,3 +1,2 @@
1
- import { i as resolveBuildConfig, t as defineBuildConfig } from "../build-config-C3a-o3_B.mjs";
2
- import { n as stripIfDisabled, t as dropFiles } from "../transforms-htxfTwsY.mjs";
3
- export { defineBuildConfig, dropFiles, resolveBuildConfig, stripIfDisabled };
1
+ import { i as resolveBuildConfig, t as defineBuildConfig } from "../build-config-pWdmLnrk.mjs";
2
+ export { defineBuildConfig, resolveBuildConfig };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as loadConfig } from "../load-config-xMf2wxH8.mjs";
2
+ import { t as loadConfig } from "../load-config-C4Oe2qZO.mjs";
3
3
  import { register } from "tsx/esm/api";
4
4
  //#region src/cli/resolve-config.ts
5
5
  async function main() {
@@ -1,6 +1,6 @@
1
1
  import { o as getZodDefault, t as NO_DEFAULT } from "./schema-Ca7SxXgS.mjs";
2
2
  import { _ as handleErrnoException, d as readJsonFile, f as readJsonlFile, g as layer$7, l as paths, n as makeRootCache, t as handleWrite, v as FileSystem, y as BadArgument } from "./write-DgIRjo23.mjs";
3
- import { loadSchema } from "./config-DXRCDUxG.mjs";
3
+ import { loadSchema } from "./config-BK78JDRI.mjs";
4
4
  import fs from "node:fs";
5
5
  import * as NodePath from "node:path";
6
6
  import path from "node:path";
@@ -8030,7 +8030,7 @@ async function run(argv) {
8030
8030
  process.exit(args.help ? 0 : 1);
8031
8031
  }
8032
8032
  if (args.command === "generate") {
8033
- const { findConfigFile, loadConfig } = await import("./config-DXRCDUxG.mjs");
8033
+ const { findConfigFile, loadConfig } = await import("./config-BK78JDRI.mjs");
8034
8034
  const resolved = await loadConfig(args.config ?? findConfigFile(process.cwd()));
8035
8035
  await generateMigration({
8036
8036
  schemaPath: resolved.schemaPath,
package/dist/config.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { a as Config, c as ResolvedBuildConfig, d as Transform, f as TransformInput, g as definePlugin, h as defineConfig, i as BundleConfig, l as ResolvedConfig, m as defineBuildConfig, n as stripIfDisabled, o as MirrorConfig, p as TransformOutput, r as BuildConfig, s as Plugin, t as dropFiles, u as ResolvedPlugin } from "./transforms-CuTODvDx.mjs";
2
- import { _ as subscribeConfig, c as getPlugin, l as getPlugins, n as ConfigSnapshot, o as getAppEntrypoint, r as PluginRecord, s as getConfig, u as getSplashPath } from "./runtime-pCeVzj--.mjs";
3
- export { type BuildConfig, type BundleConfig, type Config, type ConfigSnapshot, type MirrorConfig, type Plugin, type PluginRecord, type ResolvedBuildConfig, type ResolvedConfig, type ResolvedPlugin, type Transform, type TransformInput, type TransformOutput, defineBuildConfig, defineConfig, definePlugin, dropFiles, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, stripIfDisabled, subscribeConfig };
1
+ import { a as Config, c as PackageManagerSpec, d as ResolvedConfig, f as ResolvedPlugin, h as definePlugin, i as BundleConfig, l as Plugin, m as defineConfig, n as BuildContext, o as EmitContext, p as defineBuildConfig, r as BuildPlugin, s as MirrorConfig, t as BuildConfig, u as ResolvedBuildConfig } from "./build-config-Dzg2frpk.mjs";
2
+ import { c as getConfig, d as getSplashPath, g as subscribeConfig, l as getPlugin, n as ConfigSnapshot, r as PluginRecord, s as getAppEntrypoint, u as getPlugins } from "./runtime-BQWntcOb.mjs";
3
+ export { type BuildConfig, type BuildContext, type BuildPlugin, type BundleConfig, type Config, type ConfigSnapshot, type EmitContext, type MirrorConfig, type PackageManagerSpec, type Plugin, type PluginRecord, type ResolvedBuildConfig, type ResolvedConfig, type ResolvedPlugin, defineBuildConfig, defineConfig, definePlugin, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, subscribeConfig };
package/dist/config.mjs CHANGED
@@ -1,4 +1,3 @@
1
1
  import { getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, subscribeConfig } from "./runtime.mjs";
2
- import { n as defineConfig, r as definePlugin, t as defineBuildConfig } from "./build-config-C3a-o3_B.mjs";
3
- import { n as stripIfDisabled, t as dropFiles } from "./transforms-htxfTwsY.mjs";
4
- export { defineBuildConfig, defineConfig, definePlugin, dropFiles, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, stripIfDisabled, subscribeConfig };
2
+ import { n as defineConfig, r as definePlugin, t as defineBuildConfig } from "./build-config-pWdmLnrk.mjs";
3
+ export { defineBuildConfig, defineConfig, definePlugin, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, subscribeConfig };
@@ -469,7 +469,7 @@ function parseGenerateArgs(argv) {
469
469
  }
470
470
  async function runGenerate(argv) {
471
471
  const flags = parseGenerateArgs(argv);
472
- const { generateMigration } = await import("./cli-C3R1LBMY.mjs");
472
+ const { generateMigration } = await import("./cli-kL6mPgBE.mjs");
473
473
  if (flags.schema || flags.migrationsOut) {
474
474
  if (!flags.schema || !flags.migrationsOut) {
475
475
  console.error("zen db generate: --schema and --migrations must be passed together.");
@@ -495,7 +495,7 @@ async function runGenerate(argv) {
495
495
  console.error(" --schema <path> --migrations <path> directly.");
496
496
  process.exit(1);
497
497
  }
498
- const { loadConfig } = await import("./load-config-xMf2wxH8.mjs").then((n) => n.n);
498
+ const { loadConfig } = await import("./load-config-C4Oe2qZO.mjs").then((n) => n.n);
499
499
  const { resolved } = await loadConfig(projectDir);
500
500
  const cwd = path.resolve(process.cwd());
501
501
  let bestMatch = null;
package/dist/db.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as createSchema, _ as InferSchema$1, a as dbStringify, b as blob, f as CollectionRefBrand$1, g as InferRoot$1, i as dbParse, n as connectReplica, o as ClientProxy, p as CollectionRefValue$1, s as CollectionNode$1, v as Schema$1, x as collection, y as SchemaShape$1 } from "./index-M_lSNBrq.mjs";
1
+ import { S as createSchema, _ as InferSchema$1, a as dbStringify, b as blob, f as CollectionRefBrand$1, g as InferRoot$1, i as dbParse, n as connectReplica, o as ClientProxy, p as CollectionRefValue$1, s as CollectionNode$1, v as Schema$1, x as collection, y as SchemaShape$1 } from "./index-DeDxePAa.mjs";
2
2
  import { z as z$1 } from "zod";
3
3
 
4
4
  //#region src/db.d.ts
@@ -0,0 +1,301 @@
1
+ import { t as linkProject } from "./link-glX89NV5.mjs";
2
+ import { existsSync, statSync } from "node:fs";
3
+ import path, { resolve } from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import { subscribe } from "@parcel/watcher";
6
+ //#region src/cli/lib/link-watcher.ts
7
+ const RELINK_DEBOUNCE_MS = 120;
8
+ const COMMON_IGNORES = [
9
+ "**/node_modules",
10
+ "**/.git",
11
+ "**/.zenbu",
12
+ "**/.next",
13
+ "**/dist",
14
+ "**/build",
15
+ "**/.turbo",
16
+ "**/.cache"
17
+ ];
18
+ /**
19
+ * Start a file watcher that re-runs `zen link` whenever any source the
20
+ * link command consumes changes. Used by `zen dev` so users don't have to
21
+ * remember to rerun `zen link` after adding a service file or editing
22
+ * `zenbu.config.ts`.
23
+ *
24
+ * Failure modes:
25
+ * - Initial link throws (bad config): we still start the watcher so the
26
+ * user can recover by editing the file. The error is reported once;
27
+ * subsequent failed relinks during typing stay silent on purpose.
28
+ * - Relink throws (broken syntax mid-edit): swallowed silently. The next
29
+ * successful relink will catch up.
30
+ *
31
+ * The watcher rebuilds its subscription set after every successful relink:
32
+ * the resolved plugin set drives which directories are watched, and that
33
+ * set itself can change when the user edits `zenbu.config.ts` to add or
34
+ * remove a plugin entry.
35
+ */
36
+ async function startLinkWatcher(projectDir, opts = {}) {
37
+ let subscriptions = [];
38
+ let closed = false;
39
+ let relinkScheduled = false;
40
+ let relinkInFlight = false;
41
+ let relinkPending = false;
42
+ let lastWatchKey = null;
43
+ let initial = null;
44
+ try {
45
+ initial = await linkProject(projectDir, {
46
+ registryOverride: opts.registryOverride ?? null,
47
+ quiet: true
48
+ });
49
+ if (opts.verbose) console.error(`[zen dev] initial link ok`);
50
+ } catch (err) {
51
+ console.error(`[zen dev] initial link failed (will retry on file change): ${err instanceof Error ? err.message : err}`);
52
+ }
53
+ const compileFromResult = (result) => {
54
+ if (!result) return [{
55
+ rootDir: projectDir,
56
+ exactPaths: new Set([
57
+ "zenbu.config.ts",
58
+ "zenbu.config.mts",
59
+ "zenbu.config.js",
60
+ "zenbu.config.mjs"
61
+ ].map((n) => path.join(projectDir, n))),
62
+ globMatchers: []
63
+ }];
64
+ return compileTargets(result);
65
+ };
66
+ const subscribeAll = async (targets) => {
67
+ await teardownAll();
68
+ if (closed) return;
69
+ for (const target of targets) try {
70
+ const sub = await subscribe(target.rootDir, (err, events) => {
71
+ if (err) return;
72
+ if (!eventsAreRelevant(events, target)) return;
73
+ scheduleRelink();
74
+ }, { ignore: COMMON_IGNORES });
75
+ if (closed) {
76
+ await sub.unsubscribe().catch(() => {});
77
+ return;
78
+ }
79
+ subscriptions.push(sub);
80
+ } catch (err) {
81
+ if (opts.verbose) console.error(`[zen dev] watcher subscribe failed for ${target.rootDir}: ${err instanceof Error ? err.message : err}`);
82
+ }
83
+ };
84
+ const teardownAll = async () => {
85
+ const subs = subscriptions;
86
+ subscriptions = [];
87
+ await Promise.all(subs.map((s) => s.unsubscribe().catch(() => {})));
88
+ };
89
+ const reconcileSubscriptions = async (result) => {
90
+ const targets = compileFromResult(result);
91
+ const key = watchKey(targets);
92
+ if (key === lastWatchKey) return;
93
+ lastWatchKey = key;
94
+ await subscribeAll(targets);
95
+ };
96
+ await reconcileSubscriptions(initial);
97
+ function scheduleRelink() {
98
+ if (closed) return;
99
+ if (relinkInFlight) {
100
+ relinkPending = true;
101
+ return;
102
+ }
103
+ if (relinkScheduled) return;
104
+ relinkScheduled = true;
105
+ setTimeout(() => {
106
+ relinkScheduled = false;
107
+ runRelink();
108
+ }, RELINK_DEBOUNCE_MS);
109
+ }
110
+ async function runRelink() {
111
+ if (closed) return;
112
+ relinkInFlight = true;
113
+ try {
114
+ const result = await linkProject(projectDir, {
115
+ registryOverride: opts.registryOverride ?? null,
116
+ quiet: true
117
+ });
118
+ if (opts.verbose) console.error(`[zen dev] relinked`);
119
+ await reconcileSubscriptions(result);
120
+ } catch {} finally {
121
+ relinkInFlight = false;
122
+ if (relinkPending && !closed) {
123
+ relinkPending = false;
124
+ scheduleRelink();
125
+ }
126
+ }
127
+ }
128
+ return { async close() {
129
+ closed = true;
130
+ await teardownAll();
131
+ } };
132
+ }
133
+ /**
134
+ * Build the set of (directory, filter) pairs we need watchers for. The
135
+ * filter side matters: we subscribe recursively to a dir but only treat
136
+ * matching events as link-relevant.
137
+ *
138
+ * The set covers everything `linkProject` reads:
139
+ * - the `zenbu.config.ts` itself,
140
+ * - any `zenbu.plugin.ts` files imported from the config,
141
+ * - per-plugin schema/preload/events/migrations files,
142
+ * - per-plugin service glob expansions.
143
+ */
144
+ function compileTargets(result) {
145
+ const exactPaths = new Set([result.resolvedConfigPath]);
146
+ for (const f of result.pluginSourceFiles) exactPaths.add(f);
147
+ const globMatchers = [];
148
+ const candidateDirs = /* @__PURE__ */ new Set();
149
+ candidateDirs.add(path.dirname(result.resolvedConfigPath));
150
+ for (const f of result.pluginSourceFiles) candidateDirs.add(path.dirname(f));
151
+ for (const plugin of result.resolved.plugins) addPluginPaths(plugin, exactPaths, globMatchers, candidateDirs);
152
+ return minimalCover([...candidateDirs]).map((rootDir) => ({
153
+ rootDir,
154
+ exactPaths,
155
+ globMatchers
156
+ }));
157
+ }
158
+ function addPluginPaths(plugin, exactPaths, globMatchers, candidateDirs) {
159
+ candidateDirs.add(plugin.dir);
160
+ for (const p of [
161
+ plugin.schemaPath,
162
+ plugin.preloadPath,
163
+ plugin.eventsPath,
164
+ plugin.migrationsPath
165
+ ]) if (p) {
166
+ exactPaths.add(p);
167
+ candidateDirs.add(path.dirname(p));
168
+ }
169
+ for (const serviceGlobAbs of plugin.services) {
170
+ if (!serviceGlobAbs.includes("*")) {
171
+ exactPaths.add(serviceGlobAbs);
172
+ candidateDirs.add(path.dirname(serviceGlobAbs));
173
+ continue;
174
+ }
175
+ const dir = path.dirname(serviceGlobAbs);
176
+ const base = path.basename(serviceGlobAbs);
177
+ const re = new RegExp("^" + base.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$");
178
+ globMatchers.push({
179
+ dir,
180
+ re
181
+ });
182
+ candidateDirs.add(dir);
183
+ }
184
+ }
185
+ function eventsAreRelevant(events, target) {
186
+ for (const event of events) {
187
+ const abs = event.path;
188
+ if (target.exactPaths.has(abs)) return true;
189
+ for (const { dir, re } of target.globMatchers) if (path.dirname(abs) === dir && re.test(path.basename(abs))) return true;
190
+ }
191
+ return false;
192
+ }
193
+ function minimalCover(dirs) {
194
+ const unique = [...new Set(dirs.map((d) => path.resolve(d)))];
195
+ unique.sort((a, b) => a.length - b.length);
196
+ const kept = [];
197
+ for (const d of unique) if (!kept.some((k) => d === k || d.startsWith(k + path.sep))) kept.push(d);
198
+ return kept;
199
+ }
200
+ function watchKey(targets) {
201
+ const parts = [];
202
+ for (const t of targets) {
203
+ const exacts = [...t.exactPaths].sort().join("|");
204
+ const globs = t.globMatchers.map((m) => `${m.dir}::${m.re.source}`).sort().join("|");
205
+ parts.push(`${t.rootDir}<<${exacts}>>${globs}`);
206
+ }
207
+ return parts.sort().join("\n");
208
+ }
209
+ //#endregion
210
+ //#region src/cli/commands/dev.ts
211
+ /**
212
+ * `zen dev` — launch the local app under Electron with the setup-gate as the
213
+ * main entry. This is the entrypoint of `pnpm dev` in scaffolded apps. The
214
+ * command is intentionally hookable: future versions can layer doctor checks,
215
+ * environment validation, or a managed dev-server here without touching the
216
+ * user's `package.json` scripts.
217
+ *
218
+ * Defaults to a foreground/blocking child so Ctrl+C in the terminal kills the
219
+ * Electron process and `pnpm dev` exits cleanly. Pass `--detach` to spawn it
220
+ * in the background instead (returns immediately, Electron lives until quit).
221
+ */
222
+ function parseArgs(argv) {
223
+ let pathArg;
224
+ let detach = false;
225
+ let verbose = false;
226
+ let watch = true;
227
+ for (let i = 0; i < argv.length; i++) {
228
+ const arg = argv[i];
229
+ if (arg === "--detach") detach = true;
230
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
231
+ else if (arg === "--no-watch") watch = false;
232
+ else if (!arg.startsWith("-") && pathArg == null) pathArg = arg;
233
+ else {
234
+ console.error(`zen dev: unknown flag "${arg}"`);
235
+ console.error(`valid: zen dev [path] [--detach] [--verbose] [--no-watch]`);
236
+ process.exit(1);
237
+ }
238
+ }
239
+ const projectDir = pathArg ? resolve(process.cwd(), pathArg) : process.cwd();
240
+ if (!existsSync(projectDir)) {
241
+ console.error(`zen dev: path "${pathArg}" does not exist`);
242
+ process.exit(1);
243
+ }
244
+ try {
245
+ if (!statSync(projectDir).isDirectory()) {
246
+ console.error(`zen dev: path "${pathArg}" is not a directory`);
247
+ process.exit(1);
248
+ }
249
+ } catch {
250
+ console.error(`zen dev: cannot stat "${pathArg}"`);
251
+ process.exit(1);
252
+ }
253
+ return {
254
+ projectDir,
255
+ detach,
256
+ verbose,
257
+ watch
258
+ };
259
+ }
260
+ function resolveLocalElectron(projectDir) {
261
+ const candidates = [path.join(projectDir, "node_modules", "electron", "dist", "Electron.app", "Contents", "MacOS", "Electron"), path.join(projectDir, "node_modules", ".bin", "electron")];
262
+ for (const candidate of candidates) if (existsSync(candidate)) return candidate;
263
+ throw new Error(`Electron is not installed in ${projectDir}. Run \`pnpm install\` in the app.`);
264
+ }
265
+ function ensureSetupGate(projectDir) {
266
+ const setupGate = path.join(projectDir, "node_modules", "@zenbujs", "core", "dist", "setup-gate.mjs");
267
+ if (!existsSync(setupGate)) throw new Error(`@zenbujs/core setup-gate not found at ${setupGate}. Run \`pnpm install\` in the app.`);
268
+ }
269
+ async function runDev(argv) {
270
+ const { projectDir, detach, verbose, watch } = parseArgs(argv);
271
+ if (verbose) console.error("[zen dev] launching:", projectDir);
272
+ const electron = resolveLocalElectron(projectDir);
273
+ ensureSetupGate(projectDir);
274
+ let watcher = null;
275
+ if (watch && !detach) try {
276
+ watcher = await startLinkWatcher(projectDir, { verbose });
277
+ } catch (err) {
278
+ console.error(`[zen dev] link watcher disabled: ${err instanceof Error ? err.message : err}`);
279
+ }
280
+ const electronArgs = [projectDir, `--project=${projectDir}`];
281
+ if (detach) {
282
+ spawn(electron, electronArgs, {
283
+ cwd: projectDir,
284
+ detached: true,
285
+ stdio: "ignore"
286
+ }).unref();
287
+ return;
288
+ }
289
+ const child = spawn(electron, electronArgs, {
290
+ cwd: projectDir,
291
+ stdio: "inherit"
292
+ });
293
+ process.on("SIGINT", () => child.kill("SIGINT"));
294
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
295
+ child.on("exit", async (code, signal) => {
296
+ if (watcher) await watcher.close().catch(() => {});
297
+ process.exit(code ?? (signal ? 1 : 0));
298
+ });
299
+ }
300
+ //#endregion
301
+ export { runDev };
@@ -1,2 +1,2 @@
1
- import { t as bootstrapEnv } from "./env-bootstrap-DW2hVhSO.mjs";
1
+ import { t as bootstrapEnv } from "./env-bootstrap-rTs8KR3-.mjs";
2
2
  export { bootstrapEnv };
@@ -0,0 +1,19 @@
1
+ //#region src/events.d.ts
2
+ /**
3
+ * Core's own events file. Mirrors the contract every plugin follows
4
+ * (`events: "./path/to/events.ts"` in zenbu.config / zenbu.plugin),
5
+ * exporting a single `Events` type that the link generator scans into
6
+ * the registry's `CoreEvents`.
7
+ *
8
+ * Each top-level key is a namespace; `rpc.emit.<namespace>.<event>(payload)`
9
+ * inside a service emits with the matching payload type.
10
+ */
11
+ type Events = {
12
+ advice: {
13
+ reload: {
14
+ type: string;
15
+ };
16
+ };
17
+ };
18
+ //#endregion
19
+ export { Events };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ //#region src/shared/host-version.ts
4
+ /**
5
+ * Single source of truth for reading the .app's "host version" — the
6
+ * concrete semver string the developer authored on
7
+ * `defineBuildConfig({ hostVersion: "..." })` and that
8
+ * `zen build:electron` baked into `<bundle>/host.json`.
9
+ *
10
+ * Imported by both:
11
+ * - `packages/core/src/launcher.ts` (tsdown inlines this into
12
+ * `dist/launcher.mjs`; the launcher cannot `import "@zenbujs/core"`)
13
+ * - `packages/core/src/services/updater.ts` (resolved through normal
14
+ * `@zenbujs/core/...` resolution at runtime)
15
+ *
16
+ * The file lives at the bundle root (`<APP_PATH>/host.json`, where
17
+ * `APP_PATH = app.getAppPath()`), separate from `app-config.json` so
18
+ * its single purpose stays obvious.
19
+ */
20
+ const HOST_VERSION_FILENAME = "host.json";
21
+ /**
22
+ * Read `<appPath>/host.json` and return its `version` field. Throws an
23
+ * informative error if the file is missing, unreadable, malformed, or
24
+ * the `version` field is missing/empty. Callers in dev mode should
25
+ * check for the file's existence first (or use `tryReadHostVersion`).
26
+ */
27
+ function readHostVersion(appPath) {
28
+ const filePath = path.join(appPath, HOST_VERSION_FILENAME);
29
+ let raw;
30
+ try {
31
+ raw = fs.readFileSync(filePath, "utf8");
32
+ } catch (err) {
33
+ const e = err;
34
+ if (e.code === "ENOENT") throw new Error(`[host-version] missing ${filePath}. Was this .app built with a recent \`zen build:electron\`? The build step is responsible for writing host.json.`);
35
+ throw new Error(`[host-version] failed to read ${filePath}: ${e.message ?? String(e)}`);
36
+ }
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(raw);
40
+ } catch (err) {
41
+ throw new Error(`[host-version] ${filePath} is not valid JSON: ${err.message}`);
42
+ }
43
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.version !== "string" || parsed.version.trim().length === 0) throw new Error(`[host-version] ${filePath} is missing a non-empty \`version\` string.`);
44
+ return {
45
+ version: parsed.version.trim(),
46
+ path: filePath
47
+ };
48
+ }
49
+ /** Non-throwing variant. Returns `null` when the file is absent or invalid. */
50
+ function tryReadHostVersion(appPath) {
51
+ try {
52
+ return readHostVersion(appPath);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ /** Inverse of `readHostVersion` — used by `zen build:electron`. */
58
+ function writeHostVersion(appPath, version) {
59
+ const filePath = path.join(appPath, HOST_VERSION_FILENAME);
60
+ const body = { version };
61
+ fs.writeFileSync(filePath, JSON.stringify(body, null, 2) + "\n");
62
+ return filePath;
63
+ }
64
+ //#endregion
65
+ export { tryReadHostVersion as n, writeHostVersion as r, HOST_VERSION_FILENAME as t };