@drift-beacon/plugin 0.1.0
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 +21 -0
- package/README.md +368 -0
- package/bin/dbplugin.js +11 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +38 -0
- package/dist/dev.d.ts +38 -0
- package/dist/dev.js +8 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +9 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +8 -0
- package/dist/internal/actions.d.ts +35 -0
- package/dist/internal/actions.js +1 -0
- package/dist/internal/collection.d.ts +15 -0
- package/dist/internal/collection.js +52 -0
- package/dist/internal/compatibility.d.ts +26 -0
- package/dist/internal/compatibility.js +52 -0
- package/dist/internal/json-equal.d.ts +3 -0
- package/dist/internal/json-equal.js +20 -0
- package/dist/internal/manifest.d.ts +51 -0
- package/dist/internal/manifest.js +73 -0
- package/dist/internal/models.d.ts +62 -0
- package/dist/internal/models.js +387 -0
- package/dist/internal/package-layout.d.ts +21 -0
- package/dist/internal/package-layout.js +19 -0
- package/dist/internal/protocol.d.ts +96 -0
- package/dist/internal/protocol.js +8 -0
- package/dist/internal/rows.d.ts +41 -0
- package/dist/internal/rows.js +1 -0
- package/dist/internal.d.ts +17 -0
- package/dist/internal.js +13 -0
- package/dist/main.d.ts +92 -0
- package/dist/main.js +14 -0
- package/dist/pack.d.ts +24 -0
- package/dist/pack.js +131 -0
- package/dist/prepare.d.ts +28 -0
- package/dist/prepare.js +121 -0
- package/dist/types.d.ts +193 -0
- package/dist/types.js +10 -0
- package/dist/ui.d.ts +25 -0
- package/dist/ui.js +265 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.js +5 -0
- package/dist/vite.d.ts +8 -0
- package/dist/vite.js +370 -0
- package/package.json +73 -0
package/dist/vite.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* `driftBeacon()` (`@drift-beacon/plugin/vite`): the Vite plugin of a plugin package. The UI is the author's
|
|
11
|
+
* own Vite app rooted at `ui/`; this plugin builds main next to it, so `dist/` is exactly the release package.
|
|
12
|
+
*
|
|
13
|
+
* `vite build` builds main once the UI is written. `vite` (`pnpm dev`) serves the UI with HMR, watch-builds main
|
|
14
|
+
* into `dist/` and keeps the manifest, the icon and the config types in sync. It writes a marker (dev.ts) that
|
|
15
|
+
* the Drift Beacon server polls; the server then connects to Vite's HMR socket and sends lines to print here.
|
|
16
|
+
*
|
|
17
|
+
* Through `link:`, a bare `vite` import from here would resolve to Cartridge's copy, whose types never match
|
|
18
|
+
* another copy's. So vite is only a type here: at run time the plugin loads the project's own vite, and the
|
|
19
|
+
* public signature names no vite type.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
22
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { pathToFileURL } from "node:url";
|
|
26
|
+
import { stripVTControlCharacters, styleText } from "node:util";
|
|
27
|
+
import { DEV_MARKER } from "./dev.js";
|
|
28
|
+
import { PACKAGE_JSON } from "./internal/package-layout.js";
|
|
29
|
+
import { prepare } from "./prepare.js";
|
|
30
|
+
/** How long `pnpm dev` waits for a Drift Beacon server before saying how to connect one. */
|
|
31
|
+
const HINT_DELAY_MS = 5_000;
|
|
32
|
+
/**
|
|
33
|
+
* The Vite plugin for a Drift Beacon plugin package: `plugins: [driftBeacon()]`. It owns `root`, `base` and
|
|
34
|
+
* `build.outDir`. Typed as `{ name: string }` so it fits any Vite version's `plugins`.
|
|
35
|
+
*/
|
|
36
|
+
export function driftBeacon() {
|
|
37
|
+
let pkg = "";
|
|
38
|
+
let dist = "";
|
|
39
|
+
let manifest;
|
|
40
|
+
let manifestBytes;
|
|
41
|
+
const plugin = {
|
|
42
|
+
name: "drift-beacon",
|
|
43
|
+
config(config, env) {
|
|
44
|
+
if (env.isPreview)
|
|
45
|
+
return;
|
|
46
|
+
pkg = path.resolve(config.root ?? process.cwd());
|
|
47
|
+
// Read, not assumed: `dbplugin pack` redirects the whole build into a temp folder through `build.outDir`.
|
|
48
|
+
dist = config.build?.outDir ? path.resolve(pkg, config.build.outDir) : path.join(pkg, "dist");
|
|
49
|
+
for (const file of ["ui/index.html", "main/src/index.ts"]) {
|
|
50
|
+
if (!existsSync(path.join(pkg, file)))
|
|
51
|
+
throw new Error(`driftBeacon(): ${file} not found in ${pkg}`);
|
|
52
|
+
}
|
|
53
|
+
const hmr = config.server?.hmr;
|
|
54
|
+
if (typeof hmr === "object" && (hmr.port !== undefined || hmr.path !== undefined)) {
|
|
55
|
+
throw new Error("driftBeacon(): a custom server.hmr port or path is not supported");
|
|
56
|
+
}
|
|
57
|
+
({ manifest, bytes: manifestBytes } = prepare(pkg, { strict: true }));
|
|
58
|
+
// `dist/ui` is outside the root `ui/`, so Vite only empties it when asked to.
|
|
59
|
+
return { root: path.join(pkg, "ui"), base: "./", build: { outDir: path.join(dist, "ui"), emptyOutDir: true } };
|
|
60
|
+
},
|
|
61
|
+
// `vite` (`pnpm dev`). A Vite restart creates the new server before closing the old one: both share the state.
|
|
62
|
+
configureServer(server) {
|
|
63
|
+
serveDev(server, devStates.get(pkg) ?? startDev(pkg, dist, manifest, manifestBytes, server.config.logger));
|
|
64
|
+
},
|
|
65
|
+
// `vite build` (and `dbplugin pack`). Runs after the UI is written (and Vite has copied `ui/public/` into `dist/ui/`).
|
|
66
|
+
async writeBundle() {
|
|
67
|
+
const vite = await loadVite(pkg);
|
|
68
|
+
await vite.build(mainBuild(vite, pkg, { outDir: path.join(dist, "main"), minify: true, sourcemap: false, logLevel: "warn" }));
|
|
69
|
+
writeAtomic(path.join(dist, "manifest.json"), manifestBytes);
|
|
70
|
+
writeAtomic(path.join(dist, "package.json"), `${JSON.stringify(PACKAGE_JSON, null, 2)}\n`);
|
|
71
|
+
writeMarker(dist, { pluginId: manifest.id, session: null, uiUrl: null });
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
return plugin;
|
|
75
|
+
}
|
|
76
|
+
/** The project's own vite: through `link:`, a bare import would resolve to the SDK's copy. */
|
|
77
|
+
const loadVite = async (pkg) => import(__rewriteRelativeImportExtension(pathToFileURL(createRequire(path.join(pkg, "package.json")).resolve("vite")).href));
|
|
78
|
+
/** Main as one ESM file, with every dependency bundled except the SDK, which the plugin host supplies. */
|
|
79
|
+
function mainBuild(vite, pkg, options) {
|
|
80
|
+
// One file on both Vite 7 (Rollup) and Vite 8 (Rolldown, which deprecates `inlineDynamicImports`).
|
|
81
|
+
const single = "rolldownVersion" in vite ? { codeSplitting: false } : { inlineDynamicImports: true };
|
|
82
|
+
return {
|
|
83
|
+
configFile: false,
|
|
84
|
+
root: pkg,
|
|
85
|
+
// Vite copies publicDir in every build: a stray `<pkg>/public/` must not reach `dist/main/`.
|
|
86
|
+
publicDir: false,
|
|
87
|
+
logLevel: options.logLevel,
|
|
88
|
+
build: {
|
|
89
|
+
ssr: "main/src/index.ts",
|
|
90
|
+
outDir: options.outDir,
|
|
91
|
+
target: "node24",
|
|
92
|
+
emptyOutDir: true,
|
|
93
|
+
minify: options.minify,
|
|
94
|
+
sourcemap: options.sourcemap,
|
|
95
|
+
watch: options.watch ?? null,
|
|
96
|
+
rollupOptions: {
|
|
97
|
+
external: ["@drift-beacon/plugin"],
|
|
98
|
+
output: { format: "es", entryFileNames: "index.js", ...single },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
ssr: { noExternal: true },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// On globalThis, so the state survives whatever reloads this module; keyed by package root, so one process
|
|
105
|
+
// (a test file) can serve several roots.
|
|
106
|
+
const DEV_STATES = Symbol.for("drift-beacon.dev");
|
|
107
|
+
const shared = globalThis;
|
|
108
|
+
shared[DEV_STATES] ??= new Map();
|
|
109
|
+
const devStates = shared[DEV_STATES];
|
|
110
|
+
/** Once per root: seed `dist/` and start the main watch build. */
|
|
111
|
+
function startDev(pkg, dist, manifest, bytes, logger) {
|
|
112
|
+
writeAtomic(path.join(dist, "manifest.json"), bytes);
|
|
113
|
+
writeAtomic(path.join(dist, "package.json"), `${JSON.stringify(PACKAGE_JSON, null, 2)}\n`);
|
|
114
|
+
const state = {
|
|
115
|
+
pkg,
|
|
116
|
+
dist,
|
|
117
|
+
session: randomUUID(),
|
|
118
|
+
manifest,
|
|
119
|
+
manifestBytes: bytes,
|
|
120
|
+
watcher: Promise.resolve(undefined),
|
|
121
|
+
buildId: hashDist(dist),
|
|
122
|
+
uiUrl: null,
|
|
123
|
+
socket: null,
|
|
124
|
+
hint: undefined,
|
|
125
|
+
logger,
|
|
126
|
+
servers: 0,
|
|
127
|
+
};
|
|
128
|
+
devStates.set(pkg, state);
|
|
129
|
+
syncIcon(state, true);
|
|
130
|
+
state.watcher = watchMain(state).catch((error) => {
|
|
131
|
+
print(state.logger, { level: "error", text: `✗ Could not start the main build: ${shorten(state, error.message)}` });
|
|
132
|
+
return undefined;
|
|
133
|
+
});
|
|
134
|
+
return state;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Builds main into `.drift-beacon/main-next/` on every save, then moves the files into `dist/main/`: never a
|
|
138
|
+
* half-written bundle in `dist/`, and at the same depth, so the map's relative sources still resolve.
|
|
139
|
+
*/
|
|
140
|
+
async function watchMain(state) {
|
|
141
|
+
const vite = await loadVite(state.pkg);
|
|
142
|
+
const staging = path.join(state.pkg, ".drift-beacon/main-next");
|
|
143
|
+
const options = { outDir: staging, minify: false, sourcemap: true, watch: {}, logLevel: "silent" };
|
|
144
|
+
const watcher = (await vite.build(mainBuild(vite, state.pkg, options)));
|
|
145
|
+
// Vite closes each event's bundle itself.
|
|
146
|
+
watcher.on("event", (event) => {
|
|
147
|
+
if (event.code === "BUNDLE_END") {
|
|
148
|
+
try {
|
|
149
|
+
moveFiles(staging, path.join(state.dist, "main"));
|
|
150
|
+
const changed = writeDevMarker(state);
|
|
151
|
+
print(state.logger, {
|
|
152
|
+
level: "info",
|
|
153
|
+
text: `main built in ${event.duration} ms${changed ? "" : " · no changes"}`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
print(state.logger, { level: "error", text: `✗ Could not update dist/main: ${error.message}` });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (event.code === "ERROR") {
|
|
161
|
+
// dist/ is untouched, so the server keeps the last good build.
|
|
162
|
+
const reason = shorten(state, event.error.message).replaceAll(/\n\s*\n/g, "\n");
|
|
163
|
+
print(state.logger, {
|
|
164
|
+
level: "error",
|
|
165
|
+
text: `✗ main build failed: ${reason}\nThe runtime keeps running the last good build.`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return watcher;
|
|
170
|
+
}
|
|
171
|
+
/** Per dev server: the manifest and icon watchers, the marker, and the server's socket. */
|
|
172
|
+
function serveDev(server, state) {
|
|
173
|
+
state.servers += 1;
|
|
174
|
+
state.logger = server.config.logger;
|
|
175
|
+
const manifestFile = path.join(state.pkg, "manifest.json");
|
|
176
|
+
server.watcher.add(manifestFile);
|
|
177
|
+
// Vite already watches its root `ui/`, so the icon's changes arrive here too.
|
|
178
|
+
server.watcher.on("all", (event, file) => {
|
|
179
|
+
if (file === manifestFile && event !== "unlink")
|
|
180
|
+
updateManifest(state);
|
|
181
|
+
else if (state.manifest.icon && file === path.join(state.pkg, "ui/public", state.manifest.icon))
|
|
182
|
+
syncIcon(state, false);
|
|
183
|
+
});
|
|
184
|
+
server.httpServer?.on("listening", () => {
|
|
185
|
+
state.uiUrl = server.resolvedUrls?.local[0] ?? null;
|
|
186
|
+
// Even before main has built: the server can connect and serve the UI meanwhile.
|
|
187
|
+
writeDevMarker(state);
|
|
188
|
+
clearTimeout(state.hint);
|
|
189
|
+
state.hint = setTimeout(() => {
|
|
190
|
+
if (!state.socket)
|
|
191
|
+
print(state.logger, { level: "warn", text: noServerHint(state) });
|
|
192
|
+
}, HINT_DELAY_MS).unref();
|
|
193
|
+
});
|
|
194
|
+
// On the dev server's own close: its http server emits "close" only if it listened, and has none in middleware mode.
|
|
195
|
+
const close = server.close.bind(server);
|
|
196
|
+
let open = true;
|
|
197
|
+
server.close = () => {
|
|
198
|
+
if (open) {
|
|
199
|
+
open = false;
|
|
200
|
+
release(state);
|
|
201
|
+
}
|
|
202
|
+
return close();
|
|
203
|
+
};
|
|
204
|
+
// Vite custom events over the HMR socket. Never `server.ws.send`: it broadcasts, plugin iframes included.
|
|
205
|
+
server.ws.on("drift-beacon:hello", (data, client) => {
|
|
206
|
+
if (data?.session !== state.session)
|
|
207
|
+
return;
|
|
208
|
+
const welcome = { v: 1, session: state.session, pluginId: state.manifest.id };
|
|
209
|
+
client.send("drift-beacon:welcome", welcome); // this socket only
|
|
210
|
+
clearTimeout(state.hint);
|
|
211
|
+
const { socket } = client;
|
|
212
|
+
state.socket = socket;
|
|
213
|
+
socket.once("close", () => {
|
|
214
|
+
if (state.socket !== socket)
|
|
215
|
+
return;
|
|
216
|
+
state.socket = null;
|
|
217
|
+
print(state.logger, { level: "warn", text: "! Drift Beacon server disconnected; waiting for it…" });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
server.ws.on("drift-beacon:print", (data, client) => {
|
|
221
|
+
if (client.socket === state.socket)
|
|
222
|
+
print(state.logger, data);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/** One dev server closed: the last one for the root stops the main watch build and forgets the root. */
|
|
226
|
+
function release(state) {
|
|
227
|
+
if (--state.servers > 0)
|
|
228
|
+
return;
|
|
229
|
+
clearTimeout(state.hint);
|
|
230
|
+
if (devStates.get(state.pkg) === state)
|
|
231
|
+
devStates.delete(state.pkg);
|
|
232
|
+
void state.watcher.then((watcher) => watcher?.close());
|
|
233
|
+
}
|
|
234
|
+
/** A saved manifest: checked, copied, and its config types and icon refreshed. An invalid one keeps the old copy. */
|
|
235
|
+
function updateManifest(state) {
|
|
236
|
+
try {
|
|
237
|
+
const { manifest, bytes } = prepare(state.pkg, { strict: true });
|
|
238
|
+
// A missing icon was already reported under its name; say it again only for a new name.
|
|
239
|
+
const renamed = manifest.icon !== state.manifest.icon;
|
|
240
|
+
state.manifest = manifest;
|
|
241
|
+
state.manifestBytes = bytes;
|
|
242
|
+
syncIcon(state, renamed);
|
|
243
|
+
// Copies the manifest.
|
|
244
|
+
writeDevMarker(state);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
print(state.logger, {
|
|
248
|
+
level: "error",
|
|
249
|
+
text: `✗ ${shorten(state, error.message)}\nDrift Beacon keeps the last valid manifest.`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Copies the manifest icon from `ui/public/` into `dist/ui/` (Vite does it in a build), or removes a deleted one. */
|
|
254
|
+
function syncIcon(state, warnMissing) {
|
|
255
|
+
const { icon } = state.manifest;
|
|
256
|
+
if (!icon)
|
|
257
|
+
return;
|
|
258
|
+
const source = path.join(state.pkg, "ui/public", icon);
|
|
259
|
+
const target = path.join(state.dist, "ui", icon);
|
|
260
|
+
try {
|
|
261
|
+
if (existsSync(source)) {
|
|
262
|
+
writeAtomic(target, readFileSync(source));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
rmSync(target, { force: true });
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
print(state.logger, { level: "error", text: `✗ Could not copy the icon: ${error.message}` });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (warnMissing) {
|
|
272
|
+
print(state.logger, {
|
|
273
|
+
level: "warn",
|
|
274
|
+
text: `! manifest.json names icon ${icon}, but ui/public/${icon} is missing`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** Moves every staged file into `to`, maps first and `index.js` last, so the new index.js never meets an old map. */
|
|
279
|
+
function moveFiles(from, to) {
|
|
280
|
+
const rank = (file) => (file === "index.js" ? 2 : file.endsWith(".map") ? 0 : 1);
|
|
281
|
+
const files = readdirSync(from, { recursive: true, withFileTypes: true })
|
|
282
|
+
.filter((entry) => entry.isFile())
|
|
283
|
+
.map((entry) => path.relative(from, path.join(entry.parentPath, entry.name)))
|
|
284
|
+
.sort((a, b) => rank(a) - rank(b));
|
|
285
|
+
for (const file of files) {
|
|
286
|
+
mkdirSync(path.dirname(path.join(to, file)), { recursive: true });
|
|
287
|
+
renameSync(path.join(from, file), path.join(to, file));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Writes the dev marker for what is in `dist/` now; true when its build id changed. The manifest, `package.json`
|
|
292
|
+
* and a missing icon are written first, so a `dist/` deleted while `pnpm dev` runs is whole again.
|
|
293
|
+
*/
|
|
294
|
+
function writeDevMarker(state) {
|
|
295
|
+
writeAtomic(path.join(state.dist, "manifest.json"), state.manifestBytes);
|
|
296
|
+
writeAtomic(path.join(state.dist, "package.json"), `${JSON.stringify(PACKAGE_JSON, null, 2)}\n`);
|
|
297
|
+
const { icon } = state.manifest;
|
|
298
|
+
if (icon && !existsSync(path.join(state.dist, "ui", icon)))
|
|
299
|
+
syncIcon(state, false);
|
|
300
|
+
const buildId = writeMarker(state.dist, { pluginId: state.manifest.id, session: state.session, uiUrl: state.uiUrl });
|
|
301
|
+
const changed = buildId !== state.buildId;
|
|
302
|
+
state.buildId = buildId;
|
|
303
|
+
return changed;
|
|
304
|
+
}
|
|
305
|
+
const noServerHint = (state) => [
|
|
306
|
+
`! No Drift Beacon server has connected after ${HINT_DELAY_MS / 1000} s.`,
|
|
307
|
+
`The server connects when it finds dist/${DEV_MARKER} in a folder directly`,
|
|
308
|
+
`under its DEV_PLUGINS_PATH. Run it in development mode with DEV_PLUGINS_PATH=${path.dirname(state.pkg)}`,
|
|
309
|
+
].join("\n");
|
|
310
|
+
/** Plain text with package paths made relative, for messages that come from Vite or the file system. */
|
|
311
|
+
const shorten = (state, text) => stripVTControlCharacters(text).replaceAll(`${state.pkg}${path.sep}`, "").trim();
|
|
312
|
+
const TIME = new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "numeric", second: "numeric" });
|
|
313
|
+
const STYLES = {
|
|
314
|
+
info: [],
|
|
315
|
+
success: "green",
|
|
316
|
+
warn: "yellow",
|
|
317
|
+
error: "red",
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* Prints a line (from the server, or about the local build) through Vite's logger, timed like Vite's own lines
|
|
321
|
+
* but tagged `[beacon]` (Vite's `timestamp` option would tag it `[vite]`). The first line is coloured by level;
|
|
322
|
+
* the others are dimmed and indented under it.
|
|
323
|
+
*/
|
|
324
|
+
function print(logger, { level, text }) {
|
|
325
|
+
const time = TIME.format(new Date());
|
|
326
|
+
const [first = "", ...rest] = String(text).split("\n");
|
|
327
|
+
const indent = " ".repeat(`${time} [beacon] `.length);
|
|
328
|
+
const message = [
|
|
329
|
+
`${styleText("dim", time)} ${styleText(["bold", "cyan"], "[beacon]")} ${styleText(STYLES[level] ?? [], first)}`,
|
|
330
|
+
...rest.map((line) => styleText("dim", indent + line)),
|
|
331
|
+
].join("\n");
|
|
332
|
+
if (level === "error")
|
|
333
|
+
logger.error(message);
|
|
334
|
+
else if (level === "warn")
|
|
335
|
+
logger.warn(message);
|
|
336
|
+
else
|
|
337
|
+
logger.info(message);
|
|
338
|
+
}
|
|
339
|
+
// ============================================================================
|
|
340
|
+
// Shared
|
|
341
|
+
// ============================================================================
|
|
342
|
+
/** Writes the marker for what is in `dist/` now and returns its build id. */
|
|
343
|
+
function writeMarker(dist, { pluginId, session, uiUrl }) {
|
|
344
|
+
const buildId = hashDist(dist);
|
|
345
|
+
// The HMR socket shares the UI's host and port; a relative base serves it at `/`.
|
|
346
|
+
const marker = { v: 1, pluginId, buildId, session, uiUrl, wsUrl: uiUrl?.replace(/^http/, "ws") ?? null };
|
|
347
|
+
writeAtomic(path.join(dist, DEV_MARKER), `${JSON.stringify(marker, null, 2)}\n`);
|
|
348
|
+
return buildId;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* The build id: sha256 of what the server loads, first 16 hex characters. Null until main is built. It
|
|
352
|
+
* covers `ui/index.html` so a UI-only build still refreshes an open static UI.
|
|
353
|
+
*/
|
|
354
|
+
function hashDist(dist) {
|
|
355
|
+
if (!existsSync(path.join(dist, "main/index.js")))
|
|
356
|
+
return null;
|
|
357
|
+
const hash = createHash("sha256");
|
|
358
|
+
for (const name of ["manifest.json", "main/index.js", "main/index.js.map", "ui/index.html"]) {
|
|
359
|
+
const file = path.join(dist, name);
|
|
360
|
+
if (existsSync(file))
|
|
361
|
+
hash.update(`${name}\0`).update(readFileSync(file));
|
|
362
|
+
}
|
|
363
|
+
return hash.digest("hex").slice(0, 16);
|
|
364
|
+
}
|
|
365
|
+
/** Writes through a temporary file and a rename, so the server never reads a partial file. */
|
|
366
|
+
function writeAtomic(file, content) {
|
|
367
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
368
|
+
writeFileSync(`${file}.tmp`, content);
|
|
369
|
+
renameSync(`${file}.tmp`, file);
|
|
370
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@drift-beacon/plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Drift Beacon plugin SDK: APIs for plugin main code and UIs, the driftBeacon() Vite plugin and the dbplugin CLI",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Drift Beacon",
|
|
8
|
+
"url": "https://driftbeacon.app"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://driftbeacon.app",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"drift-beacon",
|
|
13
|
+
"plugin",
|
|
14
|
+
"sdk",
|
|
15
|
+
"vite-plugin"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.13"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./ui": {
|
|
27
|
+
"types": "./dist/ui.d.ts",
|
|
28
|
+
"default": "./dist/ui.js"
|
|
29
|
+
},
|
|
30
|
+
"./vite": {
|
|
31
|
+
"types": "./dist/vite.d.ts",
|
|
32
|
+
"default": "./dist/vite.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"bin": {
|
|
37
|
+
"dbplugin": "bin/dbplugin.js"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"bin",
|
|
41
|
+
"dist",
|
|
42
|
+
"!dist/**/*.map"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"fflate": "^0.8.3"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"typescript": ">=5.9",
|
|
52
|
+
"vite": "^7.1.2 || ^8.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"typescript": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"vite": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "26.6.2",
|
|
64
|
+
"typescript": "6.0.3",
|
|
65
|
+
"vite": "7.1.2"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
69
|
+
"clean": "rm -rf dist",
|
|
70
|
+
"typecheck": "tsc --noEmit && node scripts/api-report.mjs --check",
|
|
71
|
+
"api:update": "node scripts/api-report.mjs"
|
|
72
|
+
}
|
|
73
|
+
}
|