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.
269 lines
7.0 KiB
JavaScript
269 lines
7.0 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.parseAttributes = parseAttributes;
|
|
const espree_1 = require("./espree");
|
|
const RE_IS_SPACE = /^\s$/u;
|
|
class State {
|
|
constructor(code, index) {
|
|
this.code = code;
|
|
this.index = index;
|
|
}
|
|
getCurr() {
|
|
return this.code[this.index];
|
|
}
|
|
skipSpaces() {
|
|
while (this.currIsSpace()) {
|
|
this.advance();
|
|
if (this.eof())
|
|
break;
|
|
}
|
|
}
|
|
currIsSpace() {
|
|
return RE_IS_SPACE.test(this.getCurr() || "");
|
|
}
|
|
currIs(expect) {
|
|
return this.code.startsWith(expect, this.index);
|
|
}
|
|
eof() {
|
|
return this.index >= this.code.length;
|
|
}
|
|
eat(expect) {
|
|
if (!this.currIs(expect)) {
|
|
return null;
|
|
}
|
|
this.index += expect.length;
|
|
return expect;
|
|
}
|
|
advance() {
|
|
this.index++;
|
|
return this.getCurr();
|
|
}
|
|
}
|
|
/** Parse HTML attributes */
|
|
function parseAttributes(code, startIndex) {
|
|
const attributes = [];
|
|
const state = new State(code, startIndex);
|
|
while (!state.eof()) {
|
|
state.skipSpaces();
|
|
if (state.currIs(">") || state.currIs("/>") || state.eof())
|
|
break;
|
|
attributes.push(parseAttribute(state));
|
|
}
|
|
return { attributes, index: state.index };
|
|
}
|
|
/** Parse HTML attribute */
|
|
function parseAttribute(state) {
|
|
const start = state.index;
|
|
// parse key
|
|
const key = parseAttributeKey(state);
|
|
const keyEnd = state.index;
|
|
state.skipSpaces();
|
|
if (!state.eat("=")) {
|
|
return {
|
|
type: "Attribute",
|
|
name: key,
|
|
value: true,
|
|
start,
|
|
end: keyEnd,
|
|
};
|
|
}
|
|
state.skipSpaces();
|
|
if (state.eof()) {
|
|
return {
|
|
type: "Attribute",
|
|
name: key,
|
|
value: true,
|
|
start,
|
|
end: keyEnd,
|
|
};
|
|
}
|
|
// parse value
|
|
const value = parseAttributeValue(state);
|
|
return {
|
|
type: "Attribute",
|
|
name: key,
|
|
value: [value],
|
|
start,
|
|
end: state.index,
|
|
};
|
|
}
|
|
/** Parse HTML attribute key */
|
|
function parseAttributeKey(state) {
|
|
const start = state.index;
|
|
while (state.advance()) {
|
|
if (state.currIs("=") ||
|
|
state.currIs(">") ||
|
|
state.currIs("/>") ||
|
|
state.currIsSpace()) {
|
|
break;
|
|
}
|
|
}
|
|
const end = state.index;
|
|
return state.code.slice(start, end);
|
|
}
|
|
/** Parse HTML attribute value */
|
|
function parseAttributeValue(state) {
|
|
const start = state.index;
|
|
const quote = state.eat('"') || state.eat("'");
|
|
const startBk = state.index;
|
|
const expression = parseAttributeMustache(state);
|
|
if (expression) {
|
|
if (!quote || state.eat(quote)) {
|
|
const end = state.index;
|
|
return {
|
|
type: "ExpressionTag",
|
|
expression,
|
|
start,
|
|
end,
|
|
};
|
|
}
|
|
}
|
|
state.index = startBk;
|
|
if (quote) {
|
|
if (state.eof()) {
|
|
return {
|
|
type: "Text",
|
|
data: quote,
|
|
raw: quote,
|
|
start,
|
|
end: state.index,
|
|
};
|
|
}
|
|
let c;
|
|
while ((c = state.getCurr())) {
|
|
state.advance();
|
|
if (c === quote) {
|
|
const end = state.index;
|
|
const data = state.code.slice(start + 1, end - 1);
|
|
return {
|
|
type: "Text",
|
|
data,
|
|
raw: data,
|
|
start: start + 1,
|
|
end: end - 1,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
while (state.advance()) {
|
|
if (state.currIsSpace() || state.currIs(">") || state.currIs("/>")) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const end = state.index;
|
|
const data = state.code.slice(start, end);
|
|
return {
|
|
type: "Text",
|
|
data,
|
|
raw: data,
|
|
start,
|
|
end,
|
|
};
|
|
}
|
|
/** Parse mustache */
|
|
function parseAttributeMustache(state) {
|
|
if (!state.eat("{")) {
|
|
return null;
|
|
}
|
|
// parse simple expression
|
|
const leadingComments = [];
|
|
const startBk = state.index;
|
|
state.skipSpaces();
|
|
let start = state.index;
|
|
while (!state.eof()) {
|
|
if (state.eat("//")) {
|
|
leadingComments.push(parseInlineComment(state.index - 2));
|
|
state.skipSpaces();
|
|
start = state.index;
|
|
continue;
|
|
}
|
|
if (state.eat("/*")) {
|
|
leadingComments.push(parseBlockComment(state.index - 2));
|
|
state.skipSpaces();
|
|
start = state.index;
|
|
continue;
|
|
}
|
|
const stringQuote = state.eat('"') || state.eat("'");
|
|
if (stringQuote) {
|
|
skipString(stringQuote);
|
|
state.skipSpaces();
|
|
continue;
|
|
}
|
|
const endCandidate = state.index;
|
|
state.skipSpaces();
|
|
if (state.eat("}")) {
|
|
const end = endCandidate;
|
|
try {
|
|
const espree = (0, espree_1.getEspree)();
|
|
const expression = espree.parse(state.code.slice(start, end), {
|
|
ecmaVersion: espree.latestEcmaVersion,
|
|
}).body[0].expression;
|
|
delete expression.range;
|
|
return Object.assign(Object.assign({}, expression), { leadingComments,
|
|
start,
|
|
end });
|
|
}
|
|
catch (_a) {
|
|
break;
|
|
}
|
|
}
|
|
state.advance();
|
|
}
|
|
state.index = startBk;
|
|
return null;
|
|
function parseInlineComment(tokenStart) {
|
|
const valueStart = state.index;
|
|
let valueEnd = null;
|
|
while (!state.eof()) {
|
|
if (state.eat("\n")) {
|
|
valueEnd = state.index - 1;
|
|
break;
|
|
}
|
|
state.advance();
|
|
}
|
|
if (valueEnd == null) {
|
|
valueEnd = state.index;
|
|
}
|
|
return {
|
|
type: "Line",
|
|
value: state.code.slice(valueStart, valueEnd),
|
|
start: tokenStart,
|
|
end: state.index,
|
|
};
|
|
}
|
|
function parseBlockComment(tokenStart) {
|
|
const valueStart = state.index;
|
|
let valueEnd = null;
|
|
while (!state.eof()) {
|
|
if (state.eat("*/")) {
|
|
valueEnd = state.index - 2;
|
|
break;
|
|
}
|
|
state.advance();
|
|
}
|
|
if (valueEnd == null) {
|
|
valueEnd = state.index;
|
|
}
|
|
return {
|
|
type: "Block",
|
|
value: state.code.slice(valueStart, valueEnd),
|
|
start: tokenStart,
|
|
end: state.index,
|
|
};
|
|
}
|
|
function skipString(stringQuote) {
|
|
while (!state.eof()) {
|
|
if (state.eat(stringQuote)) {
|
|
break;
|
|
}
|
|
if (state.eat("\\")) {
|
|
// escape
|
|
state.advance();
|
|
}
|
|
state.advance();
|
|
}
|
|
}
|
|
}
|