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.
153 lines
3.4 KiB
JavaScript
153 lines
3.4 KiB
JavaScript
import { WalkerBase } from './walker.js';
|
|
|
|
/**
|
|
* @typedef { import('estree').Node} Node
|
|
* @typedef { import('./walker.js').WalkerContext} WalkerContext
|
|
* @typedef {(
|
|
* this: WalkerContext,
|
|
* node: Node,
|
|
* parent: Node | null,
|
|
* key: string | number | symbol | null | undefined,
|
|
* index: number | null | undefined
|
|
* ) => Promise<void>} AsyncHandler
|
|
*/
|
|
|
|
export class AsyncWalker extends WalkerBase {
|
|
/**
|
|
*
|
|
* @param {AsyncHandler} [enter]
|
|
* @param {AsyncHandler} [leave]
|
|
*/
|
|
constructor(enter, leave) {
|
|
super();
|
|
|
|
/** @type {boolean} */
|
|
this.should_skip = false;
|
|
|
|
/** @type {boolean} */
|
|
this.should_remove = false;
|
|
|
|
/** @type {Node | null} */
|
|
this.replacement = null;
|
|
|
|
/** @type {WalkerContext} */
|
|
this.context = {
|
|
skip: () => (this.should_skip = true),
|
|
remove: () => (this.should_remove = true),
|
|
replace: (node) => (this.replacement = node)
|
|
};
|
|
|
|
/** @type {AsyncHandler | undefined} */
|
|
this.enter = enter;
|
|
|
|
/** @type {AsyncHandler | undefined} */
|
|
this.leave = leave;
|
|
}
|
|
|
|
/**
|
|
* @template {Node} Parent
|
|
* @param {Node} node
|
|
* @param {Parent | null} parent
|
|
* @param {keyof Parent} [prop]
|
|
* @param {number | null} [index]
|
|
* @returns {Promise<Node | null>}
|
|
*/
|
|
async visit(node, parent, prop, index) {
|
|
if (node) {
|
|
if (this.enter) {
|
|
const _should_skip = this.should_skip;
|
|
const _should_remove = this.should_remove;
|
|
const _replacement = this.replacement;
|
|
this.should_skip = false;
|
|
this.should_remove = false;
|
|
this.replacement = null;
|
|
|
|
await this.enter.call(this.context, node, parent, prop, index);
|
|
|
|
if (this.replacement) {
|
|
node = this.replacement;
|
|
this.replace(parent, prop, index, node);
|
|
}
|
|
|
|
if (this.should_remove) {
|
|
this.remove(parent, prop, index);
|
|
}
|
|
|
|
const skipped = this.should_skip;
|
|
const removed = this.should_remove;
|
|
|
|
this.should_skip = _should_skip;
|
|
this.should_remove = _should_remove;
|
|
this.replacement = _replacement;
|
|
|
|
if (skipped) return node;
|
|
if (removed) return null;
|
|
}
|
|
|
|
/** @type {keyof Node} */
|
|
let key;
|
|
|
|
for (key in node) {
|
|
/** @type {unknown} */
|
|
const value = node[key];
|
|
|
|
if (value && typeof value === 'object') {
|
|
if (Array.isArray(value)) {
|
|
const nodes = /** @type {Array<unknown>} */ (value);
|
|
for (let i = 0; i < nodes.length; i += 1) {
|
|
const item = nodes[i];
|
|
if (isNode(item)) {
|
|
if (!(await this.visit(item, node, key, i))) {
|
|
// removed
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
} else if (isNode(value)) {
|
|
await this.visit(value, node, key, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.leave) {
|
|
const _replacement = this.replacement;
|
|
const _should_remove = this.should_remove;
|
|
this.replacement = null;
|
|
this.should_remove = false;
|
|
|
|
await this.leave.call(this.context, node, parent, prop, index);
|
|
|
|
if (this.replacement) {
|
|
node = this.replacement;
|
|
this.replace(parent, prop, index, node);
|
|
}
|
|
|
|
if (this.should_remove) {
|
|
this.remove(parent, prop, index);
|
|
}
|
|
|
|
const removed = this.should_remove;
|
|
|
|
this.replacement = _replacement;
|
|
this.should_remove = _should_remove;
|
|
|
|
if (removed) return null;
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ducktype a node.
|
|
*
|
|
* @param {unknown} value
|
|
* @returns {value is Node}
|
|
*/
|
|
function isNode(value) {
|
|
return (
|
|
value !== null && typeof value === 'object' && 'type' in value && typeof value.type === 'string'
|
|
);
|
|
}
|