refactor: split web frontend + gateway out to uniweb app (bump 0.13.0)

The SPA (web/) and the web gateway (cmd/webgw) move to a dedicated app
projects/message_bus/apps/uniweb (its own Gitea sub-repo). unibus is now
strictly the bus plane: membership/keys, the client library and demo peers.
uniweb consumes unibus as a Go module via replace => ../unibus.

No capability lost; same SPA and gateway, in their own service folder.
go build/vet/test green after extraction.
This commit is contained in:
2026-06-13 21:21:08 +02:00
parent fadee1a7d0
commit 9661a5ce1f
31166 changed files with 2029366 additions and 3677 deletions
+1
View File
@@ -0,0 +1 @@
../../postcss@8.5.15/node_modules/postcss
@@ -0,0 +1 @@
../../postcss-js@4.1.0_postcss@8.5.15/node_modules/postcss-js
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright 2015 Andrey Sitnik <andrey@sitnik.ru>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,67 @@
# PostCSS Mixins
<img align="right" width="135" height="95"
title="Philosophers stone, logo of PostCSS"
src="https://postcss.org/logo-leftp.svg">
[PostCSS] plugin for mixins.
Note, that you must set this plugin before [postcss-simple-vars]
and [postcss-nested].
```css
@define-mixin icon $network, $color: blue {
.icon.is-$(network) {
color: $color;
@mixin-content;
}
.icon.is-$(network):hover {
color: white;
background: $color;
}
}
@mixin icon twitter {
background: url(twt.png);
}
@mixin icon youtube, red {
background: url(youtube.png);
}
```
```css
.icon.is-twitter {
color: blue;
background: url(twt.png);
}
.icon.is-twitter:hover {
color: white;
background: blue;
}
.icon.is-youtube {
color: red;
background: url(youtube.png);
}
.icon.is-youtube:hover {
color: white;
background: red;
}
```
[postcss-utilities] collection is better for `clearfix` and other popular hacks.
For simple cases you can use [postcss-define-property].
[postcss-define-property]: https://github.com/daleeidd/postcss-define-property
[postcss-utilities]: https://github.com/ismamz/postcss-utilities
[postcss-simple-vars]: https://github.com/postcss/postcss-simple-vars
[postcss-nested]: https://github.com/postcss/postcss-nested
[PostCSS]: https://github.com/postcss/postcss
<a href="https://evilmartians.com/?utm_source=postcss-mixins">
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg"
alt="Sponsored by Evil Martians" width="236" height="54">
</a>
## Docs
Read full docs **[here](https://github.com/postcss/postcss-mixins#readme)**.
@@ -0,0 +1,20 @@
import type { PluginCreator, AtRule } from 'postcss'
declare interface MixinOutput {
[key: string]: string | MixinOutput
}
declare interface Mixin {
(mixinAtRule: AtRule, ...args: string[])
}
declare type Mixins = Record<string, MixinOutput | Mixin>
declare const mixins: PluginCreator<{
mixins?: Mixins
mixinsDir?: string | string[]
mixinsFiles?: string | string[]
silent?: boolean
}>
export = mixins
@@ -0,0 +1,283 @@
let { readFileSync } = require('node:fs')
let { basename, extname, join, relative } = require('node:path')
let { parse } = require('postcss-js')
let vars = require('postcss-simple-vars')
let sugarss = require('sugarss')
let { globSync } = require('tinyglobby')
let MIXINS_GLOB = '*.{js,cjs,mjs,json,css,sss,pcss}'
function addMixin(helpers, mixins, rule, file) {
let name = rule.params.split(/\s/, 1)[0]
let other = rule.params.slice(name.length).trim()
let args = []
if (other.length) {
args = helpers.list.comma(other).map(str => {
let arg = str.split(':', 1)[0]
let defaults = str.slice(arg.length + 1)
return [arg.slice(1).trim(), defaults.trim()]
})
}
let content = false
rule.walkAtRules('mixin-content', () => {
content = true
return false
})
mixins[name] = { args, content, mixin: rule }
if (file) mixins[name].file = file
rule.remove()
}
function processModulesForHotReloadRecursively(module, helpers) {
let moduleId = module.id
module.children.forEach(childModule => {
helpers.result.messages.push({
file: childModule.id,
parent: moduleId,
type: 'dependency'
})
processModulesForHotReloadRecursively(childModule, helpers)
})
delete require.cache[moduleId]
}
function loadGlobalMixin(helpers, globs) {
let cwd = process.cwd()
let files = globSync(globs, {
caseSensitiveMatch: false,
expandDirectories: false,
ignore: ['**/.git/**']
})
let mixins = {}
files.forEach(i => {
let ext = extname(i).toLowerCase()
let name = basename(i, extname(i))
let path = join(cwd, relative(cwd, i))
if (ext === '.css' || ext === '.pcss' || ext === '.sss') {
let content = readFileSync(path)
let root
if (ext === '.sss') {
root = sugarss.parse(content, { from: path })
} else {
root = helpers.parse(content, { from: path })
}
root.walkAtRules('define-mixin', atrule => {
addMixin(helpers, mixins, atrule, path)
})
} else {
try {
mixins[name] = { file: path, mixin: require(path) }
let module = require.cache[require.resolve(path)]
if (module) {
processModulesForHotReloadRecursively(module, helpers)
}
} catch {}
}
})
return mixins
}
function addGlobalMixins(helpers, local, global, parent) {
for (let name in global) {
helpers.result.messages.push({
file: global[name].file,
parent: parent || '',
type: 'dependency'
})
local[name] = global[name]
}
}
function watchNewMixins(helpers, mixinsDirs) {
let uniqueDirsPath = Array.from(new Set(mixinsDirs))
for (let dir of uniqueDirsPath) {
helpers.result.messages.push({
dir,
glob: MIXINS_GLOB,
parent: '',
type: 'dir-dependency'
})
}
}
function processMixinContent(rule, from) {
rule.walkAtRules('mixin-content', content => {
if (from.nodes && from.nodes.length > 0) {
content.replaceWith(from.clone().nodes)
} else {
content.remove()
}
})
}
function insertObject(rule, obj, singeArgumentsMap) {
let root = parse(obj)
root.each(node => {
node.source = rule.source
})
processMixinContent(root, rule)
unwrapSingleArguments(root.nodes, singeArgumentsMap)
rule.parent.insertBefore(rule, root)
}
function unwrapSingleArguments(rules, singleArgumentsMap) {
if (singleArgumentsMap.size <= 0) {
return
}
for (let rule of rules) {
if (rule.type === 'decl') {
if (rule.value.includes('single-arg')) {
let newValue = rule.value
for (let [key, value] of singleArgumentsMap) {
newValue = newValue.replace(key, value)
}
rule.value = newValue
}
} else if (rule.type === 'rule') {
unwrapSingleArguments(rule.nodes, singleArgumentsMap)
}
}
}
function resolveSingleArgumentValue(value, parentNode) {
let content = value.slice('single-arg'.length).trim()
if (!content.startsWith('(') || !content.endsWith(')')) {
throw parentNode.error(
'Content of single-arg must be wrapped in brackets: ' + value
)
}
return content.slice(1, -1)
}
function insertMixin(helpers, mixins, rule, opts) {
let name = rule.params.split(/\s/, 1)[0]
let rest = rule.params.slice(name.length).trim()
if (name.includes('(')) {
throw rule.error(
'Remove brackets from mixin. Like: @mixin name(1px) → @mixin name 1px'
)
}
let params
if (rest.trim() === '') {
params = []
} else {
params = helpers.list.comma(rest)
}
let meta = mixins[name]
let mixin = meta && meta.mixin
let singleArgumentsMap = new Map(
params
.filter(param => param.startsWith('single-arg'))
.map(param => [param, resolveSingleArgumentValue(param, rule)])
)
if (!meta) {
if (!opts.silent) {
throw rule.error('Undefined mixin ' + name)
}
} else if (mixin.name === 'define-mixin') {
let i
let values = {}
for (i = 0; i < meta.args.length; i++) {
values[meta.args[i][0]] = params[i] || meta.args[i][1]
}
let proxy = new helpers.Root()
for (i = 0; i < mixin.nodes.length; i++) {
let node = mixin.nodes[i].clone()
delete node.raws.before
proxy.append(node)
}
if (meta.args.length) {
proxy = helpers.postcss([vars({ only: values })]).process(proxy).root
}
if (meta.content) processMixinContent(proxy, rule)
unwrapSingleArguments(proxy.nodes, singleArgumentsMap)
rule.parent.insertBefore(rule, proxy)
} else if (typeof mixin === 'object') {
insertObject(rule, mixin, singleArgumentsMap)
} else if (typeof mixin === 'function') {
let args = [rule].concat(params)
rule.walkAtRules(atRule => {
if (atRule.name === 'add-mixin' || atRule.name === 'mixin') {
insertMixin(helpers, mixins, atRule, opts)
}
})
let nodes = mixin(...args)
if (typeof nodes === 'object') {
insertObject(rule, nodes, singleArgumentsMap)
}
} else {
throw new Error('Wrong ' + name + ' mixin type ' + typeof mixin)
}
if (rule.parent) rule.remove()
}
module.exports = (opts = {}) => {
let loadFrom = []
if (opts.mixinsDir) {
if (!Array.isArray(opts.mixinsDir)) {
opts.mixinsDir = [opts.mixinsDir]
}
loadFrom = opts.mixinsDir.map(dir => join(dir, MIXINS_GLOB))
}
if (opts.mixinsFiles) loadFrom = loadFrom.concat(opts.mixinsFiles)
loadFrom = loadFrom.map(path => path.replace(/\\/g, '/'))
return {
postcssPlugin: 'postcss-mixins',
prepare() {
let mixins = {}
if (typeof opts.mixins === 'object') {
for (let i in opts.mixins) {
mixins[i] = { mixin: opts.mixins[i] }
}
}
return {
AtRule: {
'add-mixin': (node, helpers) => {
insertMixin(helpers, mixins, node, opts)
},
'define-mixin': (node, helpers) => {
addMixin(helpers, mixins, node)
node.remove()
},
'mixin': (node, helpers) => {
insertMixin(helpers, mixins, node, opts)
}
},
Once(root, helpers) {
if (loadFrom.length > 0) {
try {
let global = loadGlobalMixin(helpers, loadFrom)
addGlobalMixins(helpers, mixins, global, opts.parent)
} catch {}
}
},
OnceExit(_, helpers) {
if (opts.mixinsDir && opts.mixinsDir.length > 0) {
watchNewMixins(helpers, opts.mixinsDir)
}
}
}
}
}
}
module.exports.postcss = true
@@ -0,0 +1,37 @@
{
"name": "postcss-mixins",
"version": "12.1.2",
"description": "PostCSS plugin for mixins",
"keywords": [
"postcss",
"css",
"postcss-plugin",
"mixins",
"sass"
],
"author": "Andrey Sitnik <andrey@sitnik.ru>",
"license": "MIT",
"repository": "postcss/postcss-mixins",
"engines": {
"node": "^20.0 || ^22.0 || >=24.0"
},
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"peerDependencies": {
"postcss": "^8.2.14"
},
"dependencies": {
"postcss-js": "^4.0.1",
"postcss-simple-vars": "^7.0.1",
"sugarss": "^5.0.0",
"tinyglobby": "^0.2.14"
}
}
@@ -0,0 +1 @@
../../postcss-simple-vars@7.0.1_postcss@8.5.15/node_modules/postcss-simple-vars
+1
View File
@@ -0,0 +1 @@
../../sugarss@5.0.1_postcss@8.5.15/node_modules/sugarss
@@ -0,0 +1 @@
../../tinyglobby@0.2.17/node_modules/tinyglobby