Problem: the hexagonal refactor churns the backend tree for nine more phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper, compose/web image) must move to its own repo first so doc/layout rewrites land once on a backend-only tree. Change: - New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/ (updateURL repointed to oikos-web releases), compose/, own CI (web + desktop jobs), own deploy script (CI-green gate, TOCTOU guard, version-tagged images, prune-to-3), own webhook receiver on :9798 + launchd unit, own compose project publishing the same 8091:80. - Cutover executed on mac-mini in order: oikos stack's web service stopped+removed, oikos-web project brought up on 8091; outer Caddy untouched (targets the published port) — serving + Authentik flow + /wails 404 quirk verified post-cutover. - Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install targets, the compose web service, oikos-web from deploy.sh's fallback prune list; wails + go-keyring dropped from go.mod, vendor synced. - README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now point at the new repo; mbse + mascot design docs carry a path note. Risk: production SPA serving depends on the new pipeline now; rollback is versioned-image re-up of the old web service from a pre-split checkout (port 8091). Desktop builds installed before the split still check dtoro/oikos releases — one manual reinstall, noted in the oikos-web release notes. Verification: go vet, make test (race), make generate-check, golangci (no new findings; baseline down 400→365); post-cutover curls — localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
272 lines
7.9 KiB
JavaScript
272 lines
7.9 KiB
JavaScript
import { EventEmitter } from 'node:events';
|
|
import createDebug from 'debug';
|
|
import { f } from './chunk-browser.mjs';
|
|
import { normalizeRequestId } from './utils.mjs';
|
|
|
|
function createHmrEmitter() {
|
|
const emitter = new EventEmitter();
|
|
return emitter;
|
|
}
|
|
function viteNodeHmrPlugin() {
|
|
const emitter = createHmrEmitter();
|
|
return {
|
|
name: "vite-node:hmr",
|
|
config() {
|
|
if (process.platform === "darwin" && false) {
|
|
return {
|
|
server: {
|
|
watch: {
|
|
useFsEvents: false,
|
|
usePolling: false
|
|
}
|
|
}
|
|
};
|
|
}
|
|
},
|
|
configureServer(server) {
|
|
const _send = server.ws.send;
|
|
server.emitter = emitter;
|
|
server.ws.send = function(payload) {
|
|
_send(payload);
|
|
emitter.emit("message", payload);
|
|
};
|
|
const environments = server.environments;
|
|
if (environments) {
|
|
environments.ssr.hot.send = function(payload) {
|
|
_send(payload);
|
|
emitter.emit("message", payload);
|
|
};
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
const debugHmr = createDebug("vite-node:hmr");
|
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
function getCache(runner) {
|
|
if (!cache.has(runner)) {
|
|
cache.set(runner, {
|
|
hotModulesMap: /* @__PURE__ */ new Map(),
|
|
dataMap: /* @__PURE__ */ new Map(),
|
|
disposeMap: /* @__PURE__ */ new Map(),
|
|
pruneMap: /* @__PURE__ */ new Map(),
|
|
customListenersMap: /* @__PURE__ */ new Map(),
|
|
ctxToListenersMap: /* @__PURE__ */ new Map(),
|
|
messageBuffer: [],
|
|
isFirstUpdate: false,
|
|
pending: false,
|
|
queued: []
|
|
});
|
|
}
|
|
return cache.get(runner);
|
|
}
|
|
function sendMessageBuffer(runner, emitter) {
|
|
const maps = getCache(runner);
|
|
maps.messageBuffer.forEach((msg) => emitter.emit("custom", msg));
|
|
maps.messageBuffer.length = 0;
|
|
}
|
|
async function reload(runner, files) {
|
|
Array.from(runner.moduleCache.keys()).forEach((fsPath) => {
|
|
if (!fsPath.includes("node_modules")) {
|
|
runner.moduleCache.delete(fsPath);
|
|
}
|
|
});
|
|
return Promise.all(files.map((file) => runner.executeId(file)));
|
|
}
|
|
async function notifyListeners(runner, event, data) {
|
|
const maps = getCache(runner);
|
|
const cbs = maps.customListenersMap.get(event);
|
|
if (cbs) {
|
|
await Promise.all(cbs.map((cb) => cb(data)));
|
|
}
|
|
}
|
|
async function queueUpdate(runner, p) {
|
|
const maps = getCache(runner);
|
|
maps.queued.push(p);
|
|
if (!maps.pending) {
|
|
maps.pending = true;
|
|
await Promise.resolve();
|
|
maps.pending = false;
|
|
const loading = [...maps.queued];
|
|
maps.queued = [];
|
|
(await Promise.all(loading)).forEach((fn) => fn && fn());
|
|
}
|
|
}
|
|
async function fetchUpdate(runner, { path, acceptedPath }) {
|
|
path = normalizeRequestId(path);
|
|
acceptedPath = normalizeRequestId(acceptedPath);
|
|
const maps = getCache(runner);
|
|
const mod = maps.hotModulesMap.get(path);
|
|
if (!mod) {
|
|
return;
|
|
}
|
|
const isSelfUpdate = path === acceptedPath;
|
|
let fetchedModule;
|
|
const qualifiedCallbacks = mod.callbacks.filter(
|
|
({ deps }) => deps.includes(acceptedPath)
|
|
);
|
|
if (isSelfUpdate || qualifiedCallbacks.length > 0) {
|
|
const disposer = maps.disposeMap.get(acceptedPath);
|
|
if (disposer) {
|
|
await disposer(maps.dataMap.get(acceptedPath));
|
|
}
|
|
try {
|
|
[fetchedModule] = await reload(runner, [acceptedPath]);
|
|
} catch (e) {
|
|
warnFailedFetch(e, acceptedPath);
|
|
}
|
|
}
|
|
return () => {
|
|
for (const { deps, fn } of qualifiedCallbacks) {
|
|
fn(deps.map((dep) => dep === acceptedPath ? fetchedModule : void 0));
|
|
}
|
|
const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`;
|
|
console.log(`${f.cyan("[vite-node]")} hot updated: ${loggedPath}`);
|
|
};
|
|
}
|
|
function warnFailedFetch(err, path) {
|
|
if (!(err instanceof Error) || !err.message.match("fetch")) {
|
|
console.error(err);
|
|
}
|
|
console.error(
|
|
`[hmr] Failed to reload ${path}. This could be due to syntax errors or importing non-existent modules. (see errors above)`
|
|
);
|
|
}
|
|
async function handleMessage(runner, emitter, files, payload) {
|
|
const maps = getCache(runner);
|
|
switch (payload.type) {
|
|
case "connected":
|
|
sendMessageBuffer(runner, emitter);
|
|
break;
|
|
case "update":
|
|
await notifyListeners(runner, "vite:beforeUpdate", payload);
|
|
await Promise.all(
|
|
payload.updates.map((update) => {
|
|
if (update.type === "js-update") {
|
|
return queueUpdate(runner, fetchUpdate(runner, update));
|
|
}
|
|
console.error(`${f.cyan("[vite-node]")} no support css hmr.}`);
|
|
return null;
|
|
})
|
|
);
|
|
await notifyListeners(runner, "vite:afterUpdate", payload);
|
|
break;
|
|
case "full-reload":
|
|
await notifyListeners(runner, "vite:beforeFullReload", payload);
|
|
maps.customListenersMap.delete("vite:beforeFullReload");
|
|
await reload(runner, files);
|
|
break;
|
|
case "custom":
|
|
await notifyListeners(runner, payload.event, payload.data);
|
|
break;
|
|
case "prune":
|
|
await notifyListeners(runner, "vite:beforePrune", payload);
|
|
payload.paths.forEach((path) => {
|
|
const fn = maps.pruneMap.get(path);
|
|
if (fn) {
|
|
fn(maps.dataMap.get(path));
|
|
}
|
|
});
|
|
break;
|
|
case "error": {
|
|
await notifyListeners(runner, "vite:error", payload);
|
|
const err = payload.err;
|
|
console.error(
|
|
`${f.cyan("[vite-node]")} Internal Server Error
|
|
${err.message}
|
|
${err.stack}`
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
function createHotContext(runner, emitter, files, ownerPath) {
|
|
debugHmr("createHotContext", ownerPath);
|
|
const maps = getCache(runner);
|
|
if (!maps.dataMap.has(ownerPath)) {
|
|
maps.dataMap.set(ownerPath, {});
|
|
}
|
|
const mod = maps.hotModulesMap.get(ownerPath);
|
|
if (mod) {
|
|
mod.callbacks = [];
|
|
}
|
|
const newListeners = /* @__PURE__ */ new Map();
|
|
maps.ctxToListenersMap.set(ownerPath, newListeners);
|
|
function acceptDeps(deps, callback = () => {
|
|
}) {
|
|
const mod2 = maps.hotModulesMap.get(ownerPath) || {
|
|
id: ownerPath,
|
|
callbacks: []
|
|
};
|
|
mod2.callbacks.push({
|
|
deps,
|
|
fn: callback
|
|
});
|
|
maps.hotModulesMap.set(ownerPath, mod2);
|
|
}
|
|
const hot = {
|
|
get data() {
|
|
return maps.dataMap.get(ownerPath);
|
|
},
|
|
acceptExports(_, callback) {
|
|
acceptDeps([ownerPath], callback && (([mod2]) => callback(mod2)));
|
|
},
|
|
accept(deps, callback) {
|
|
if (typeof deps === "function" || !deps) {
|
|
acceptDeps([ownerPath], ([mod2]) => deps && deps(mod2));
|
|
} else if (typeof deps === "string") {
|
|
acceptDeps([deps], ([mod2]) => callback && callback(mod2));
|
|
} else if (Array.isArray(deps)) {
|
|
acceptDeps(deps, callback);
|
|
} else {
|
|
throw new TypeError("invalid hot.accept() usage.");
|
|
}
|
|
},
|
|
dispose(cb) {
|
|
maps.disposeMap.set(ownerPath, cb);
|
|
},
|
|
prune(cb) {
|
|
maps.pruneMap.set(ownerPath, cb);
|
|
},
|
|
invalidate() {
|
|
notifyListeners(runner, "vite:invalidate", {
|
|
path: ownerPath,
|
|
message: void 0
|
|
});
|
|
return reload(runner, files);
|
|
},
|
|
on(event, cb) {
|
|
const addToMap = (map) => {
|
|
const existing = map.get(event) || [];
|
|
existing.push(cb);
|
|
map.set(event, existing);
|
|
};
|
|
addToMap(maps.customListenersMap);
|
|
addToMap(newListeners);
|
|
},
|
|
off(event, cb) {
|
|
const removeFromMap = (map) => {
|
|
const existing = map.get(event);
|
|
if (existing === void 0) {
|
|
return;
|
|
}
|
|
const pruned = existing.filter((l) => l !== cb);
|
|
if (pruned.length === 0) {
|
|
map.delete(event);
|
|
return;
|
|
}
|
|
map.set(event, pruned);
|
|
};
|
|
removeFromMap(maps.customListenersMap);
|
|
removeFromMap(newListeners);
|
|
},
|
|
send(event, data) {
|
|
maps.messageBuffer.push(JSON.stringify({ type: "custom", event, data }));
|
|
sendMessageBuffer(runner, emitter);
|
|
}
|
|
};
|
|
return hot;
|
|
}
|
|
|
|
export { createHotContext as a, createHmrEmitter as c, getCache as g, handleMessage as h, reload as r, sendMessageBuffer as s, viteNodeHmrPlugin as v };
|