@zenbujs/core 0.0.1

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 (94) hide show
  1. package/LICENSE +11 -0
  2. package/dist/advice-config-CjgkEf2E.mjs +135 -0
  3. package/dist/advice-config-Cy133IQP.mjs +2 -0
  4. package/dist/advice-runtime.d.mts +35 -0
  5. package/dist/advice-runtime.mjs +131 -0
  6. package/dist/advice.d.mts +36 -0
  7. package/dist/advice.mjs +2 -0
  8. package/dist/base-window-BUt8pwbw.mjs +94 -0
  9. package/dist/base-window-DEIAk618.mjs +2 -0
  10. package/dist/build-config-pbv0w4oN.mjs +17 -0
  11. package/dist/build-electron-B4Gd0Gi4.mjs +516 -0
  12. package/dist/build-source-_q1n1zTV.mjs +162 -0
  13. package/dist/chunk-Dm34NbLt.mjs +6 -0
  14. package/dist/cli/bin.d.mts +1 -0
  15. package/dist/cli/bin.mjs +88 -0
  16. package/dist/cli/build.d.mts +53 -0
  17. package/dist/cli/build.mjs +48 -0
  18. package/dist/cli-BLbQQIVB.mjs +8054 -0
  19. package/dist/config-CdVrW85P.mjs +59 -0
  20. package/dist/config-LK73dJmO.mjs +2 -0
  21. package/dist/db-ByKPbnP6.mjs +2 -0
  22. package/dist/db-DhuAJrye.mjs +531 -0
  23. package/dist/db.d.mts +16 -0
  24. package/dist/db.mjs +16 -0
  25. package/dist/dev-BuqklM0k.mjs +85 -0
  26. package/dist/env-bootstrap-BtVME-CU.d.mts +16 -0
  27. package/dist/env-bootstrap-rj7I-59x.mjs +53 -0
  28. package/dist/env-bootstrap.d.mts +2 -0
  29. package/dist/env-bootstrap.mjs +2 -0
  30. package/dist/http-IBcLzbYu.mjs +2 -0
  31. package/dist/index-Bhlbyrn7.d.mts +63 -0
  32. package/dist/index-CPZ5d6Hl.d.mts +442 -0
  33. package/dist/index-FtE8MXJ_.d.mts +1 -0
  34. package/dist/index.d.mts +6 -0
  35. package/dist/index.mjs +5 -0
  36. package/dist/launcher.mjs +173 -0
  37. package/dist/link-6roQ7Cn6.mjs +580 -0
  38. package/dist/loaders/zenbu.d.mts +22 -0
  39. package/dist/loaders/zenbu.mjs +267 -0
  40. package/dist/log-CyKv8hQg.mjs +20 -0
  41. package/dist/mirror-sync-CodOnwkD.mjs +332 -0
  42. package/dist/monorepo-CmGPHsVm.mjs +119 -0
  43. package/dist/node-D4M19_mV.mjs +5 -0
  44. package/dist/node-loader.d.mts +17 -0
  45. package/dist/node-loader.mjs +33 -0
  46. package/dist/pause-DvAUNmKn.mjs +52 -0
  47. package/dist/publish-source-BVgB62Zj.mjs +131 -0
  48. package/dist/react.d.mts +76 -0
  49. package/dist/react.mjs +291 -0
  50. package/dist/registry-Dh_e7HU1.d.mts +61 -0
  51. package/dist/registry.d.mts +2 -0
  52. package/dist/registry.mjs +1 -0
  53. package/dist/reloader-BCkLjDhS.mjs +2 -0
  54. package/dist/reloader-lLAJ3lqg.mjs +164 -0
  55. package/dist/renderer-host-Bg8QdeeH.mjs +1508 -0
  56. package/dist/renderer-host-DpvBPTHJ.mjs +2 -0
  57. package/dist/rpc-BwwQK6hD.mjs +71 -0
  58. package/dist/rpc-CqitnyR4.mjs +2 -0
  59. package/dist/rpc.d.mts +2 -0
  60. package/dist/rpc.mjs +2 -0
  61. package/dist/runtime-CjqDr8Yf.d.mts +109 -0
  62. package/dist/runtime-DUFKDIe4.mjs +409 -0
  63. package/dist/runtime.d.mts +2 -0
  64. package/dist/runtime.mjs +2 -0
  65. package/dist/schema-CIg4GzHQ.mjs +100 -0
  66. package/dist/schema-DMoSkwUx.d.mts +62 -0
  67. package/dist/schema-dGK6qkfR.mjs +28 -0
  68. package/dist/schema.d.mts +2 -0
  69. package/dist/schema.mjs +2 -0
  70. package/dist/server-BXwZEQ-n.mjs +66 -0
  71. package/dist/server-DjrZUbbu.mjs +2 -0
  72. package/dist/services/default.d.mts +11 -0
  73. package/dist/services/default.mjs +22 -0
  74. package/dist/services/index.d.mts +276 -0
  75. package/dist/services/index.mjs +7 -0
  76. package/dist/setup-gate-BeD6WS6d.mjs +110 -0
  77. package/dist/setup-gate-BqOzm7zp.d.mts +4 -0
  78. package/dist/setup-gate.d.mts +2 -0
  79. package/dist/setup-gate.mjs +2 -0
  80. package/dist/src-pELM4_iH.mjs +376 -0
  81. package/dist/trace-DCB7qFzT.mjs +10 -0
  82. package/dist/transform-DJH3vN4b.mjs +84041 -0
  83. package/dist/transport-BMSzG2-F.mjs +1045 -0
  84. package/dist/view-registry-BualWgAf.mjs +2 -0
  85. package/dist/vite-plugins-Bh3SCOw-.mjs +331 -0
  86. package/dist/vite.d.mts +68 -0
  87. package/dist/vite.mjs +2 -0
  88. package/dist/window-CM2a9Kyc.mjs +2 -0
  89. package/dist/window-CmmpCVX6.mjs +156 -0
  90. package/dist/write-9dRFczGJ.mjs +1248 -0
  91. package/migrations/0000_migration.ts +34 -0
  92. package/migrations/meta/0000_snapshot.json +18 -0
  93. package/migrations/meta/_journal.json +10 -0
  94. package/package.json +124 -0
@@ -0,0 +1,580 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ //#region src/cli/commands/link.ts
4
+ function findManifest(from) {
5
+ let dir = path.resolve(from);
6
+ while (true) {
7
+ const candidate = path.join(dir, "zenbu.plugin.json");
8
+ if (fs.existsSync(candidate)) return candidate;
9
+ const parent = path.dirname(dir);
10
+ if (parent === dir) return null;
11
+ dir = parent;
12
+ }
13
+ }
14
+ function findFileUp(from, name) {
15
+ let dir = path.resolve(from);
16
+ while (true) {
17
+ const candidate = path.join(dir, name);
18
+ if (fs.existsSync(candidate)) return candidate;
19
+ const parent = path.dirname(dir);
20
+ if (parent === dir) return null;
21
+ dir = parent;
22
+ }
23
+ }
24
+ /** jsonc-lite: strips // and /* * / comments and trailing commas. */
25
+ function readJsonLoose(filePath) {
26
+ const stripped = fs.readFileSync(filePath, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,\s*([\]}])/g, "$1");
27
+ return JSON.parse(stripped);
28
+ }
29
+ /**
30
+ * Extract the `#registry/*` mapping from a tsconfig (typically a
31
+ * gitignored `tsconfig.local.json`) and return the absolute directory it
32
+ * resolves to. Returns null when the alias isn't declared.
33
+ */
34
+ function readRegistryDirFromTsconfig(tsconfigPath) {
35
+ try {
36
+ const cfg = readJsonLoose(tsconfigPath);
37
+ const entry = cfg?.compilerOptions?.paths?.["#registry/*"];
38
+ if (!Array.isArray(entry) || typeof entry[0] !== "string") return null;
39
+ const trimmed = entry[0].replace(/\/\*$/, "");
40
+ const baseUrl = typeof cfg?.compilerOptions?.baseUrl === "string" ? path.resolve(path.dirname(tsconfigPath), cfg.compilerOptions.baseUrl) : path.dirname(tsconfigPath);
41
+ return path.resolve(baseUrl, trimmed);
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ /**
47
+ * An app root is the nearest ancestor (or self) that contains BOTH a
48
+ * `zenbu.plugin.json` and a `config.json` whose `plugins` is an array.
49
+ * That signature distinguishes the app from a bare plugin manifest.
50
+ */
51
+ function findAppRoot(from) {
52
+ let dir = path.resolve(from);
53
+ while (true) {
54
+ const manifest = path.join(dir, "zenbu.plugin.json");
55
+ const config = path.join(dir, "config.json");
56
+ if (fs.existsSync(manifest) && fs.existsSync(config)) try {
57
+ const parsed = JSON.parse(fs.readFileSync(config, "utf8"));
58
+ if (Array.isArray(parsed?.plugins)) return dir;
59
+ } catch {}
60
+ const parent = path.dirname(dir);
61
+ if (parent === dir) return null;
62
+ dir = parent;
63
+ }
64
+ }
65
+ /**
66
+ * Pick the directory `zen link` should write registry types into.
67
+ * Resolution order:
68
+ * 1. `--registry <dir>` flag / `ZENBU_REGISTRY_DIR` env (escape hatch)
69
+ * 2. Nearest `tsconfig.local.json` ancestor's `#registry/*` mapping
70
+ * (canonical — this is the same path TS uses to resolve the alias)
71
+ * 3. The manifest's `devAppPath` field (dev hint, fallback)
72
+ * 4. Walk up to the host app root and use `<app>/types`
73
+ * 5. Fail with a message that explains the available knobs
74
+ */
75
+ function resolveRegistryDir(opts) {
76
+ if (opts.registryOverride) return path.resolve(opts.registryOverride);
77
+ if (process.env.ZENBU_REGISTRY_DIR) return path.resolve(process.env.ZENBU_REGISTRY_DIR);
78
+ const manifestDir = path.dirname(opts.manifestPath);
79
+ const tsconfigLocal = findFileUp(manifestDir, "tsconfig.local.json");
80
+ if (tsconfigLocal) {
81
+ const fromTs = readRegistryDirFromTsconfig(tsconfigLocal);
82
+ if (fromTs) return fromTs;
83
+ }
84
+ if (opts.manifest.devAppPath) return path.resolve(manifestDir, opts.manifest.devAppPath, "types");
85
+ const appRoot = findAppRoot(manifestDir);
86
+ if (appRoot) return path.join(appRoot, "types");
87
+ console.error(`zen link: could not determine target types directory for ${opts.manifestPath}.`);
88
+ console.error(` Try one of:`);
89
+ console.error(` • run from inside an app dir (with both zenbu.plugin.json and config.json),`);
90
+ console.error(` • add a "devAppPath" field to ${path.basename(opts.manifestPath)} pointing at the host app,`);
91
+ console.error(` • create a tsconfig.local.json with a "#registry/*" path mapping,`);
92
+ console.error(` • or pass --registry <dir>.`);
93
+ process.exit(1);
94
+ }
95
+ function expandGlob(baseDir, pattern) {
96
+ if (!pattern.includes("*")) {
97
+ const full = path.resolve(baseDir, pattern);
98
+ return fs.existsSync(full) ? [full] : [];
99
+ }
100
+ const dir = path.resolve(baseDir, path.dirname(pattern));
101
+ const filePattern = path.basename(pattern);
102
+ const regex = new RegExp("^" + filePattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$");
103
+ try {
104
+ return fs.readdirSync(dir).filter((f) => regex.test(f)).map((f) => path.resolve(dir, f));
105
+ } catch {
106
+ return [];
107
+ }
108
+ }
109
+ function discoverServices(baseDir, serviceGlobs) {
110
+ const entries = [];
111
+ const classRe = /export\s+class\s+(\w+)\s+extends\s+(?:Service\b|serviceWithDeps\s*\()/;
112
+ const keyRe = /static\s+key\s*=\s*["']([^"']+)["']/;
113
+ for (const glob of serviceGlobs) for (const filePath of expandGlob(baseDir, glob)) {
114
+ const content = fs.readFileSync(filePath, "utf8");
115
+ const classMatch = content.match(classRe);
116
+ const keyMatch = content.match(keyRe);
117
+ if (classMatch && keyMatch) entries.push({
118
+ className: classMatch[1],
119
+ key: keyMatch[1],
120
+ filePath
121
+ });
122
+ }
123
+ return entries;
124
+ }
125
+ function relativeFromRegistry(registryDir, absPath) {
126
+ let rel = path.relative(registryDir, absPath);
127
+ if (!rel.startsWith(".")) rel = "./" + rel;
128
+ return rel.replace(/\.ts$/, "");
129
+ }
130
+ const SERVICE_BASE_LITERAL = [
131
+ ` | "evaluate"`,
132
+ ` | "shutdown"`,
133
+ ` | "constructor"`,
134
+ ` | "effect"`,
135
+ ` | "__cleanupAllEffects"`,
136
+ ` | "__effectCleanups"`,
137
+ ` | "ctx"`
138
+ ].join("\n");
139
+ function generateServicesFile(registryDir, allServices) {
140
+ const imports = [];
141
+ const routerEntries = [];
142
+ const usedNames = /* @__PURE__ */ new Map();
143
+ function uniqueName(base) {
144
+ const count = usedNames.get(base) ?? 0;
145
+ usedNames.set(base, count + 1);
146
+ return count === 0 ? base : `${base}_${count}`;
147
+ }
148
+ for (const [, services] of allServices) for (const svc of services) {
149
+ const alias = uniqueName(svc.className);
150
+ const importPath = relativeFromRegistry(registryDir, svc.filePath);
151
+ imports.push(`import type { ${svc.className}${alias !== svc.className ? ` as ${alias}` : ""} } from "${importPath}"`);
152
+ const quotedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(svc.key) ? svc.key : `"${svc.key}"`;
153
+ routerEntries.push(` ${quotedKey}: ExtractRpcMethods<${alias}>`);
154
+ }
155
+ return [
156
+ "// Generated by: zen link",
157
+ "",
158
+ `import type { CoreServiceRouter } from "@zenbujs/core/registry"`,
159
+ ...imports,
160
+ "",
161
+ `type ServiceBase =\n${SERVICE_BASE_LITERAL}`,
162
+ "",
163
+ "type ExtractRpcMethods<T> = {",
164
+ " [K in Exclude<keyof T, ServiceBase | `_${string}`> as T[K] extends (",
165
+ " ...args: any[]",
166
+ " ) => any",
167
+ " ? K",
168
+ " : never]: T[K]",
169
+ "}",
170
+ "",
171
+ "export type PluginServiceRouter = {",
172
+ ...routerEntries.map((e) => e + ";"),
173
+ "}",
174
+ "",
175
+ "export type ServiceRouter = CoreServiceRouter & PluginServiceRouter",
176
+ ""
177
+ ].join("\n");
178
+ }
179
+ function generatePreloadsFile(registryDir, allPreloads) {
180
+ const imports = [];
181
+ const entries = [];
182
+ const usedAliases = /* @__PURE__ */ new Map();
183
+ function uniqueAlias(base) {
184
+ const count = usedAliases.get(base) ?? 0;
185
+ usedAliases.set(base, count + 1);
186
+ return count === 0 ? base : `${base}${count}`;
187
+ }
188
+ for (const [, { name, preloadPath }] of allPreloads) {
189
+ const alias = uniqueAlias(`${name.replace(/(^|[-_])(\w)/g, (_m, _p, c) => _p ? c.toUpperCase() : c)}Preload`);
190
+ const importPath = relativeFromRegistry(registryDir, preloadPath);
191
+ imports.push(`import type { default as ${alias} } from "${importPath}"`);
192
+ const quotedName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
193
+ entries.push(` ${quotedName}: Awaited<ReturnType<typeof ${alias}>>`);
194
+ }
195
+ return [
196
+ "// Generated by: zen link",
197
+ "",
198
+ `import type { CorePreloads } from "@zenbujs/core/registry"`,
199
+ ...imports,
200
+ "",
201
+ "export type PluginPreloads = {",
202
+ ...entries.map((e) => e + ";"),
203
+ "}",
204
+ "",
205
+ "export type Preloads = CorePreloads & PluginPreloads",
206
+ ""
207
+ ].join("\n");
208
+ }
209
+ function generateEventsFile(registryDir, allEvents) {
210
+ const imports = [];
211
+ const aliases = [];
212
+ const usedAliases = /* @__PURE__ */ new Map();
213
+ function uniqueAlias(base) {
214
+ const count = usedAliases.get(base) ?? 0;
215
+ usedAliases.set(base, count + 1);
216
+ return count === 0 ? base : `${base}${count}`;
217
+ }
218
+ for (const [, { name, eventsPath }] of allEvents) {
219
+ const alias = uniqueAlias(`Events_${name.replace(/(^|[-_])(\w)/g, (_m, _p, c) => _p ? c.toUpperCase() : c)}`);
220
+ const importPath = relativeFromRegistry(registryDir, eventsPath);
221
+ imports.push(`import type { Events as ${alias} } from "${importPath}"`);
222
+ aliases.push(alias);
223
+ }
224
+ const body = aliases.length === 0 ? "Record<string, never>" : aliases.join(" & ");
225
+ return [
226
+ "// Generated by: zen link",
227
+ "",
228
+ `import type { CoreEvents } from "@zenbujs/core/registry"`,
229
+ ...imports,
230
+ "",
231
+ "/**",
232
+ " * Intersection of every plugin's `Events` type. Plugins extend this",
233
+ " * by declaring `events: \"./path/to/events.ts\"` in their",
234
+ " * zenbu.plugin.json and exporting `export type Events = { ... }` from",
235
+ " * that file. Each plugin chooses its own top-level namespace keys",
236
+ " * (e.g. `pty`, `bottomTerminal`); colliding keys with different payload",
237
+ " * shapes collapse to `never` at use sites — caught at compile time.",
238
+ " */",
239
+ `export type PluginEvents = CoreEvents & ${body}`,
240
+ ""
241
+ ].join("\n");
242
+ }
243
+ /**
244
+ * Generate the module-augmentation that wires `DbRoot`, `ServiceRouter`,
245
+ * and `PluginEvents` from the user's registry into `@zenbujs/core/react`'s
246
+ * `ZenbuRegister` interface. With this in place the renderer hooks are
247
+ * fully typed without a single generic at the call site.
248
+ */
249
+ function generateRegisterFile() {
250
+ return [
251
+ "// Generated by: zen link",
252
+ "",
253
+ `import type { DbRoot } from "./db-sections"`,
254
+ `import type { ServiceRouter } from "./services"`,
255
+ `import type { PluginEvents } from "./events"`,
256
+ "",
257
+ `declare module "@zenbujs/core/registry" {`,
258
+ " interface ZenbuRegister {",
259
+ " db: DbRoot",
260
+ " rpc: ServiceRouter",
261
+ " events: PluginEvents",
262
+ " }",
263
+ "}",
264
+ "",
265
+ "export {}",
266
+ ""
267
+ ].join("\n");
268
+ }
269
+ function generateDbSectionsFile(registryDir, allSchemas) {
270
+ const imports = [];
271
+ const sectionEntries = [];
272
+ const usedAliases = /* @__PURE__ */ new Map();
273
+ function uniqueAlias(base) {
274
+ const count = usedAliases.get(base) ?? 0;
275
+ usedAliases.set(base, count + 1);
276
+ return count === 0 ? base : `${base}${count}`;
277
+ }
278
+ for (const [, { name, schemaPath }] of allSchemas) {
279
+ const alias = uniqueAlias(name.replace(/(^|[-_])(\w)/g, (_m, _p, c) => c.toUpperCase()) + "Root");
280
+ const importPath = relativeFromRegistry(registryDir, schemaPath);
281
+ imports.push(`import type { SchemaRoot as ${alias} } from "${importPath}"`);
282
+ const quotedName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
283
+ sectionEntries.push(` ${quotedName}: ${alias}`);
284
+ }
285
+ return [
286
+ "// Generated by: zen link",
287
+ "",
288
+ `import type { CoreDbSections } from "@zenbujs/core/registry"`,
289
+ ...imports,
290
+ "",
291
+ "export type PluginDbSections = {",
292
+ ...sectionEntries.map((e) => e + ";"),
293
+ "}",
294
+ "",
295
+ "export type DbSections = CoreDbSections & PluginDbSections",
296
+ "",
297
+ "export type DbRoot = { plugin: DbSections }",
298
+ ""
299
+ ].join("\n");
300
+ }
301
+ function readExistingRegistry(registryDir) {
302
+ const services = /* @__PURE__ */ new Map();
303
+ const schemas = /* @__PURE__ */ new Map();
304
+ const preloads = /* @__PURE__ */ new Map();
305
+ const events = /* @__PURE__ */ new Map();
306
+ const servicesPath = path.join(registryDir, "services.ts");
307
+ if (fs.existsSync(servicesPath)) {
308
+ const content = fs.readFileSync(servicesPath, "utf8");
309
+ const importRe = /import type \{ (\w+)(?:\s+as\s+(\w+))? \} from "([^"]+)"/g;
310
+ const routerRe = /^\s+(?:"([^"]+)"|(\w+)):\s+ExtractRpcMethods<(\w+)>/gm;
311
+ const importMap = /* @__PURE__ */ new Map();
312
+ for (const m of content.matchAll(importRe)) {
313
+ const className = m[1];
314
+ const alias = m[2] ?? m[1];
315
+ const relPath = m[3];
316
+ const absPath = path.resolve(registryDir, relPath) + ".ts";
317
+ importMap.set(alias, {
318
+ className,
319
+ filePath: absPath
320
+ });
321
+ }
322
+ for (const m of content.matchAll(routerRe)) {
323
+ const key = m[1] ?? m[2];
324
+ const alias = m[3];
325
+ const imp = importMap.get(alias);
326
+ if (!imp) continue;
327
+ let pluginName = null;
328
+ let dir = path.dirname(imp.filePath);
329
+ while (dir !== path.dirname(dir)) {
330
+ const candidate = path.join(dir, "zenbu.plugin.json");
331
+ if (fs.existsSync(candidate)) {
332
+ try {
333
+ pluginName = JSON.parse(fs.readFileSync(candidate, "utf8")).name;
334
+ } catch {}
335
+ break;
336
+ }
337
+ dir = path.dirname(dir);
338
+ }
339
+ if (!pluginName) continue;
340
+ if (!services.has(pluginName)) services.set(pluginName, []);
341
+ services.get(pluginName).push({
342
+ className: imp.className,
343
+ key,
344
+ filePath: imp.filePath
345
+ });
346
+ }
347
+ }
348
+ const dbPath = path.join(registryDir, "db-sections.ts");
349
+ if (fs.existsSync(dbPath)) {
350
+ const content = fs.readFileSync(dbPath, "utf8");
351
+ const importRe = /import type \{ SchemaRoot as (\w+) \} from "([^"]+)"/g;
352
+ const sectionRe = /^\s+(?:"([^"]+)"|(\w+)):\s+(\w+)/gm;
353
+ const importMap = /* @__PURE__ */ new Map();
354
+ for (const m of content.matchAll(importRe)) {
355
+ const alias = m[1];
356
+ const relPath = m[2];
357
+ importMap.set(alias, path.resolve(registryDir, relPath) + ".ts");
358
+ }
359
+ for (const m of content.matchAll(sectionRe)) {
360
+ const name = m[1] ?? m[2];
361
+ const alias = m[3];
362
+ const schemaPath = importMap.get(alias);
363
+ if (schemaPath) schemas.set(name, {
364
+ name,
365
+ schemaPath
366
+ });
367
+ }
368
+ }
369
+ const preloadsPath = path.join(registryDir, "preloads.ts");
370
+ if (fs.existsSync(preloadsPath)) {
371
+ const content = fs.readFileSync(preloadsPath, "utf8");
372
+ const importRe = /import type \{ default as (\w+) \} from "([^"]+)"/g;
373
+ const entryRe = /^\s+(?:"([^"]+)"|(\w+)):\s+Awaited<ReturnType<typeof (\w+)>>/gm;
374
+ const importMap = /* @__PURE__ */ new Map();
375
+ for (const m of content.matchAll(importRe)) {
376
+ const alias = m[1];
377
+ const relPath = m[2];
378
+ importMap.set(alias, path.resolve(registryDir, relPath) + ".ts");
379
+ }
380
+ for (const m of content.matchAll(entryRe)) {
381
+ const name = m[1] ?? m[2];
382
+ const alias = m[3];
383
+ const preloadPath = importMap.get(alias);
384
+ if (preloadPath) preloads.set(name, {
385
+ name,
386
+ preloadPath
387
+ });
388
+ }
389
+ }
390
+ const eventsRegistryPath = path.join(registryDir, "events.ts");
391
+ if (fs.existsSync(eventsRegistryPath)) {
392
+ const content = fs.readFileSync(eventsRegistryPath, "utf8");
393
+ for (const m of content.matchAll(/import type \{ Events as (\w+) \} from "([^"]+)"/g)) {
394
+ m[1];
395
+ const relPath = m[2];
396
+ const eventsPath = path.resolve(registryDir, relPath) + ".ts";
397
+ let pluginName = null;
398
+ let dir = path.dirname(eventsPath);
399
+ while (dir !== path.dirname(dir)) {
400
+ const candidate = path.join(dir, "zenbu.plugin.json");
401
+ if (fs.existsSync(candidate)) {
402
+ try {
403
+ pluginName = JSON.parse(fs.readFileSync(candidate, "utf8")).name;
404
+ } catch {}
405
+ break;
406
+ }
407
+ dir = path.dirname(dir);
408
+ }
409
+ if (!pluginName) continue;
410
+ events.set(pluginName, {
411
+ name: pluginName,
412
+ eventsPath
413
+ });
414
+ }
415
+ }
416
+ return {
417
+ services,
418
+ schemas,
419
+ preloads,
420
+ events
421
+ };
422
+ }
423
+ function parseLinkArgs(argv) {
424
+ let manifestArg = null;
425
+ let typesConfigArg = null;
426
+ let registryOverride = null;
427
+ for (let i = 0; i < argv.length; i++) {
428
+ const arg = argv[i];
429
+ if (arg === "--registry" && i + 1 < argv.length) registryOverride = argv[++i];
430
+ else if (arg.startsWith("--registry=")) registryOverride = arg.slice(11);
431
+ else if (arg === "--types-config" && i + 1 < argv.length) typesConfigArg = argv[++i];
432
+ else if (arg.startsWith("--types-config=")) typesConfigArg = arg.slice(15);
433
+ else if (!arg.startsWith("-") && !manifestArg) manifestArg = arg;
434
+ }
435
+ return {
436
+ manifestArg,
437
+ typesConfigArg,
438
+ registryOverride
439
+ };
440
+ }
441
+ function linkOne(manifestPath, existing) {
442
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
443
+ const pluginName = manifest.name;
444
+ const baseDir = path.dirname(manifestPath);
445
+ console.log(`Linking types "${pluginName}" from ${baseDir}`);
446
+ const serviceEntries = discoverServices(baseDir, manifest.services ?? []);
447
+ console.log(` Found ${serviceEntries.length} service(s)`);
448
+ const schemaEntry = manifest.schema ? {
449
+ name: pluginName,
450
+ schemaPath: path.resolve(baseDir, manifest.schema)
451
+ } : null;
452
+ if (schemaEntry) console.log(` Schema: ${schemaEntry.schemaPath}`);
453
+ const preloadEntry = manifest.preload ? {
454
+ name: pluginName,
455
+ preloadPath: path.resolve(baseDir, manifest.preload)
456
+ } : null;
457
+ if (preloadEntry) console.log(` Preload: ${preloadEntry.preloadPath}`);
458
+ const eventsEntry = manifest.events ? {
459
+ name: pluginName,
460
+ eventsPath: path.resolve(baseDir, manifest.events)
461
+ } : null;
462
+ if (eventsEntry) console.log(` Events: ${eventsEntry.eventsPath}`);
463
+ existing.services.set(pluginName, serviceEntries);
464
+ if (schemaEntry) existing.schemas.set(pluginName, schemaEntry);
465
+ if (preloadEntry) existing.preloads.set(pluginName, preloadEntry);
466
+ else existing.preloads.delete(pluginName);
467
+ if (eventsEntry) existing.events.set(pluginName, eventsEntry);
468
+ else existing.events.delete(pluginName);
469
+ }
470
+ /**
471
+ * Per-install bootstrap for a plugin. The plugin ships a portable
472
+ * `tsconfig.json` with no host-specific paths; this writes a sibling
473
+ * `tsconfig.local.json` (gitignored) that wires `#registry/*` to the host
474
+ * app's `types/` so the plugin can `import type {...} from "#registry/foo"`
475
+ * the same way the host does. Idempotent.
476
+ *
477
+ * This is the only mechanism that knows where the host lives — it has to
478
+ * be regenerated whenever the plugin moves between hosts.
479
+ */
480
+ /**
481
+ * Generate `<plugin>/tsconfig.local.json` — the BASE config that the
482
+ * plugin's committed `tsconfig.json` extends. This file is gitignored and
483
+ * regenerated by every `zen link` so the host-specific bits (the
484
+ * `#registry/*` path mapping and the registry-augmentation include) move
485
+ * with the plugin's host installation.
486
+ *
487
+ * Why this and not `tsconfig.json` itself: the IDE (VSCode/Cursor) walks
488
+ * up looking for `tsconfig.json` and uses only that one. It never picks up
489
+ * a sibling `tsconfig.local.json` on its own. So we keep `tsconfig.json`
490
+ * as the IDE-discovered, plugin-author-edited entry point and have it
491
+ * `"extends": "./tsconfig.local.json"`. The generated file then carries
492
+ * `paths` + `include`, which TS merges into the IDE's resolved program.
493
+ */
494
+ function writePluginTsconfigLocal(pluginManifestPath, registryDir) {
495
+ const pluginDir = path.dirname(pluginManifestPath);
496
+ const ownTsconfig = path.join(pluginDir, "tsconfig.json");
497
+ if (!fs.existsSync(ownTsconfig)) return;
498
+ if (fs.existsSync(path.join(pluginDir, "config.json"))) return;
499
+ const target = path.join(pluginDir, "tsconfig.local.json");
500
+ let registryRel = path.relative(pluginDir, registryDir);
501
+ if (!registryRel.startsWith(".")) registryRel = "./" + registryRel;
502
+ const body = {
503
+ compilerOptions: { paths: { "#registry/*": [`${registryRel}/*`] } },
504
+ include: ["src", `${registryRel}/zenbu-register.ts`]
505
+ };
506
+ const next = JSON.stringify(body, null, 2) + "\n";
507
+ let prev = null;
508
+ try {
509
+ prev = fs.readFileSync(target, "utf8");
510
+ } catch {}
511
+ if (prev === next) return;
512
+ fs.writeFileSync(target, next);
513
+ console.log(` Wrote ${target}`);
514
+ }
515
+ /**
516
+ * When the manifest being linked is the host app's own manifest (sitting
517
+ * next to its `config.json`), expand the work list to also include every
518
+ * plugin path declared in `config.json#plugins`. For any other manifest
519
+ * (a plugin sitting inside or outside the app), only that single manifest
520
+ * is returned — the caller decides not to touch sibling plugins.
521
+ */
522
+ function expandAppManifests(manifestPath) {
523
+ const manifestDir = path.dirname(manifestPath);
524
+ const appRoot = findAppRoot(manifestDir);
525
+ if (appRoot !== manifestDir) return [manifestPath];
526
+ const configPath = path.join(appRoot, "config.json");
527
+ let pluginEntries = [];
528
+ try {
529
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
530
+ if (Array.isArray(config?.plugins)) pluginEntries = config.plugins;
531
+ } catch {}
532
+ const all = [manifestPath];
533
+ for (const entry of pluginEntries) {
534
+ const resolved = path.isAbsolute(entry) ? entry : path.resolve(appRoot, entry);
535
+ if (path.resolve(resolved) === path.resolve(manifestPath)) continue;
536
+ if (!fs.existsSync(resolved)) {
537
+ console.warn(` ⚠ skipping ${entry} (not found at ${resolved})`);
538
+ continue;
539
+ }
540
+ all.push(resolved);
541
+ }
542
+ return all;
543
+ }
544
+ async function runLink(argv) {
545
+ const { manifestArg, typesConfigArg, registryOverride } = parseLinkArgs(argv);
546
+ const typeConfigPath = typesConfigArg ? path.resolve(typesConfigArg) : null;
547
+ const manifestPath = typeConfigPath ? null : manifestArg ? path.resolve(manifestArg) : findManifest(process.cwd());
548
+ if (!typeConfigPath && !manifestPath) {
549
+ console.error("zen link: could not find zenbu.plugin.json in current directory or any parent.");
550
+ console.error(" For internal framework types, pass --types-config <path>.");
551
+ process.exit(1);
552
+ }
553
+ const rootConfigPath = typeConfigPath ?? manifestPath;
554
+ const registryDir = resolveRegistryDir({
555
+ manifestPath: rootConfigPath,
556
+ manifest: JSON.parse(fs.readFileSync(rootConfigPath, "utf8")),
557
+ registryOverride
558
+ });
559
+ console.log(`Registry: ${registryDir}`);
560
+ const manifestPaths = typeConfigPath ? [typeConfigPath] : expandAppManifests(manifestPath);
561
+ fs.mkdirSync(registryDir, { recursive: true });
562
+ const existing = readExistingRegistry(registryDir);
563
+ for (const mp of manifestPaths) linkOne(mp, existing);
564
+ const writes = [
565
+ ["services.ts", generateServicesFile(registryDir, existing.services)],
566
+ ["db-sections.ts", generateDbSectionsFile(registryDir, existing.schemas)],
567
+ ["preloads.ts", generatePreloadsFile(registryDir, existing.preloads)],
568
+ ["events.ts", generateEventsFile(registryDir, existing.events)],
569
+ ["zenbu-register.ts", generateRegisterFile()]
570
+ ];
571
+ for (const [name, body] of writes) {
572
+ const target = path.join(registryDir, name);
573
+ fs.writeFileSync(target, body);
574
+ console.log(` Wrote ${target}`);
575
+ }
576
+ for (const mp of manifestPaths) writePluginTsconfigLocal(mp, registryDir);
577
+ console.log("Done.");
578
+ }
579
+ //#endregion
580
+ export { runLink };
@@ -0,0 +1,22 @@
1
+ //#region src/loaders/zenbu.d.ts
2
+ type LoaderContext = {
3
+ hot?: {
4
+ watch?: (url: URL) => void;
5
+ invalidate?: () => void;
6
+ };
7
+ };
8
+ type NextResolve = (specifier: string, context: LoaderContext) => unknown;
9
+ type NextLoad = (url: string, context: LoaderContext) => unknown;
10
+ type TracePort = {
11
+ on: (event: "message", handler: (message: unknown) => void) => void;
12
+ postMessage: (message: unknown) => void;
13
+ unref?: () => void;
14
+ };
15
+ type InitializeData = {
16
+ tracePort?: TracePort;
17
+ };
18
+ declare function initialize(data?: InitializeData): void;
19
+ declare function resolve(specifier: string, context: LoaderContext, nextResolve: NextResolve): unknown;
20
+ declare function load(url: string, context: LoaderContext, nextLoad: NextLoad): unknown;
21
+ //#endregion
22
+ export { initialize, load, resolve };