@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.
- package/LICENSE +11 -0
- package/dist/advice-config-CjgkEf2E.mjs +135 -0
- package/dist/advice-config-Cy133IQP.mjs +2 -0
- package/dist/advice-runtime.d.mts +35 -0
- package/dist/advice-runtime.mjs +131 -0
- package/dist/advice.d.mts +36 -0
- package/dist/advice.mjs +2 -0
- package/dist/base-window-BUt8pwbw.mjs +94 -0
- package/dist/base-window-DEIAk618.mjs +2 -0
- package/dist/build-config-pbv0w4oN.mjs +17 -0
- package/dist/build-electron-B4Gd0Gi4.mjs +516 -0
- package/dist/build-source-_q1n1zTV.mjs +162 -0
- package/dist/chunk-Dm34NbLt.mjs +6 -0
- package/dist/cli/bin.d.mts +1 -0
- package/dist/cli/bin.mjs +88 -0
- package/dist/cli/build.d.mts +53 -0
- package/dist/cli/build.mjs +48 -0
- package/dist/cli-BLbQQIVB.mjs +8054 -0
- package/dist/config-CdVrW85P.mjs +59 -0
- package/dist/config-LK73dJmO.mjs +2 -0
- package/dist/db-ByKPbnP6.mjs +2 -0
- package/dist/db-DhuAJrye.mjs +531 -0
- package/dist/db.d.mts +16 -0
- package/dist/db.mjs +16 -0
- package/dist/dev-BuqklM0k.mjs +85 -0
- package/dist/env-bootstrap-BtVME-CU.d.mts +16 -0
- package/dist/env-bootstrap-rj7I-59x.mjs +53 -0
- package/dist/env-bootstrap.d.mts +2 -0
- package/dist/env-bootstrap.mjs +2 -0
- package/dist/http-IBcLzbYu.mjs +2 -0
- package/dist/index-Bhlbyrn7.d.mts +63 -0
- package/dist/index-CPZ5d6Hl.d.mts +442 -0
- package/dist/index-FtE8MXJ_.d.mts +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +5 -0
- package/dist/launcher.mjs +173 -0
- package/dist/link-6roQ7Cn6.mjs +580 -0
- package/dist/loaders/zenbu.d.mts +22 -0
- package/dist/loaders/zenbu.mjs +267 -0
- package/dist/log-CyKv8hQg.mjs +20 -0
- package/dist/mirror-sync-CodOnwkD.mjs +332 -0
- package/dist/monorepo-CmGPHsVm.mjs +119 -0
- package/dist/node-D4M19_mV.mjs +5 -0
- package/dist/node-loader.d.mts +17 -0
- package/dist/node-loader.mjs +33 -0
- package/dist/pause-DvAUNmKn.mjs +52 -0
- package/dist/publish-source-BVgB62Zj.mjs +131 -0
- package/dist/react.d.mts +76 -0
- package/dist/react.mjs +291 -0
- package/dist/registry-Dh_e7HU1.d.mts +61 -0
- package/dist/registry.d.mts +2 -0
- package/dist/registry.mjs +1 -0
- package/dist/reloader-BCkLjDhS.mjs +2 -0
- package/dist/reloader-lLAJ3lqg.mjs +164 -0
- package/dist/renderer-host-Bg8QdeeH.mjs +1508 -0
- package/dist/renderer-host-DpvBPTHJ.mjs +2 -0
- package/dist/rpc-BwwQK6hD.mjs +71 -0
- package/dist/rpc-CqitnyR4.mjs +2 -0
- package/dist/rpc.d.mts +2 -0
- package/dist/rpc.mjs +2 -0
- package/dist/runtime-CjqDr8Yf.d.mts +109 -0
- package/dist/runtime-DUFKDIe4.mjs +409 -0
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +2 -0
- package/dist/schema-CIg4GzHQ.mjs +100 -0
- package/dist/schema-DMoSkwUx.d.mts +62 -0
- package/dist/schema-dGK6qkfR.mjs +28 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +2 -0
- package/dist/server-BXwZEQ-n.mjs +66 -0
- package/dist/server-DjrZUbbu.mjs +2 -0
- package/dist/services/default.d.mts +11 -0
- package/dist/services/default.mjs +22 -0
- package/dist/services/index.d.mts +276 -0
- package/dist/services/index.mjs +7 -0
- package/dist/setup-gate-BeD6WS6d.mjs +110 -0
- package/dist/setup-gate-BqOzm7zp.d.mts +4 -0
- package/dist/setup-gate.d.mts +2 -0
- package/dist/setup-gate.mjs +2 -0
- package/dist/src-pELM4_iH.mjs +376 -0
- package/dist/trace-DCB7qFzT.mjs +10 -0
- package/dist/transform-DJH3vN4b.mjs +84041 -0
- package/dist/transport-BMSzG2-F.mjs +1045 -0
- package/dist/view-registry-BualWgAf.mjs +2 -0
- package/dist/vite-plugins-Bh3SCOw-.mjs +331 -0
- package/dist/vite.d.mts +68 -0
- package/dist/vite.mjs +2 -0
- package/dist/window-CM2a9Kyc.mjs +2 -0
- package/dist/window-CmmpCVX6.mjs +156 -0
- package/dist/write-9dRFczGJ.mjs +1248 -0
- package/migrations/0000_migration.ts +34 -0
- package/migrations/meta/0000_snapshot.json +18 -0
- package/migrations/meta/_journal.json +10 -0
- 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 };
|