mesh-build-go publishes the build runner as its own package, so the other build modules share it without mesh-sdk (ADR 0593 §1)
The runner, the toolchain guard, the fetch, the parsers, buildcpu, the conformance kit and mesh-pack move from mesh-sdk's buildstep into modules/mesh-build-go/buildstep (own go.mod). mesh-build-go, mesh-build-node and mesh-build-image require it by a same-repository replace; what the delivery parts read (the failure contract, the fetch request, the stale sweep, the module tree shape, NewID, CapacitySetting) stays imported from mesh-sdk as sdkstep. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -4,8 +4,9 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -90,13 +91,13 @@ func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
|
||||
const StepEndTool = "build_node_step_end"
|
||||
|
||||
// endLeftRunning is the module's start sweep (issue 1890): it ends the steps of its node that a previous process left
|
||||
// running (buildstep.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
|
||||
// running (sdkstep.EndStaleAtStart; every step names its owner). main runs it once at the start, and the module's test
|
||||
// runs it through the conformance kit's left-running case.
|
||||
func (m *Module) endLeftRunning(ctx context.Context, scan buildstep.Scan, booted time.Time, logf func(string, ...any)) {
|
||||
buildstep.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
|
||||
func (m *Module) endLeftRunning(ctx context.Context, scan sdkstep.Scan, booted time.Time, logf func(string, ...any)) {
|
||||
sdkstep.EndStaleAtStart(ctx, scan, m.Node, booted, logf)
|
||||
}
|
||||
|
||||
// stepScan is the module's steps state as buildstep reads it.
|
||||
func (m *Module) stepScan() buildstep.Scan {
|
||||
return buildstep.ScanOf(stdio.State(delivery.StepsState))
|
||||
func (m *Module) stepScan() sdkstep.Scan {
|
||||
return sdkstep.ScanOf(stdio.State(delivery.StepsState))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -31,7 +32,7 @@ func fakeSteps(calls *[]string) []StepTool {
|
||||
}
|
||||
|
||||
func nodeAsk(kind string) delivery.StepAsk {
|
||||
a := delivery.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: delivery.AskNode, Asker: "mesh-delivery-checker", At: time.Now()}
|
||||
a := delivery.StepAsk{ID: sdkstep.NewID(), Kind: kind, Language: delivery.AskNode, Asker: "mesh-delivery-checker", At: time.Now()}
|
||||
fetch := map[string]any{"repository": "novox/mesh-catalog", "commit": askCommit}
|
||||
if kind == delivery.AskDelivery {
|
||||
a.Asker = "mesh-delivery-builder"
|
||||
@@ -115,7 +116,7 @@ func TestManifestTakesTheBuildCapacity(t *testing.T) {
|
||||
t.Errorf("the build-hours description names a node pair")
|
||||
}
|
||||
var capacity float64
|
||||
_ = json.Unmarshal(doc.Config[buildstep.CapacitySetting].Default, &capacity)
|
||||
_ = json.Unmarshal(doc.Config[sdkstep.CapacitySetting].Default, &capacity)
|
||||
if capacity != 0 {
|
||||
t.Errorf("the capacity's default = %g, want 0", capacity)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -213,11 +214,11 @@ func (m *Module) bundle(ctx context.Context, root string, publish bool, dir stri
|
||||
|
||||
// writeLaunchers writes, beside every entrypoint, an executable that imports the entrypoint and serves what it
|
||||
// registered over MCP on stdio (builder.go:1926-1956; novox/hq ADR 0193). Which entrypoint gets which launcher is
|
||||
// mesh-sdk's one rule, buildstep.Launchers, which the controller reads a bundle by (R4e). Its answer is each
|
||||
// mesh-sdk's one rule, sdkstep.Launchers, which the controller reads a bundle by (R4e). Its answer is each
|
||||
// entrypoint's launcher, relative to the bundle's root.
|
||||
func writeLaunchers(root string, a artifact) (map[string]string, error) {
|
||||
out := map[string]string{}
|
||||
for entry, launcher := range buildstep.Launchers(a.Language, a.Entrypoints) {
|
||||
for entry, launcher := range sdkstep.Launchers(a.Language, a.Entrypoints) {
|
||||
body := "#!/usr/bin/env node\n" +
|
||||
"// Written by the mesh's builder (novox/hq ADR 0193): serve what " + entry + " registers,\n" +
|
||||
"// over MCP on stdio, as the module the node's runtime names in MESH_SERVED_MODULE.\n" +
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -255,7 +255,7 @@ func TestTheBuildRunsWithThePackagesTwoLevelsAboveTheModule(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// R4e: the launchers a build writes are mesh-sdk's one rule (buildstep.Launchers), the names the controller reads a bundle
|
||||
// R4e: the launchers a build writes are mesh-sdk's one rule (sdkstep.Launchers), the names the controller reads a bundle
|
||||
// by; an entrypoint that is not JavaScript gets none.
|
||||
func TestTheLaunchersAreTheSDKsRule(t *testing.T) {
|
||||
a := artifact{Language: "typescript", Entrypoints: []string{"tools/index.js", "web/server.js", "assets/x.css"}}
|
||||
@@ -263,7 +263,7 @@ func TestTheLaunchersAreTheSDKsRule(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := buildstep.Launchers(a.Language, a.Entrypoints); !maps.Equal(got, want) || len(got) != 2 {
|
||||
if want := sdkstep.Launchers(a.Language, a.Entrypoints); !maps.Equal(got, want) || len(got) != 2 {
|
||||
t.Errorf("launchers %v, mesh-sdk's rule %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -55,15 +56,15 @@ type TestResult struct {
|
||||
// the catalogue's TypeScript against the bounds is a later release's (R4f), not this one's gate.
|
||||
OverBound bool `json:"over_bound"`
|
||||
Failed []Failure `json:"failed,omitempty"`
|
||||
// By is the build module and the SDK it was built with (buildstep.By): the checker names it when a failure hides
|
||||
// By is the build module and the SDK it was built with (sdkstep.By): the checker names it when a failure hides
|
||||
// its cause.
|
||||
By string `json:"by,omitempty"`
|
||||
}
|
||||
|
||||
// Failure is the SDK's failure contract (buildstep.Failure, issue 1901): a failing test of the test script, read from
|
||||
// Failure is the SDK's failure contract (sdkstep.Failure, issue 1901): a failing test of the test script, read from
|
||||
// node's TAP reporter, or a type-check or build that failed, each with its cause (never empty, never a passing line) and
|
||||
// its own output up to 4096 bytes, cause first.
|
||||
type Failure = buildstep.Failure
|
||||
type Failure = sdkstep.Failure
|
||||
|
||||
func (m *Module) testTool() StepTool {
|
||||
return StepTool{
|
||||
@@ -106,7 +107,7 @@ func (m *Module) test(ctx context.Context, args map[string]any) (any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := &TestResult{Workspace: workspace, Module: module, Level: level, By: buildstep.By("mesh-build-node")}
|
||||
res := &TestResult{Workspace: workspace, Module: module, Level: level, By: sdkstep.By("mesh-build-node")}
|
||||
script := "test"
|
||||
if level == "integration" {
|
||||
script = "test:integration"
|
||||
@@ -203,7 +204,7 @@ func testFailures(where, name, dir string, r forgekey.Ran, report string) []Fail
|
||||
if name == "test" || name == "test:integration" {
|
||||
return buildstep.ReadTAP(where, name, dir, report, r.Stdout+"\n"+r.Stderr)
|
||||
}
|
||||
return []Failure{buildstep.ConsoleFailure(where, name, r.Stdout+"\n"+r.Stderr)}
|
||||
return []Failure{sdkstep.ConsoleFailure(where, name, r.Stdout+"\n"+r.Stderr)}
|
||||
}
|
||||
|
||||
// failureWhy is the words of one failure: the test or check that failed, where, and its cause.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.novox.be/novox/mesh-sdk/go/buildcpu"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/buildcpu"
|
||||
)
|
||||
|
||||
// The processors a step may take (novox/hq ADR 0553, issue 1806). The node-engine holds this module's runtime, with
|
||||
|
||||
@@ -23,7 +23,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -56,8 +57,8 @@ type FetchResult struct {
|
||||
Conflicts []string `json:"conflicts,omitempty"`
|
||||
Changed []string `json:"changed,omitempty"`
|
||||
// Programs are the changed files that are compiled programs (issue 1577); a merge answers it, a pointer to an empty list when there are none, absent for a fetch that made no merge.
|
||||
Programs *[]buildstep.Program `json:"programs,omitempty"`
|
||||
Trees map[string]buildstep.ModuleTree `json:"trees,omitempty"`
|
||||
Programs *[]buildstep.Program `json:"programs,omitempty"`
|
||||
Trees map[string]sdkstep.ModuleTree `json:"trees,omitempty"`
|
||||
// Group is the group's package the fetch packed and served to the TypeScript members (the call's `package`):
|
||||
// "<name>@<version> <integrity>".
|
||||
Group string `json:"group,omitempty"`
|
||||
@@ -90,8 +91,8 @@ func (m *Module) fetchTool() StepTool {
|
||||
"repository": map[string]any{"type": "string", "description": "<owner>/<repository> on the forge"},
|
||||
"commit": map[string]any{"type": "string", "description": "the full commit, 40 hex digits"},
|
||||
"ref": map[string]any{"type": "string", "description": "refs/pull/<n>/head when only a pull request's head carries the commit"},
|
||||
"dir": buildstep.FetchRequestProperties()["dir"],
|
||||
"paths": buildstep.FetchRequestProperties()["paths"],
|
||||
"dir": sdkstep.FetchRequestProperties()["dir"],
|
||||
"paths": sdkstep.FetchRequestProperties()["paths"],
|
||||
"package": map[string]any{"type": "object", "description": "optional: the group's package member (ADR 0500 §5), packed here, publishing nothing, and served to each TypeScript member whose lock names its version, in place of the registry's",
|
||||
"properties": map[string]any{"repository": map[string]any{"type": "string"}, "commit": map[string]any{"type": "string"}, "ref": map[string]any{"type": "string"}},
|
||||
"required": []string{"repository", "commit"}},
|
||||
@@ -109,7 +110,7 @@ func (m *Module) fetchTool() StepTool {
|
||||
func (m *Module) fetch(ctx context.Context, args map[string]any) (res any, err error) {
|
||||
// What the builder and the checker ask, in mesh-sdk's one form (ADR 0499 §4): with the module's directory and its
|
||||
// named paths, the workspace is narrowed to them (buildstep.Narrow).
|
||||
req, err := buildstep.ParseFetchRequest(args)
|
||||
req, err := sdkstep.ParseFetchRequest(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,7 +119,7 @@ func (m *Module) fetch(ctx context.Context, args map[string]any) (res any, err e
|
||||
return nil, err
|
||||
}
|
||||
m.CleanWorkspaces(m.now())
|
||||
id := buildstep.NewID()
|
||||
id := sdkstep.NewID()
|
||||
done := m.spaces().Hold(id)
|
||||
defer func() {
|
||||
done()
|
||||
@@ -131,7 +132,7 @@ func (m *Module) fetch(ctx context.Context, args map[string]any) (res any, err e
|
||||
return m.fetchInto(ctx, req, group, id)
|
||||
}
|
||||
|
||||
func (m *Module) fetchInto(ctx context.Context, req buildstep.FetchRequest, group *groupPackage, id string) (any, error) {
|
||||
func (m *Module) fetchInto(ctx context.Context, req sdkstep.FetchRequest, group *groupPackage, id string) (any, error) {
|
||||
repository, commit, ref, base := req.Repository, req.Commit, req.Ref, req.Base
|
||||
var err error
|
||||
root := filepath.Join(m.Work, id)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,18 +5,18 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep/conformance"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep/conformance"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
// 1901/AC7: the module's own parse path (testFailures, as the test step calls it on a command that failed) keeps the
|
||||
// failure contract on every captured node --test and tsc case of the SDK's conformance kit.
|
||||
func TestTheTestStepKeepsTheFailureContract(t *testing.T) {
|
||||
conformance.Run(t, conformance.NodeTest, func(o conformance.Output) []buildstep.Failure {
|
||||
conformance.Run(t, conformance.NodeTest, func(o conformance.Output) []sdkstep.Failure {
|
||||
return testFailures(".", "test", o.Dir, forgekey.Ran{Status: 1, Stdout: o.Stdout, Stderr: o.Stderr}, o.Report)
|
||||
})
|
||||
conformance.Run(t, conformance.TypeCheck, func(o conformance.Output) []buildstep.Failure {
|
||||
conformance.Run(t, conformance.TypeCheck, func(o conformance.Output) []sdkstep.Failure {
|
||||
return testFailures(".", "type-check", o.Dir, forgekey.Ran{Status: 2, Stdout: o.Stdout, Stderr: o.Stderr}, "")
|
||||
})
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func TestTheTestStepKeepsTheFailureContract(t *testing.T) {
|
||||
// case: a step a previous process left running is ended at once and said, whatever its age, and a step of this process
|
||||
// is kept.
|
||||
func TestTheStartSweepEndsWhatAPreviousProcessLeft(t *testing.T) {
|
||||
conformance.LeftRunning(t, func(ctx context.Context, scan buildstep.Scan, node string, before time.Time, logf func(string, ...any)) {
|
||||
conformance.LeftRunning(t, func(ctx context.Context, scan sdkstep.Scan, node string, before time.Time, logf func(string, ...any)) {
|
||||
(&Module{Node: node}).endLeftRunning(ctx, scan, before, logf)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/health"
|
||||
@@ -68,7 +69,7 @@ type Module struct {
|
||||
// sets floorOf(node), and a zero Floor checks nothing.
|
||||
Floor buildstep.Floor
|
||||
|
||||
// Settings is the module's settings file, which holds the node's build capacity (buildstep.CapacitySetting); Capacity,
|
||||
// Settings is the module's settings file, which holds the node's build capacity (sdkstep.CapacitySetting); Capacity,
|
||||
// when set, answers it instead (a test).
|
||||
Settings string
|
||||
Capacity func() int
|
||||
@@ -139,7 +140,7 @@ func (m *Module) Start(ctx context.Context) {
|
||||
m.Say("the toolchain: %v", err)
|
||||
}
|
||||
m.CleanWorkspaces(m.now())
|
||||
buildstep.EndStaleRound(m.stepScan(), m.Node, m.Say) // asks past the step bound, on the module's own round (issue 1890)
|
||||
sdkstep.EndStaleRound(m.stepScan(), m.Node, m.Say) // asks past the step bound, on the module's own round (issue 1890)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
)
|
||||
|
||||
// registry is the artifact store's address, host:port, as the manifest bound it into RegistryFile.
|
||||
|
||||
@@ -5,8 +5,8 @@ package main
|
||||
// it, and swept unheld past buildstep.UnheldBound.
|
||||
|
||||
import (
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
)
|
||||
|
||||
// ReleaseTool removes a workspace whose caller's steps have ended.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||
"git.novox.be/novox/mesh-sdk/go/health"
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user