diff --git a/docs/plans/2026-03-14-build-ui-elements.md b/docs/plans/2026-03-14-build-ui-elements.md new file mode 100644 index 0000000..5e37af5 --- /dev/null +++ b/docs/plans/2026-03-14-build-ui-elements.md @@ -0,0 +1,115 @@ +# Build UI Service Provider + Lit Custom Elements + +**Date**: 14 March 2026 +**Module**: `forge.lthn.ai/core/go-build` +**Pattern**: Follows `go-scm/pkg/api/` provider + `go-scm/ui/` Lit elements + +## Overview + +Add a service provider (`BuildProvider`) that exposes the existing build, release, and SDK subsystems as REST endpoints, plus a set of Lit custom elements for GUI display within the Core IDE. + +The provider wraps existing functions — no business logic is reimplemented. + +## Architecture + +``` +go-build/ +├── pkg/api/ +│ ├── provider.go # BuildProvider (Provider + Streamable + Describable + Renderable) +│ ├── embed.go # //go:embed for UI assets +│ └── ui/dist/ # Built JS bundle (populated by npm run build) +├── pkg/api/ +│ └── provider_test.go # Identity + endpoint tests +└── ui/ + ├── package.json + ├── tsconfig.json + ├── vite.config.ts + ├── index.html # Demo page + └── src/ + ├── index.ts # Bundle entry + ├── build-panel.ts # — tabs container + ├── build-config.ts # — .core/build.yaml + discovery + ├── build-artifacts.ts # — dist/ contents + ├── build-release.ts # — version, changelog, publish + ├── build-sdk.ts # — OpenAPI diff, SDK generation + └── shared/ + ├── api.ts # Typed fetch wrapper for /api/v1/build/* + └── events.ts # WS event connection for build.* channels +``` + +## REST Endpoints + +All under `/api/v1/build`: + +| Method | Path | Handler | Wraps | +|--------|---------------------|------------------|------------------------------------| +| GET | /config | getConfig | `build.LoadConfig(io.Local, cwd)` | +| GET | /discover | discoverProject | `build.Discover(io.Local, cwd)` | +| POST | /build | triggerBuild | Full build pipeline | +| GET | /artifacts | listArtifacts | Scan `dist/` directory | +| GET | /release/version | getVersion | `release.DetermineVersion(cwd)` | +| GET | /release/changelog | getChangelog | `release.Generate(cwd, "", "")` | +| POST | /release | triggerRelease | `release.Run()` or `Publish()` | +| GET | /sdk/diff | getSdkDiff | `sdk.Diff(base, revision)` | +| POST | /sdk/generate | generateSdk | `sdk.SDK.Generate(ctx)` | + +## WS Channels + +- `build.started` — Build commenced (includes project type, targets) +- `build.complete` — Build finished (includes artifact list) +- `build.failed` — Build error (includes error message) +- `release.started` — Release pipeline started +- `release.complete` — Release published +- `sdk.generated` — SDK generation complete + +## Custom Elements + +### `` (build-panel.ts) +Top-level tabbed container with tabs: Config, Build, Release, SDK. +Follows HLCRF layout from go-scm. + +### `` (build-config.ts) +- Displays `.core/build.yaml` fields (project name, binary, targets, flags, signing) +- Shows detected project type from discovery +- Read-only display of current configuration + +### `` (build-artifacts.ts) +- Lists files in `dist/` with size, checksum status +- "Build" button with confirmation dialogue (POST /build is destructive) +- Real-time progress via WS events + +### `` (build-release.ts) +- Current version from git tags +- Changelog preview (rendered markdown) +- Publisher targets from `.core/release.yaml` +- "Release" button with confirmation dialogue + +### `` (build-sdk.ts) +- OpenAPI diff results (breaking/non-breaking changes) +- SDK generation controls (language selection) +- Generation status + +## Dependencies Added to go.mod + +``` +forge.lthn.ai/core/api v0.1.0 +forge.lthn.ai/core/go-ws v0.1.0 +github.com/gin-gonic/gin v1.11.0 +``` + +## Safety Considerations + +- POST /build and POST /release are destructive operations +- UI elements include confirmation dialogues before triggering +- Provider accepts a `projectDir` parameter (defaults to CWD) +- Build/release operations run synchronously; WS events provide progress + +## Implementation Tasks + +1. Create `pkg/api/provider.go` — BuildProvider struct + all handlers +2. Create `pkg/api/provider_test.go` — Identity + config/discover tests +3. Create `pkg/api/embed.go` — //go:embed directive +4. Create `ui/` directory with full Lit element suite +5. Update `go.mod` — add api, go-ws, gin dependencies +6. Build UI (`cd ui && npm install && npm run build`) +7. Verify Go compilation (`go build ./...`) diff --git a/go.mod b/go.mod index 46510bb..e814dd5 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,15 @@ module forge.lthn.ai/core/go-build go 1.26.0 require ( + forge.lthn.ai/core/api v0.1.0 forge.lthn.ai/core/cli v0.1.0 forge.lthn.ai/core/go-i18n v0.1.0 forge.lthn.ai/core/go-io v0.0.3 forge.lthn.ai/core/go-log v0.0.1 + forge.lthn.ai/core/go-ws v0.1.0 github.com/Snider/Borg v0.2.0 github.com/getkin/kin-openapi v0.133.0 + github.com/gin-gonic/gin v1.11.0 github.com/leaanthony/debme v1.2.1 github.com/leaanthony/gosod v1.0.4 github.com/oasdiff/oasdiff v1.11.10 diff --git a/pkg/api/embed.go b/pkg/api/embed.go new file mode 100644 index 0000000..2270c3c --- /dev/null +++ b/pkg/api/embed.go @@ -0,0 +1,11 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +package api + +import "embed" + +// Assets holds the built UI bundle (core-build.js and related files). +// The directory is populated by running `npm run build` in the ui/ directory. +// +//go:embed all:ui/dist +var Assets embed.FS diff --git a/pkg/api/provider.go b/pkg/api/provider.go new file mode 100644 index 0000000..f107403 --- /dev/null +++ b/pkg/api/provider.go @@ -0,0 +1,582 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +// Package api provides a service provider that wraps go-build's build, +// release, and SDK subsystems as REST endpoints with WebSocket event +// streaming. +package api + +import ( + "net/http" + "os" + "path/filepath" + + "forge.lthn.ai/core/api" + "forge.lthn.ai/core/api/pkg/provider" + "forge.lthn.ai/core/go-build/pkg/build" + "forge.lthn.ai/core/go-build/pkg/build/builders" + "forge.lthn.ai/core/go-build/pkg/release" + "forge.lthn.ai/core/go-build/pkg/sdk" + "forge.lthn.ai/core/go-io" + "forge.lthn.ai/core/go-ws" + "github.com/gin-gonic/gin" +) + +// BuildProvider wraps go-build's build, release, and SDK operations as a +// service provider. It implements Provider, Streamable, Describable, and +// Renderable. +type BuildProvider struct { + hub *ws.Hub + projectDir string + medium io.Medium +} + +// compile-time interface checks +var ( + _ provider.Provider = (*BuildProvider)(nil) + _ provider.Streamable = (*BuildProvider)(nil) + _ provider.Describable = (*BuildProvider)(nil) + _ provider.Renderable = (*BuildProvider)(nil) +) + +// NewProvider creates a BuildProvider for the given project directory. +// If projectDir is empty, the current working directory is used. +// The WS hub is used to emit real-time build events; pass nil if not available. +func NewProvider(projectDir string, hub *ws.Hub) *BuildProvider { + if projectDir == "" { + projectDir = "." + } + return &BuildProvider{ + hub: hub, + projectDir: projectDir, + medium: io.Local, + } +} + +// Name implements api.RouteGroup. +func (p *BuildProvider) Name() string { return "build" } + +// BasePath implements api.RouteGroup. +func (p *BuildProvider) BasePath() string { return "/api/v1/build" } + +// Element implements provider.Renderable. +func (p *BuildProvider) Element() provider.ElementSpec { + return provider.ElementSpec{ + Tag: "core-build-panel", + Source: "/assets/core-build.js", + } +} + +// Channels implements provider.Streamable. +func (p *BuildProvider) Channels() []string { + return []string{ + "build.started", + "build.complete", + "build.failed", + "release.started", + "release.complete", + "sdk.generated", + } +} + +// RegisterRoutes implements api.RouteGroup. +func (p *BuildProvider) RegisterRoutes(rg *gin.RouterGroup) { + // Build + rg.GET("/config", p.getConfig) + rg.GET("/discover", p.discoverProject) + rg.POST("/build", p.triggerBuild) + rg.GET("/artifacts", p.listArtifacts) + + // Release + rg.GET("/release/version", p.getVersion) + rg.GET("/release/changelog", p.getChangelog) + rg.POST("/release", p.triggerRelease) + + // SDK + rg.GET("/sdk/diff", p.getSdkDiff) + rg.POST("/sdk/generate", p.generateSdk) +} + +// Describe implements api.DescribableGroup. +func (p *BuildProvider) Describe() []api.RouteDescription { + return []api.RouteDescription{ + { + Method: "GET", + Path: "/config", + Summary: "Read build configuration", + Description: "Loads and returns the .core/build.yaml from the project directory.", + Tags: []string{"build", "config"}, + }, + { + Method: "GET", + Path: "/discover", + Summary: "Detect project type", + Description: "Scans the project directory for marker files and returns detected project types.", + Tags: []string{"build", "discovery"}, + }, + { + Method: "POST", + Path: "/build", + Summary: "Trigger a build", + Description: "Runs the full build pipeline for the project, producing artifacts in dist/.", + Tags: []string{"build"}, + }, + { + Method: "GET", + Path: "/artifacts", + Summary: "List build artifacts", + Description: "Returns the contents of the dist/ directory with file sizes.", + Tags: []string{"build", "artifacts"}, + }, + { + Method: "GET", + Path: "/release/version", + Summary: "Get current version", + Description: "Determines the current version from git tags.", + Tags: []string{"release", "version"}, + }, + { + Method: "GET", + Path: "/release/changelog", + Summary: "Generate changelog", + Description: "Generates a conventional-commit changelog from git history.", + Tags: []string{"release", "changelog"}, + }, + { + Method: "POST", + Path: "/release", + Summary: "Trigger release pipeline", + Description: "Publishes pre-built artifacts from dist/ to configured targets.", + Tags: []string{"release"}, + }, + { + Method: "GET", + Path: "/sdk/diff", + Summary: "OpenAPI breaking change diff", + Description: "Compares two OpenAPI specs and reports breaking changes. Requires base and revision query parameters.", + Tags: []string{"sdk", "diff"}, + RequestBody: map[string]any{ + "type": "object", + "properties": map[string]any{ + "base": map[string]any{"type": "string", "description": "Path to the base OpenAPI spec"}, + "revision": map[string]any{"type": "string", "description": "Path to the revision OpenAPI spec"}, + }, + }, + }, + { + Method: "POST", + Path: "/sdk/generate", + Summary: "Generate SDK", + Description: "Generates SDK client libraries for configured languages from the OpenAPI spec.", + Tags: []string{"sdk"}, + RequestBody: map[string]any{ + "type": "object", + "properties": map[string]any{ + "language": map[string]any{"type": "string", "description": "Target language (typescript, python, go, php). Omit for all."}, + }, + }, + }, + } +} + +// resolveDir returns the absolute project directory. +func (p *BuildProvider) resolveDir() (string, error) { + return filepath.Abs(p.projectDir) +} + +// -- Build Handlers ----------------------------------------------------------- + +func (p *BuildProvider) getConfig(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + cfg, err := build.LoadConfig(p.medium, dir) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("config_load_failed", err.Error())) + return + } + + hasConfig := build.ConfigExists(p.medium, dir) + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "config": cfg, + "has_config": hasConfig, + "path": build.ConfigPath(dir), + })) +} + +func (p *BuildProvider) discoverProject(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + types, err := build.Discover(p.medium, dir) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("discover_failed", err.Error())) + return + } + + // Convert to string slice for JSON + typeStrings := make([]string, len(types)) + for i, t := range types { + typeStrings[i] = string(t) + } + + primary := "" + if len(types) > 0 { + primary = string(types[0]) + } + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "types": typeStrings, + "primary": primary, + "dir": dir, + })) +} + +func (p *BuildProvider) triggerBuild(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + // Load build config + cfg, err := build.LoadConfig(p.medium, dir) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("config_load_failed", err.Error())) + return + } + + // Detect project type + projectType, err := build.PrimaryType(p.medium, dir) + if err != nil || projectType == "" { + c.JSON(http.StatusBadRequest, api.Fail("no_project", "no buildable project detected")) + return + } + + // Get builder + builder, err := getBuilder(projectType) + if err != nil { + c.JSON(http.StatusBadRequest, api.Fail("unsupported_type", err.Error())) + return + } + + // Determine version + version, verr := release.DetermineVersion(dir) + if verr != nil { + version = "dev" + } + + // Build name + binaryName := cfg.Project.Binary + if binaryName == "" { + binaryName = cfg.Project.Name + } + if binaryName == "" { + binaryName = filepath.Base(dir) + } + + outputDir := filepath.Join(dir, "dist") + + buildConfig := &build.Config{ + FS: p.medium, + ProjectDir: dir, + OutputDir: outputDir, + Name: binaryName, + Version: version, + LDFlags: cfg.Build.LDFlags, + CGO: cfg.Build.CGO, + } + + targets := cfg.ToTargets() + + p.emitEvent("build.started", map[string]any{ + "project_type": string(projectType), + "targets": targets, + "version": version, + }) + + artifacts, err := builder.Build(c.Request.Context(), buildConfig, targets) + if err != nil { + p.emitEvent("build.failed", map[string]any{"error": err.Error()}) + c.JSON(http.StatusInternalServerError, api.Fail("build_failed", err.Error())) + return + } + + // Archive and checksum + archived, err := build.ArchiveAll(p.medium, artifacts) + if err != nil { + p.emitEvent("build.failed", map[string]any{"error": err.Error()}) + c.JSON(http.StatusInternalServerError, api.Fail("archive_failed", err.Error())) + return + } + + checksummed, err := build.ChecksumAll(p.medium, archived) + if err != nil { + p.emitEvent("build.failed", map[string]any{"error": err.Error()}) + c.JSON(http.StatusInternalServerError, api.Fail("checksum_failed", err.Error())) + return + } + + p.emitEvent("build.complete", map[string]any{ + "artifact_count": len(checksummed), + "version": version, + }) + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "artifacts": checksummed, + "version": version, + })) +} + +// artifactInfo holds JSON-friendly metadata about a dist/ file. +type artifactInfo struct { + Name string `json:"name"` + Path string `json:"path"` + Size int64 `json:"size"` +} + +func (p *BuildProvider) listArtifacts(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + distDir := filepath.Join(dir, "dist") + if !p.medium.IsDir(distDir) { + c.JSON(http.StatusOK, api.OK(map[string]any{ + "artifacts": []artifactInfo{}, + "exists": false, + })) + return + } + + entries, err := p.medium.List(distDir) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("list_failed", err.Error())) + return + } + + var artifacts []artifactInfo + for _, entry := range entries { + if entry.IsDir() { + continue + } + info, err := entry.Info() + if err != nil { + continue + } + artifacts = append(artifacts, artifactInfo{ + Name: entry.Name(), + Path: filepath.Join(distDir, entry.Name()), + Size: info.Size(), + }) + } + + if artifacts == nil { + artifacts = []artifactInfo{} + } + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "artifacts": artifacts, + "exists": true, + })) +} + +// -- Release Handlers --------------------------------------------------------- + +func (p *BuildProvider) getVersion(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + version, err := release.DetermineVersion(dir) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("version_failed", err.Error())) + return + } + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "version": version, + })) +} + +func (p *BuildProvider) getChangelog(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + // Optional query params for ref range + fromRef := c.Query("from") + toRef := c.Query("to") + + changelog, err := release.Generate(dir, fromRef, toRef) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("changelog_failed", err.Error())) + return + } + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "changelog": changelog, + })) +} + +func (p *BuildProvider) triggerRelease(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + cfg, err := release.LoadConfig(dir) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("config_load_failed", err.Error())) + return + } + + // Parse optional dry_run parameter + dryRun := c.Query("dry_run") == "true" + + p.emitEvent("release.started", map[string]any{ + "dry_run": dryRun, + }) + + rel, err := release.Publish(c.Request.Context(), cfg, dryRun) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("release_failed", err.Error())) + return + } + + p.emitEvent("release.complete", map[string]any{ + "version": rel.Version, + "artifact_count": len(rel.Artifacts), + "dry_run": dryRun, + }) + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "version": rel.Version, + "artifacts": rel.Artifacts, + "changelog": rel.Changelog, + "dry_run": dryRun, + })) +} + +// -- SDK Handlers ------------------------------------------------------------- + +func (p *BuildProvider) getSdkDiff(c *gin.Context) { + basePath := c.Query("base") + revisionPath := c.Query("revision") + + if basePath == "" || revisionPath == "" { + c.JSON(http.StatusBadRequest, api.Fail("missing_params", "base and revision query parameters are required")) + return + } + + result, err := sdk.Diff(basePath, revisionPath) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("diff_failed", err.Error())) + return + } + + c.JSON(http.StatusOK, api.OK(result)) +} + +type sdkGenerateRequest struct { + Language string `json:"language"` +} + +func (p *BuildProvider) generateSdk(c *gin.Context) { + dir, err := p.resolveDir() + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("resolve_failed", err.Error())) + return + } + + var req sdkGenerateRequest + if err := c.ShouldBindJSON(&req); err != nil { + // No body is fine — generate all languages + req.Language = "" + } + + // Load SDK config from release config + relCfg, err := release.LoadConfig(dir) + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("config_load_failed", err.Error())) + return + } + + var sdkCfg *sdk.Config + if relCfg.SDK != nil { + sdkCfg = &sdk.Config{ + Spec: relCfg.SDK.Spec, + Languages: relCfg.SDK.Languages, + Output: relCfg.SDK.Output, + Package: sdk.PackageConfig{ + Name: relCfg.SDK.Package.Name, + Version: relCfg.SDK.Package.Version, + }, + Diff: sdk.DiffConfig{ + Enabled: relCfg.SDK.Diff.Enabled, + FailOnBreaking: relCfg.SDK.Diff.FailOnBreaking, + }, + } + } + + s := sdk.New(dir, sdkCfg) + + ctx := c.Request.Context() + if req.Language != "" { + err = s.GenerateLanguage(ctx, req.Language) + } else { + err = s.Generate(ctx) + } + + if err != nil { + c.JSON(http.StatusInternalServerError, api.Fail("sdk_generate_failed", err.Error())) + return + } + + p.emitEvent("sdk.generated", map[string]any{ + "language": req.Language, + }) + + c.JSON(http.StatusOK, api.OK(map[string]any{ + "generated": true, + "language": req.Language, + })) +} + +// -- Internal Helpers --------------------------------------------------------- + +// getBuilder returns the appropriate builder for the project type. +func getBuilder(projectType build.ProjectType) (build.Builder, error) { + switch projectType { + case build.ProjectTypeWails: + return builders.NewWailsBuilder(), nil + case build.ProjectTypeGo: + return builders.NewGoBuilder(), nil + default: + return nil, os.ErrNotExist + } +} + +// emitEvent sends a WS event if the hub is available. +func (p *BuildProvider) emitEvent(channel string, data any) { + if p.hub == nil { + return + } + _ = p.hub.SendToChannel(channel, ws.Message{ + Type: ws.TypeEvent, + Data: data, + }) +} diff --git a/pkg/api/provider_test.go b/pkg/api/provider_test.go new file mode 100644 index 0000000..dccad3c --- /dev/null +++ b/pkg/api/provider_test.go @@ -0,0 +1,77 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildProvider_Good_Identity(t *testing.T) { + p := NewProvider(".", nil) + + assert.Equal(t, "build", p.Name()) + assert.Equal(t, "/api/v1/build", p.BasePath()) +} + +func TestBuildProvider_Good_Element(t *testing.T) { + p := NewProvider(".", nil) + el := p.Element() + + assert.Equal(t, "core-build-panel", el.Tag) + assert.Equal(t, "/assets/core-build.js", el.Source) +} + +func TestBuildProvider_Good_Channels(t *testing.T) { + p := NewProvider(".", nil) + channels := p.Channels() + + assert.Contains(t, channels, "build.started") + assert.Contains(t, channels, "build.complete") + assert.Contains(t, channels, "build.failed") + assert.Contains(t, channels, "release.started") + assert.Contains(t, channels, "release.complete") + assert.Contains(t, channels, "sdk.generated") + assert.Len(t, channels, 6) +} + +func TestBuildProvider_Good_Describe(t *testing.T) { + p := NewProvider(".", nil) + routes := p.Describe() + + // Should have 9 endpoint descriptions + assert.Len(t, routes, 9) + + // Verify key routes exist + paths := make(map[string]string) + for _, r := range routes { + paths[r.Path] = r.Method + } + + assert.Equal(t, "GET", paths["/config"]) + assert.Equal(t, "GET", paths["/discover"]) + assert.Equal(t, "POST", paths["/build"]) + assert.Equal(t, "GET", paths["/artifacts"]) + assert.Equal(t, "GET", paths["/release/version"]) + assert.Equal(t, "GET", paths["/release/changelog"]) + assert.Equal(t, "POST", paths["/release"]) + assert.Equal(t, "GET", paths["/sdk/diff"]) + assert.Equal(t, "POST", paths["/sdk/generate"]) +} + +func TestBuildProvider_Good_DefaultProjectDir(t *testing.T) { + p := NewProvider("", nil) + assert.Equal(t, ".", p.projectDir) +} + +func TestBuildProvider_Good_CustomProjectDir(t *testing.T) { + p := NewProvider("/tmp/myproject", nil) + assert.Equal(t, "/tmp/myproject", p.projectDir) +} + +func TestBuildProvider_Good_NilHub(t *testing.T) { + p := NewProvider(".", nil) + // emitEvent should not panic with nil hub + p.emitEvent("build.started", map[string]any{"test": true}) +} diff --git a/pkg/api/ui/dist/core-build.js b/pkg/api/ui/dist/core-build.js new file mode 100644 index 0000000..74d281b --- /dev/null +++ b/pkg/api/ui/dist/core-build.js @@ -0,0 +1,2048 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const J = globalThis, re = J.ShadowRoot && (J.ShadyCSS === void 0 || J.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, oe = Symbol(), de = /* @__PURE__ */ new WeakMap(); +let _e = class { + constructor(e, t, r) { + if (this._$cssResult$ = !0, r !== oe) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); + this.cssText = e, this.t = t; + } + get styleSheet() { + let e = this.o; + const t = this.t; + if (re && e === void 0) { + const r = t !== void 0 && t.length === 1; + r && (e = de.get(t)), e === void 0 && ((this.o = e = new CSSStyleSheet()).replaceSync(this.cssText), r && de.set(t, e)); + } + return e; + } + toString() { + return this.cssText; + } +}; +const Ee = (s) => new _e(typeof s == "string" ? s : s + "", void 0, oe), F = (s, ...e) => { + const t = s.length === 1 ? s[0] : e.reduce((r, i, o) => r + ((n) => { + if (n._$cssResult$ === !0) return n.cssText; + if (typeof n == "number") return n; + throw Error("Value passed to 'css' function must be a 'css' function result: " + n + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); + })(i) + s[o + 1], s[0]); + return new _e(t, s, oe); +}, Ce = (s, e) => { + if (re) s.adoptedStyleSheets = e.map((t) => t instanceof CSSStyleSheet ? t : t.styleSheet); + else for (const t of e) { + const r = document.createElement("style"), i = J.litNonce; + i !== void 0 && r.setAttribute("nonce", i), r.textContent = t.cssText, s.appendChild(r); + } +}, ce = re ? (s) => s : (s) => s instanceof CSSStyleSheet ? ((e) => { + let t = ""; + for (const r of e.cssRules) t += r.cssText; + return Ee(t); +})(s) : s; +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const { is: Pe, defineProperty: ke, getOwnPropertyDescriptor: Ue, getOwnPropertyNames: Re, getOwnPropertySymbols: Oe, getPrototypeOf: De } = Object, E = globalThis, he = E.trustedTypes, ze = he ? he.emptyScript : "", ee = E.reactiveElementPolyfillSupport, L = (s, e) => s, Z = { toAttribute(s, e) { + switch (e) { + case Boolean: + s = s ? ze : null; + break; + case Object: + case Array: + s = s == null ? s : JSON.stringify(s); + } + return s; +}, fromAttribute(s, e) { + let t = s; + switch (e) { + case Boolean: + t = s !== null; + break; + case Number: + t = s === null ? null : Number(s); + break; + case Object: + case Array: + try { + t = JSON.parse(s); + } catch { + t = null; + } + } + return t; +} }, ne = (s, e) => !Pe(s, e), fe = { attribute: !0, type: String, converter: Z, reflect: !1, useDefault: !1, hasChanged: ne }; +Symbol.metadata ?? (Symbol.metadata = Symbol("metadata")), E.litPropertyMetadata ?? (E.litPropertyMetadata = /* @__PURE__ */ new WeakMap()); +let T = class extends HTMLElement { + static addInitializer(e) { + this._$Ei(), (this.l ?? (this.l = [])).push(e); + } + static get observedAttributes() { + return this.finalize(), this._$Eh && [...this._$Eh.keys()]; + } + static createProperty(e, t = fe) { + if (t.state && (t.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(e) && ((t = Object.create(t)).wrapped = !0), this.elementProperties.set(e, t), !t.noAccessor) { + const r = Symbol(), i = this.getPropertyDescriptor(e, r, t); + i !== void 0 && ke(this.prototype, e, i); + } + } + static getPropertyDescriptor(e, t, r) { + const { get: i, set: o } = Ue(this.prototype, e) ?? { get() { + return this[t]; + }, set(n) { + this[t] = n; + } }; + return { get: i, set(n) { + const d = i == null ? void 0 : i.call(this); + o == null || o.call(this, n), this.requestUpdate(e, d, r); + }, configurable: !0, enumerable: !0 }; + } + static getPropertyOptions(e) { + return this.elementProperties.get(e) ?? fe; + } + static _$Ei() { + if (this.hasOwnProperty(L("elementProperties"))) return; + const e = De(this); + e.finalize(), e.l !== void 0 && (this.l = [...e.l]), this.elementProperties = new Map(e.elementProperties); + } + static finalize() { + if (this.hasOwnProperty(L("finalized"))) return; + if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(L("properties"))) { + const t = this.properties, r = [...Re(t), ...Oe(t)]; + for (const i of r) this.createProperty(i, t[i]); + } + const e = this[Symbol.metadata]; + if (e !== null) { + const t = litPropertyMetadata.get(e); + if (t !== void 0) for (const [r, i] of t) this.elementProperties.set(r, i); + } + this._$Eh = /* @__PURE__ */ new Map(); + for (const [t, r] of this.elementProperties) { + const i = this._$Eu(t, r); + i !== void 0 && this._$Eh.set(i, t); + } + this.elementStyles = this.finalizeStyles(this.styles); + } + static finalizeStyles(e) { + const t = []; + if (Array.isArray(e)) { + const r = new Set(e.flat(1 / 0).reverse()); + for (const i of r) t.unshift(ce(i)); + } else e !== void 0 && t.push(ce(e)); + return t; + } + static _$Eu(e, t) { + const r = t.attribute; + return r === !1 ? void 0 : typeof r == "string" ? r : typeof e == "string" ? e.toLowerCase() : void 0; + } + constructor() { + super(), this._$Ep = void 0, this.isUpdatePending = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev(); + } + _$Ev() { + var e; + this._$ES = new Promise((t) => this.enableUpdating = t), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), (e = this.constructor.l) == null || e.forEach((t) => t(this)); + } + addController(e) { + var t; + (this._$EO ?? (this._$EO = /* @__PURE__ */ new Set())).add(e), this.renderRoot !== void 0 && this.isConnected && ((t = e.hostConnected) == null || t.call(e)); + } + removeController(e) { + var t; + (t = this._$EO) == null || t.delete(e); + } + _$E_() { + const e = /* @__PURE__ */ new Map(), t = this.constructor.elementProperties; + for (const r of t.keys()) this.hasOwnProperty(r) && (e.set(r, this[r]), delete this[r]); + e.size > 0 && (this._$Ep = e); + } + createRenderRoot() { + const e = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); + return Ce(e, this.constructor.elementStyles), e; + } + connectedCallback() { + var e; + this.renderRoot ?? (this.renderRoot = this.createRenderRoot()), this.enableUpdating(!0), (e = this._$EO) == null || e.forEach((t) => { + var r; + return (r = t.hostConnected) == null ? void 0 : r.call(t); + }); + } + enableUpdating(e) { + } + disconnectedCallback() { + var e; + (e = this._$EO) == null || e.forEach((t) => { + var r; + return (r = t.hostDisconnected) == null ? void 0 : r.call(t); + }); + } + attributeChangedCallback(e, t, r) { + this._$AK(e, r); + } + _$ET(e, t) { + var o; + const r = this.constructor.elementProperties.get(e), i = this.constructor._$Eu(e, r); + if (i !== void 0 && r.reflect === !0) { + const n = (((o = r.converter) == null ? void 0 : o.toAttribute) !== void 0 ? r.converter : Z).toAttribute(t, r.type); + this._$Em = e, n == null ? this.removeAttribute(i) : this.setAttribute(i, n), this._$Em = null; + } + } + _$AK(e, t) { + var o, n; + const r = this.constructor, i = r._$Eh.get(e); + if (i !== void 0 && this._$Em !== i) { + const d = r.getPropertyOptions(i), a = typeof d.converter == "function" ? { fromAttribute: d.converter } : ((o = d.converter) == null ? void 0 : o.fromAttribute) !== void 0 ? d.converter : Z; + this._$Em = i; + const u = a.fromAttribute(t, d.type); + this[i] = u ?? ((n = this._$Ej) == null ? void 0 : n.get(i)) ?? u, this._$Em = null; + } + } + requestUpdate(e, t, r, i = !1, o) { + var n; + if (e !== void 0) { + const d = this.constructor; + if (i === !1 && (o = this[e]), r ?? (r = d.getPropertyOptions(e)), !((r.hasChanged ?? ne)(o, t) || r.useDefault && r.reflect && o === ((n = this._$Ej) == null ? void 0 : n.get(e)) && !this.hasAttribute(d._$Eu(e, r)))) return; + this.C(e, t, r); + } + this.isUpdatePending === !1 && (this._$ES = this._$EP()); + } + C(e, t, { useDefault: r, reflect: i, wrapped: o }, n) { + r && !(this._$Ej ?? (this._$Ej = /* @__PURE__ */ new Map())).has(e) && (this._$Ej.set(e, n ?? t ?? this[e]), o !== !0 || n !== void 0) || (this._$AL.has(e) || (this.hasUpdated || r || (t = void 0), this._$AL.set(e, t)), i === !0 && this._$Em !== e && (this._$Eq ?? (this._$Eq = /* @__PURE__ */ new Set())).add(e)); + } + async _$EP() { + this.isUpdatePending = !0; + try { + await this._$ES; + } catch (t) { + Promise.reject(t); + } + const e = this.scheduleUpdate(); + return e != null && await e, !this.isUpdatePending; + } + scheduleUpdate() { + return this.performUpdate(); + } + performUpdate() { + var r; + if (!this.isUpdatePending) return; + if (!this.hasUpdated) { + if (this.renderRoot ?? (this.renderRoot = this.createRenderRoot()), this._$Ep) { + for (const [o, n] of this._$Ep) this[o] = n; + this._$Ep = void 0; + } + const i = this.constructor.elementProperties; + if (i.size > 0) for (const [o, n] of i) { + const { wrapped: d } = n, a = this[o]; + d !== !0 || this._$AL.has(o) || a === void 0 || this.C(o, void 0, n, a); + } + } + let e = !1; + const t = this._$AL; + try { + e = this.shouldUpdate(t), e ? (this.willUpdate(t), (r = this._$EO) == null || r.forEach((i) => { + var o; + return (o = i.hostUpdate) == null ? void 0 : o.call(i); + }), this.update(t)) : this._$EM(); + } catch (i) { + throw e = !1, this._$EM(), i; + } + e && this._$AE(t); + } + willUpdate(e) { + } + _$AE(e) { + var t; + (t = this._$EO) == null || t.forEach((r) => { + var i; + return (i = r.hostUpdated) == null ? void 0 : i.call(r); + }), this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(e)), this.updated(e); + } + _$EM() { + this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = !1; + } + get updateComplete() { + return this.getUpdateComplete(); + } + getUpdateComplete() { + return this._$ES; + } + shouldUpdate(e) { + return !0; + } + update(e) { + this._$Eq && (this._$Eq = this._$Eq.forEach((t) => this._$ET(t, this[t]))), this._$EM(); + } + updated(e) { + } + firstUpdated(e) { + } +}; +T.elementStyles = [], T.shadowRootOptions = { mode: "open" }, T[L("elementProperties")] = /* @__PURE__ */ new Map(), T[L("finalized")] = /* @__PURE__ */ new Map(), ee == null || ee({ ReactiveElement: T }), (E.reactiveElementVersions ?? (E.reactiveElementVersions = [])).push("2.1.2"); +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const q = globalThis, ue = (s) => s, Q = q.trustedTypes, pe = Q ? Q.createPolicy("lit-html", { createHTML: (s) => s }) : void 0, we = "$lit$", S = `lit$${Math.random().toFixed(9).slice(2)}$`, Ae = "?" + S, Te = `<${Ae}>`, D = document, W = () => D.createComment(""), I = (s) => s === null || typeof s != "object" && typeof s != "function", ae = Array.isArray, Be = (s) => ae(s) || typeof (s == null ? void 0 : s[Symbol.iterator]) == "function", te = `[ +\f\r]`, M = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ge = /-->/g, me = />/g, U = RegExp(`>|${te}(?:([^\\s"'>=/]+)(${te}*=${te}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), be = /'/g, ve = /"/g, xe = /^(?:script|style|textarea|title)$/i, je = (s) => (e, ...t) => ({ _$litType$: s, strings: e, values: t }), l = je(1), B = Symbol.for("lit-noChange"), c = Symbol.for("lit-nothing"), $e = /* @__PURE__ */ new WeakMap(), R = D.createTreeWalker(D, 129); +function Se(s, e) { + if (!ae(s) || !s.hasOwnProperty("raw")) throw Error("invalid template strings array"); + return pe !== void 0 ? pe.createHTML(e) : e; +} +const He = (s, e) => { + const t = s.length - 1, r = []; + let i, o = e === 2 ? "" : e === 3 ? "" : "", n = M; + for (let d = 0; d < t; d++) { + const a = s[d]; + let u, p, f = -1, g = 0; + for (; g < a.length && (n.lastIndex = g, p = n.exec(a), p !== null); ) g = n.lastIndex, n === M ? p[1] === "!--" ? n = ge : p[1] !== void 0 ? n = me : p[2] !== void 0 ? (xe.test(p[2]) && (i = RegExp("" ? (n = i ?? M, f = -1) : p[1] === void 0 ? f = -2 : (f = n.lastIndex - p[2].length, u = p[1], n = p[3] === void 0 ? U : p[3] === '"' ? ve : be) : n === ve || n === be ? n = U : n === ge || n === me ? n = M : (n = U, i = void 0); + const b = n === U && s[d + 1].startsWith("/>") ? " " : ""; + o += n === M ? a + Te : f >= 0 ? (r.push(u), a.slice(0, f) + we + a.slice(f) + S + b) : a + S + (f === -2 ? d : b); + } + return [Se(s, o + (s[t] || "") + (e === 2 ? "" : e === 3 ? "" : "")), r]; +}; +class G { + constructor({ strings: e, _$litType$: t }, r) { + let i; + this.parts = []; + let o = 0, n = 0; + const d = e.length - 1, a = this.parts, [u, p] = He(e, t); + if (this.el = G.createElement(u, r), R.currentNode = this.el.content, t === 2 || t === 3) { + const f = this.el.content.firstChild; + f.replaceWith(...f.childNodes); + } + for (; (i = R.nextNode()) !== null && a.length < d; ) { + if (i.nodeType === 1) { + if (i.hasAttributes()) for (const f of i.getAttributeNames()) if (f.endsWith(we)) { + const g = p[n++], b = i.getAttribute(f).split(S), _ = /([.?@])?(.*)/.exec(g); + a.push({ type: 1, index: o, name: _[2], strings: b, ctor: _[1] === "." ? Me : _[1] === "?" ? Le : _[1] === "@" ? qe : X }), i.removeAttribute(f); + } else f.startsWith(S) && (a.push({ type: 6, index: o }), i.removeAttribute(f)); + if (xe.test(i.tagName)) { + const f = i.textContent.split(S), g = f.length - 1; + if (g > 0) { + i.textContent = Q ? Q.emptyScript : ""; + for (let b = 0; b < g; b++) i.append(f[b], W()), R.nextNode(), a.push({ type: 2, index: ++o }); + i.append(f[g], W()); + } + } + } else if (i.nodeType === 8) if (i.data === Ae) a.push({ type: 2, index: o }); + else { + let f = -1; + for (; (f = i.data.indexOf(S, f + 1)) !== -1; ) a.push({ type: 7, index: o }), f += S.length - 1; + } + o++; + } + } + static createElement(e, t) { + const r = D.createElement("template"); + return r.innerHTML = e, r; + } +} +function j(s, e, t = s, r) { + var n, d; + if (e === B) return e; + let i = r !== void 0 ? (n = t._$Co) == null ? void 0 : n[r] : t._$Cl; + const o = I(e) ? void 0 : e._$litDirective$; + return (i == null ? void 0 : i.constructor) !== o && ((d = i == null ? void 0 : i._$AO) == null || d.call(i, !1), o === void 0 ? i = void 0 : (i = new o(s), i._$AT(s, t, r)), r !== void 0 ? (t._$Co ?? (t._$Co = []))[r] = i : t._$Cl = i), i !== void 0 && (e = j(s, i._$AS(s, e.values), i, r)), e; +} +class Ne { + constructor(e, t) { + this._$AV = [], this._$AN = void 0, this._$AD = e, this._$AM = t; + } + get parentNode() { + return this._$AM.parentNode; + } + get _$AU() { + return this._$AM._$AU; + } + u(e) { + const { el: { content: t }, parts: r } = this._$AD, i = ((e == null ? void 0 : e.creationScope) ?? D).importNode(t, !0); + R.currentNode = i; + let o = R.nextNode(), n = 0, d = 0, a = r[0]; + for (; a !== void 0; ) { + if (n === a.index) { + let u; + a.type === 2 ? u = new V(o, o.nextSibling, this, e) : a.type === 1 ? u = new a.ctor(o, a.name, a.strings, this, e) : a.type === 6 && (u = new We(o, this, e)), this._$AV.push(u), a = r[++d]; + } + n !== (a == null ? void 0 : a.index) && (o = R.nextNode(), n++); + } + return R.currentNode = D, i; + } + p(e) { + let t = 0; + for (const r of this._$AV) r !== void 0 && (r.strings !== void 0 ? (r._$AI(e, r, t), t += r.strings.length - 2) : r._$AI(e[t])), t++; + } +} +class V { + get _$AU() { + var e; + return ((e = this._$AM) == null ? void 0 : e._$AU) ?? this._$Cv; + } + constructor(e, t, r, i) { + this.type = 2, this._$AH = c, this._$AN = void 0, this._$AA = e, this._$AB = t, this._$AM = r, this.options = i, this._$Cv = (i == null ? void 0 : i.isConnected) ?? !0; + } + get parentNode() { + let e = this._$AA.parentNode; + const t = this._$AM; + return t !== void 0 && (e == null ? void 0 : e.nodeType) === 11 && (e = t.parentNode), e; + } + get startNode() { + return this._$AA; + } + get endNode() { + return this._$AB; + } + _$AI(e, t = this) { + e = j(this, e, t), I(e) ? e === c || e == null || e === "" ? (this._$AH !== c && this._$AR(), this._$AH = c) : e !== this._$AH && e !== B && this._(e) : e._$litType$ !== void 0 ? this.$(e) : e.nodeType !== void 0 ? this.T(e) : Be(e) ? this.k(e) : this._(e); + } + O(e) { + return this._$AA.parentNode.insertBefore(e, this._$AB); + } + T(e) { + this._$AH !== e && (this._$AR(), this._$AH = this.O(e)); + } + _(e) { + this._$AH !== c && I(this._$AH) ? this._$AA.nextSibling.data = e : this.T(D.createTextNode(e)), this._$AH = e; + } + $(e) { + var o; + const { values: t, _$litType$: r } = e, i = typeof r == "number" ? this._$AC(e) : (r.el === void 0 && (r.el = G.createElement(Se(r.h, r.h[0]), this.options)), r); + if (((o = this._$AH) == null ? void 0 : o._$AD) === i) this._$AH.p(t); + else { + const n = new Ne(i, this), d = n.u(this.options); + n.p(t), this.T(d), this._$AH = n; + } + } + _$AC(e) { + let t = $e.get(e.strings); + return t === void 0 && $e.set(e.strings, t = new G(e)), t; + } + k(e) { + ae(this._$AH) || (this._$AH = [], this._$AR()); + const t = this._$AH; + let r, i = 0; + for (const o of e) i === t.length ? t.push(r = new V(this.O(W()), this.O(W()), this, this.options)) : r = t[i], r._$AI(o), i++; + i < t.length && (this._$AR(r && r._$AB.nextSibling, i), t.length = i); + } + _$AR(e = this._$AA.nextSibling, t) { + var r; + for ((r = this._$AP) == null ? void 0 : r.call(this, !1, !0, t); e !== this._$AB; ) { + const i = ue(e).nextSibling; + ue(e).remove(), e = i; + } + } + setConnected(e) { + var t; + this._$AM === void 0 && (this._$Cv = e, (t = this._$AP) == null || t.call(this, e)); + } +} +class X { + get tagName() { + return this.element.tagName; + } + get _$AU() { + return this._$AM._$AU; + } + constructor(e, t, r, i, o) { + this.type = 1, this._$AH = c, this._$AN = void 0, this.element = e, this.name = t, this._$AM = i, this.options = o, r.length > 2 || r[0] !== "" || r[1] !== "" ? (this._$AH = Array(r.length - 1).fill(new String()), this.strings = r) : this._$AH = c; + } + _$AI(e, t = this, r, i) { + const o = this.strings; + let n = !1; + if (o === void 0) e = j(this, e, t, 0), n = !I(e) || e !== this._$AH && e !== B, n && (this._$AH = e); + else { + const d = e; + let a, u; + for (e = o[0], a = 0; a < o.length - 1; a++) u = j(this, d[r + a], t, a), u === B && (u = this._$AH[a]), n || (n = !I(u) || u !== this._$AH[a]), u === c ? e = c : e !== c && (e += (u ?? "") + o[a + 1]), this._$AH[a] = u; + } + n && !i && this.j(e); + } + j(e) { + e === c ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, e ?? ""); + } +} +class Me extends X { + constructor() { + super(...arguments), this.type = 3; + } + j(e) { + this.element[this.name] = e === c ? void 0 : e; + } +} +class Le extends X { + constructor() { + super(...arguments), this.type = 4; + } + j(e) { + this.element.toggleAttribute(this.name, !!e && e !== c); + } +} +class qe extends X { + constructor(e, t, r, i, o) { + super(e, t, r, i, o), this.type = 5; + } + _$AI(e, t = this) { + if ((e = j(this, e, t, 0) ?? c) === B) return; + const r = this._$AH, i = e === c && r !== c || e.capture !== r.capture || e.once !== r.once || e.passive !== r.passive, o = e !== c && (r === c || i); + i && this.element.removeEventListener(this.name, this, r), o && this.element.addEventListener(this.name, this, e), this._$AH = e; + } + handleEvent(e) { + var t; + typeof this._$AH == "function" ? this._$AH.call(((t = this.options) == null ? void 0 : t.host) ?? this.element, e) : this._$AH.handleEvent(e); + } +} +class We { + constructor(e, t, r) { + this.element = e, this.type = 6, this._$AN = void 0, this._$AM = t, this.options = r; + } + get _$AU() { + return this._$AM._$AU; + } + _$AI(e) { + j(this, e); + } +} +const se = q.litHtmlPolyfillSupport; +se == null || se(G, V), (q.litHtmlVersions ?? (q.litHtmlVersions = [])).push("3.3.2"); +const Ie = (s, e, t) => { + const r = (t == null ? void 0 : t.renderBefore) ?? e; + let i = r._$litPart$; + if (i === void 0) { + const o = (t == null ? void 0 : t.renderBefore) ?? null; + r._$litPart$ = i = new V(e.insertBefore(W(), o), o, void 0, t ?? {}); + } + return i._$AI(s), i; +}; +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const O = globalThis; +class w extends T { + constructor() { + super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; + } + createRenderRoot() { + var t; + const e = super.createRenderRoot(); + return (t = this.renderOptions).renderBefore ?? (t.renderBefore = e.firstChild), e; + } + update(e) { + const t = this.render(); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(e), this._$Do = Ie(t, this.renderRoot, this.renderOptions); + } + connectedCallback() { + var e; + super.connectedCallback(), (e = this._$Do) == null || e.setConnected(!0); + } + disconnectedCallback() { + var e; + super.disconnectedCallback(), (e = this._$Do) == null || e.setConnected(!1); + } + render() { + return B; + } +} +var ye; +w._$litElement$ = !0, w.finalized = !0, (ye = O.litElementHydrateSupport) == null || ye.call(O, { LitElement: w }); +const ie = O.litElementPolyfillSupport; +ie == null || ie({ LitElement: w }); +(O.litElementVersions ?? (O.litElementVersions = [])).push("4.2.2"); +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const K = (s) => (e, t) => { + t !== void 0 ? t.addInitializer(() => { + customElements.define(s, e); + }) : customElements.define(s, e); +}; +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const Ge = { attribute: !0, type: String, converter: Z, reflect: !1, hasChanged: ne }, Fe = (s = Ge, e, t) => { + const { kind: r, metadata: i } = t; + let o = globalThis.litPropertyMetadata.get(i); + if (o === void 0 && globalThis.litPropertyMetadata.set(i, o = /* @__PURE__ */ new Map()), r === "setter" && ((s = Object.create(s)).wrapped = !0), o.set(t.name, s), r === "accessor") { + const { name: n } = t; + return { set(d) { + const a = e.get.call(this); + e.set.call(this, d), this.requestUpdate(n, a, s, !0, d); + }, init(d) { + return d !== void 0 && this.C(n, void 0, s, d), d; + } }; + } + if (r === "setter") { + const { name: n } = t; + return function(d) { + const a = this[n]; + e.call(this, d), this.requestUpdate(n, a, s, !0, d); + }; + } + throw Error("Unsupported decorator location: " + r); +}; +function z(s) { + return (e, t) => typeof t == "object" ? Fe(s, e, t) : ((r, i, o) => { + const n = i.hasOwnProperty(o); + return i.constructor.createProperty(o, r), n ? Object.getOwnPropertyDescriptor(i, o) : void 0; + })(s, e, t); +} +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +function h(s) { + return z({ ...s, state: !0, attribute: !1 }); +} +function Ve(s, e) { + const t = new WebSocket(s); + return t.onmessage = (r) => { + var i, o, n, d, a, u, p, f, g, b, _, le; + try { + const k = JSON.parse(r.data); + ((o = (i = k.type) == null ? void 0 : i.startsWith) != null && o.call(i, "build.") || (d = (n = k.type) == null ? void 0 : n.startsWith) != null && d.call(n, "release.") || (u = (a = k.type) == null ? void 0 : a.startsWith) != null && u.call(a, "sdk.") || (f = (p = k.channel) == null ? void 0 : p.startsWith) != null && f.call(p, "build.") || (b = (g = k.channel) == null ? void 0 : g.startsWith) != null && b.call(g, "release.") || (le = (_ = k.channel) == null ? void 0 : _.startsWith) != null && le.call(_, "sdk.")) && e(k); + } catch { + } + }, t; +} +class Y { + constructor(e = "") { + this.baseUrl = e; + } + get base() { + return `${this.baseUrl}/api/v1/build`; + } + async request(e, t) { + var o; + const i = await (await fetch(`${this.base}${e}`, t)).json(); + if (!i.success) + throw new Error(((o = i.error) == null ? void 0 : o.message) ?? "Request failed"); + return i.data; + } + // -- Build ------------------------------------------------------------------ + config() { + return this.request("/config"); + } + discover() { + return this.request("/discover"); + } + build() { + return this.request("/build", { method: "POST" }); + } + artifacts() { + return this.request("/artifacts"); + } + // -- Release ---------------------------------------------------------------- + version() { + return this.request("/release/version"); + } + changelog(e, t) { + const r = new URLSearchParams(); + e && r.set("from", e), t && r.set("to", t); + const i = r.toString(); + return this.request(`/release/changelog${i ? `?${i}` : ""}`); + } + release(e = !1) { + const t = e ? "?dry_run=true" : ""; + return this.request(`/release${t}`, { method: "POST" }); + } + // -- SDK -------------------------------------------------------------------- + sdkDiff(e, t) { + const r = new URLSearchParams({ base: e, revision: t }); + return this.request(`/sdk/diff?${r.toString()}`); + } + sdkGenerate(e) { + const t = e ? JSON.stringify({ language: e }) : void 0; + return this.request("/sdk/generate", { + method: "POST", + headers: t ? { "Content-Type": "application/json" } : void 0, + body: t + }); + } +} +var Ke = Object.defineProperty, Je = Object.getOwnPropertyDescriptor, H = (s, e, t, r) => { + for (var i = r > 1 ? void 0 : r ? Je(e, t) : e, o = s.length - 1, n; o >= 0; o--) + (n = s[o]) && (i = (r ? n(e, t, i) : n(i)) || i); + return r && i && Ke(e, t, i), i; +}; +let C = class extends w { + constructor() { + super(...arguments), this.apiUrl = "", this.configData = null, this.discoverData = null, this.loading = !0, this.error = ""; + } + connectedCallback() { + super.connectedCallback(), this.api = new Y(this.apiUrl), this.reload(); + } + async reload() { + this.loading = !0, this.error = ""; + try { + const [s, e] = await Promise.all([ + this.api.config(), + this.api.discover() + ]); + this.configData = s, this.discoverData = e; + } catch (s) { + this.error = s.message ?? "Failed to load configuration"; + } finally { + this.loading = !1; + } + } + render() { + if (this.loading) + return l`
Loading configuration\u2026
`; + if (this.error) + return l`
${this.error}
`; + if (!this.configData) + return l`
No configuration available.
`; + const s = this.configData.config, e = this.discoverData; + return l` + +
+
Project Detection
+
+ Config file + + ${this.configData.has_config ? "Present" : "Using defaults"} + +
+ ${e ? l` +
+ Primary type + ${e.primary || "none"} +
+ ${e.types.length > 1 ? l` +
+ Detected types + ${e.types.join(", ")} +
+ ` : c} +
+ Directory + ${e.dir} +
+ ` : c} +
+ + +
+
Project
+ ${s.project.name ? l` +
+ Name + ${s.project.name} +
+ ` : c} + ${s.project.binary ? l` +
+ Binary + ${s.project.binary} +
+ ` : c} +
+ Main + ${s.project.main} +
+
+ + +
+
Build Settings
+ ${s.build.type ? l` +
+ Type override + ${s.build.type} +
+ ` : c} +
+ CGO + ${s.build.cgo ? "Enabled" : "Disabled"} +
+ ${s.build.flags && s.build.flags.length > 0 ? l` +
+ Flags +
+ ${s.build.flags.map((t) => l`${t}`)} +
+
+ ` : c} + ${s.build.ldflags && s.build.ldflags.length > 0 ? l` +
+ LD flags +
+ ${s.build.ldflags.map((t) => l`${t}`)} +
+
+ ` : c} +
+ + +
+
Targets
+
+ ${s.targets.map( + (t) => l`${t.os}/${t.arch}` + )} +
+
+ `; + } +}; +C.styles = F` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .section { + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + padding: 1rem; + background: #fff; + margin-bottom: 1rem; + } + + .section-title { + font-size: 0.75rem; + font-weight: 700; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + margin-bottom: 0.75rem; + } + + .field { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 0.375rem 0; + border-bottom: 1px solid #f3f4f6; + } + + .field:last-child { + border-bottom: none; + } + + .field-label { + font-size: 0.8125rem; + font-weight: 500; + colour: #374151; + } + + .field-value { + font-size: 0.8125rem; + font-family: monospace; + colour: #6b7280; + } + + .badge { + display: inline-block; + font-size: 0.6875rem; + font-weight: 600; + padding: 0.125rem 0.5rem; + border-radius: 1rem; + } + + .badge.present { + background: #dcfce7; + colour: #166534; + } + + .badge.absent { + background: #fef3c7; + colour: #92400e; + } + + .badge.type-go { + background: #dbeafe; + colour: #1e40af; + } + + .badge.type-wails { + background: #f3e8ff; + colour: #6b21a8; + } + + .badge.type-node { + background: #dcfce7; + colour: #166534; + } + + .badge.type-php { + background: #fef3c7; + colour: #92400e; + } + + .badge.type-docker { + background: #e0e7ff; + colour: #3730a3; + } + + .targets { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + margin-top: 0.25rem; + } + + .target-badge { + font-size: 0.75rem; + padding: 0.125rem 0.5rem; + background: #f3f4f6; + border-radius: 0.25rem; + font-family: monospace; + colour: #374151; + } + + .flags { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + } + + .flag { + font-size: 0.75rem; + padding: 0.0625rem 0.375rem; + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 0.25rem; + font-family: monospace; + colour: #6b7280; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .loading { + text-align: center; + padding: 2rem; + colour: #6b7280; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + } + `; +H([ + z({ attribute: "api-url" }) +], C.prototype, "apiUrl", 2); +H([ + h() +], C.prototype, "configData", 2); +H([ + h() +], C.prototype, "discoverData", 2); +H([ + h() +], C.prototype, "loading", 2); +H([ + h() +], C.prototype, "error", 2); +C = H([ + K("core-build-config") +], C); +var Ze = Object.defineProperty, Qe = Object.getOwnPropertyDescriptor, A = (s, e, t, r) => { + for (var i = r > 1 ? void 0 : r ? Qe(e, t) : e, o = s.length - 1, n; o >= 0; o--) + (n = s[o]) && (i = (r ? n(e, t, i) : n(i)) || i); + return r && i && Ze(e, t, i), i; +}; +let v = class extends w { + constructor() { + super(...arguments), this.apiUrl = "", this.artifacts = [], this.distExists = !1, this.loading = !0, this.error = "", this.building = !1, this.confirmBuild = !1, this.buildSuccess = ""; + } + connectedCallback() { + super.connectedCallback(), this.api = new Y(this.apiUrl), this.reload(); + } + async reload() { + this.loading = !0, this.error = ""; + try { + const s = await this.api.artifacts(); + this.artifacts = s.artifacts ?? [], this.distExists = s.exists ?? !1; + } catch (s) { + this.error = s.message ?? "Failed to load artifacts"; + } finally { + this.loading = !1; + } + } + handleBuildClick() { + this.confirmBuild = !0, this.buildSuccess = ""; + } + handleCancelBuild() { + this.confirmBuild = !1; + } + async handleConfirmBuild() { + var s; + this.confirmBuild = !1, this.building = !0, this.error = "", this.buildSuccess = ""; + try { + const e = await this.api.build(); + this.buildSuccess = `Build complete — ${((s = e.artifacts) == null ? void 0 : s.length) ?? 0} artifact(s) produced (${e.version})`, await this.reload(); + } catch (e) { + this.error = e.message ?? "Build failed"; + } finally { + this.building = !1; + } + } + formatSize(s) { + return s < 1024 ? `${s} B` : s < 1024 * 1024 ? `${(s / 1024).toFixed(1)} KB` : `${(s / (1024 * 1024)).toFixed(1)} MB`; + } + render() { + return this.loading ? l`
Loading artifacts\u2026
` : l` +
+ + ${this.distExists ? `${this.artifacts.length} file(s) in dist/` : "No dist/ directory"} + + +
+ + ${this.confirmBuild ? l` +
+ This will run a full build and overwrite dist/. Continue? + + +
+ ` : c} + + ${this.error ? l`
${this.error}
` : c} + ${this.buildSuccess ? l`
${this.buildSuccess}
` : c} + + ${this.artifacts.length === 0 ? l`
${this.distExists ? "dist/ is empty." : "Run a build to create artifacts."}
` : l` +
+ ${this.artifacts.map( + (s) => l` +
+ ${s.name} + ${this.formatSize(s.size)} +
+ ` + )} +
+ `} + `; + } +}; +v.styles = F` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + } + + .toolbar-info { + font-size: 0.8125rem; + colour: #6b7280; + } + + button.build { + padding: 0.5rem 1.25rem; + background: #6366f1; + colour: #fff; + border: none; + border-radius: 0.375rem; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: background 0.15s; + } + + button.build:hover { + background: #4f46e5; + } + + button.build:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .confirm { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: 0.375rem; + margin-bottom: 1rem; + font-size: 0.8125rem; + } + + .confirm-text { + flex: 1; + colour: #92400e; + } + + button.confirm-yes { + padding: 0.375rem 1rem; + background: #dc2626; + colour: #fff; + border: none; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + button.confirm-yes:hover { + background: #b91c1c; + } + + button.confirm-no { + padding: 0.375rem 0.75rem; + background: #fff; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + .list { + display: flex; + flex-direction: column; + gap: 0.375rem; + } + + .artifact { + border: 1px solid #e5e7eb; + border-radius: 0.375rem; + padding: 0.625rem 1rem; + background: #fff; + display: flex; + justify-content: space-between; + align-items: center; + } + + .artifact-name { + font-size: 0.875rem; + font-family: monospace; + font-weight: 500; + colour: #111827; + } + + .artifact-size { + font-size: 0.75rem; + colour: #6b7280; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .loading { + text-align: center; + padding: 2rem; + colour: #6b7280; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-bottom: 1rem; + } + + .success { + padding: 0.75rem; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 0.375rem; + font-size: 0.875rem; + colour: #166534; + margin-bottom: 1rem; + } + `; +A([ + z({ attribute: "api-url" }) +], v.prototype, "apiUrl", 2); +A([ + h() +], v.prototype, "artifacts", 2); +A([ + h() +], v.prototype, "distExists", 2); +A([ + h() +], v.prototype, "loading", 2); +A([ + h() +], v.prototype, "error", 2); +A([ + h() +], v.prototype, "building", 2); +A([ + h() +], v.prototype, "confirmBuild", 2); +A([ + h() +], v.prototype, "buildSuccess", 2); +v = A([ + K("core-build-artifacts") +], v); +var Xe = Object.defineProperty, Ye = Object.getOwnPropertyDescriptor, x = (s, e, t, r) => { + for (var i = r > 1 ? void 0 : r ? Ye(e, t) : e, o = s.length - 1, n; o >= 0; o--) + (n = s[o]) && (i = (r ? n(e, t, i) : n(i)) || i); + return r && i && Xe(e, t, i), i; +}; +let $ = class extends w { + constructor() { + super(...arguments), this.apiUrl = "", this.version = "", this.changelog = "", this.loading = !0, this.error = "", this.releasing = !1, this.confirmRelease = !1, this.releaseSuccess = ""; + } + connectedCallback() { + super.connectedCallback(), this.api = new Y(this.apiUrl), this.reload(); + } + async reload() { + this.loading = !0, this.error = ""; + try { + const [s, e] = await Promise.all([ + this.api.version(), + this.api.changelog() + ]); + this.version = s.version ?? "", this.changelog = e.changelog ?? ""; + } catch (s) { + this.error = s.message ?? "Failed to load release information"; + } finally { + this.loading = !1; + } + } + handleReleaseClick() { + this.confirmRelease = !0, this.releaseSuccess = ""; + } + handleCancelRelease() { + this.confirmRelease = !1; + } + async handleConfirmRelease() { + this.confirmRelease = !1, await this.doRelease(!1); + } + async handleDryRun() { + await this.doRelease(!0); + } + async doRelease(s) { + var e; + this.releasing = !0, this.error = "", this.releaseSuccess = ""; + try { + const t = await this.api.release(s), r = s ? "Dry run complete" : "Release published"; + this.releaseSuccess = `${r} — ${t.version} (${((e = t.artifacts) == null ? void 0 : e.length) ?? 0} artifact(s))`, await this.reload(); + } catch (t) { + this.error = t.message ?? "Release failed"; + } finally { + this.releasing = !1; + } + } + render() { + return this.loading ? l`
Loading release information\u2026
` : l` + ${this.error ? l`
${this.error}
` : c} + ${this.releaseSuccess ? l`
${this.releaseSuccess}
` : c} + +
+
+
Current Version
+
${this.version || "unknown"}
+
+
+ + +
+
+ + ${this.confirmRelease ? l` +
+ This will publish ${this.version} to all configured targets. This action cannot be undone. Continue? + + +
+ ` : c} + + ${this.changelog ? l` +
+
Changelog
+
${this.changelog}
+
+ ` : l`
No changelog available.
`} + `; + } +}; +$.styles = F` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .version-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + background: #fff; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + margin-bottom: 1rem; + } + + .version-label { + font-size: 0.75rem; + font-weight: 600; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + } + + .version-value { + font-size: 1.25rem; + font-weight: 700; + font-family: monospace; + colour: #111827; + } + + .actions { + display: flex; + gap: 0.5rem; + } + + button { + padding: 0.5rem 1rem; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + transition: background 0.15s; + } + + button.release { + background: #6366f1; + colour: #fff; + border: none; + font-weight: 500; + } + + button.release:hover { + background: #4f46e5; + } + + button.release:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + button.dry-run { + background: #fff; + colour: #6366f1; + border: 1px solid #6366f1; + } + + button.dry-run:hover { + background: #eef2ff; + } + + .confirm { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: #fef2f2; + border: 1px solid #fecaca; + border-radius: 0.375rem; + margin-bottom: 1rem; + font-size: 0.8125rem; + } + + .confirm-text { + flex: 1; + colour: #991b1b; + } + + button.confirm-yes { + padding: 0.375rem 1rem; + background: #dc2626; + colour: #fff; + border: none; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + button.confirm-no { + padding: 0.375rem 0.75rem; + background: #fff; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + .changelog-section { + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + background: #fff; + } + + .changelog-header { + padding: 0.75rem 1rem; + border-bottom: 1px solid #e5e7eb; + font-size: 0.75rem; + font-weight: 700; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + } + + .changelog-content { + padding: 1rem; + font-size: 0.875rem; + line-height: 1.6; + white-space: pre-wrap; + font-family: system-ui, -apple-system, sans-serif; + colour: #374151; + max-height: 400px; + overflow-y: auto; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .loading { + text-align: center; + padding: 2rem; + colour: #6b7280; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-bottom: 1rem; + } + + .success { + padding: 0.75rem; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 0.375rem; + font-size: 0.875rem; + colour: #166534; + margin-bottom: 1rem; + } + `; +x([ + z({ attribute: "api-url" }) +], $.prototype, "apiUrl", 2); +x([ + h() +], $.prototype, "version", 2); +x([ + h() +], $.prototype, "changelog", 2); +x([ + h() +], $.prototype, "loading", 2); +x([ + h() +], $.prototype, "error", 2); +x([ + h() +], $.prototype, "releasing", 2); +x([ + h() +], $.prototype, "confirmRelease", 2); +x([ + h() +], $.prototype, "releaseSuccess", 2); +$ = x([ + K("core-build-release") +], $); +var et = Object.defineProperty, tt = Object.getOwnPropertyDescriptor, y = (s, e, t, r) => { + for (var i = r > 1 ? void 0 : r ? tt(e, t) : e, o = s.length - 1, n; o >= 0; o--) + (n = s[o]) && (i = (r ? n(e, t, i) : n(i)) || i); + return r && i && et(e, t, i), i; +}; +let m = class extends w { + constructor() { + super(...arguments), this.apiUrl = "", this.basePath = "", this.revisionPath = "", this.diffResult = null, this.diffing = !1, this.diffError = "", this.selectedLanguage = "", this.generating = !1, this.generateError = "", this.generateSuccess = ""; + } + connectedCallback() { + super.connectedCallback(), this.api = new Y(this.apiUrl); + } + async reload() { + this.diffResult = null, this.diffError = "", this.generateError = "", this.generateSuccess = ""; + } + async handleDiff() { + if (!this.basePath.trim() || !this.revisionPath.trim()) { + this.diffError = "Both base and revision spec paths are required."; + return; + } + this.diffing = !0, this.diffError = "", this.diffResult = null; + try { + this.diffResult = await this.api.sdkDiff(this.basePath.trim(), this.revisionPath.trim()); + } catch (s) { + this.diffError = s.message ?? "Diff failed"; + } finally { + this.diffing = !1; + } + } + async handleGenerate() { + this.generating = !0, this.generateError = "", this.generateSuccess = ""; + try { + const e = (await this.api.sdkGenerate(this.selectedLanguage || void 0)).language || "all languages"; + this.generateSuccess = `SDK generated successfully for ${e}.`; + } catch (s) { + this.generateError = s.message ?? "Generation failed"; + } finally { + this.generating = !1; + } + } + render() { + return l` + +
+
OpenAPI Diff
+
+
+ + this.basePath = s.target.value} + /> +
+
+ + this.revisionPath = s.target.value} + /> +
+ +
+ + ${this.diffError ? l`
${this.diffError}
` : c} + + ${this.diffResult ? l` +
+
${this.diffResult.Summary}
+ ${this.diffResult.Changes && this.diffResult.Changes.length > 0 ? l` +
    + ${this.diffResult.Changes.map( + (s) => l`
  • ${s}
  • ` + )} +
+ ` : c} +
+ ` : c} +
+ + +
+
SDK Generation
+ + ${this.generateError ? l`
${this.generateError}
` : c} + ${this.generateSuccess ? l`
${this.generateSuccess}
` : c} + +
+ + +
+
+ `; + } +}; +m.styles = F` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .section { + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + padding: 1rem; + background: #fff; + margin-bottom: 1rem; + } + + .section-title { + font-size: 0.75rem; + font-weight: 700; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + margin-bottom: 0.75rem; + } + + .diff-form { + display: flex; + gap: 0.5rem; + align-items: flex-end; + margin-bottom: 1rem; + } + + .diff-field { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.25rem; + } + + .diff-field label { + font-size: 0.75rem; + font-weight: 500; + colour: #6b7280; + } + + .diff-field input { + padding: 0.375rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + font-family: monospace; + } + + .diff-field input:focus { + outline: none; + border-colour: #6366f1; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); + } + + button { + padding: 0.375rem 1rem; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + transition: background 0.15s; + } + + button.primary { + background: #6366f1; + colour: #fff; + border: none; + } + + button.primary:hover { + background: #4f46e5; + } + + button.primary:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + button.secondary { + background: #fff; + colour: #374151; + border: 1px solid #d1d5db; + } + + button.secondary:hover { + background: #f3f4f6; + } + + .diff-result { + padding: 0.75rem; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-top: 0.75rem; + } + + .diff-result.breaking { + background: #fef2f2; + border: 1px solid #fecaca; + colour: #991b1b; + } + + .diff-result.safe { + background: #f0fdf4; + border: 1px solid #bbf7d0; + colour: #166534; + } + + .diff-summary { + font-weight: 600; + margin-bottom: 0.5rem; + } + + .diff-changes { + list-style: disc; + padding-left: 1.25rem; + margin: 0; + } + + .diff-changes li { + font-size: 0.8125rem; + margin-bottom: 0.25rem; + font-family: monospace; + } + + .generate-form { + display: flex; + gap: 0.5rem; + align-items: center; + } + + .generate-form select { + padding: 0.375rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + background: #fff; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-bottom: 1rem; + } + + .success { + padding: 0.75rem; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 0.375rem; + font-size: 0.875rem; + colour: #166534; + margin-bottom: 1rem; + } + + .loading { + text-align: center; + padding: 1rem; + colour: #6b7280; + font-size: 0.875rem; + } + `; +y([ + z({ attribute: "api-url" }) +], m.prototype, "apiUrl", 2); +y([ + h() +], m.prototype, "basePath", 2); +y([ + h() +], m.prototype, "revisionPath", 2); +y([ + h() +], m.prototype, "diffResult", 2); +y([ + h() +], m.prototype, "diffing", 2); +y([ + h() +], m.prototype, "diffError", 2); +y([ + h() +], m.prototype, "selectedLanguage", 2); +y([ + h() +], m.prototype, "generating", 2); +y([ + h() +], m.prototype, "generateError", 2); +y([ + h() +], m.prototype, "generateSuccess", 2); +m = y([ + K("core-build-sdk") +], m); +var st = Object.defineProperty, it = Object.getOwnPropertyDescriptor, N = (s, e, t, r) => { + for (var i = r > 1 ? void 0 : r ? it(e, t) : e, o = s.length - 1, n; o >= 0; o--) + (n = s[o]) && (i = (r ? n(e, t, i) : n(i)) || i); + return r && i && st(e, t, i), i; +}; +let P = class extends w { + constructor() { + super(...arguments), this.apiUrl = "", this.wsUrl = "", this.activeTab = "config", this.wsConnected = !1, this.lastEvent = "", this.ws = null, this.tabs = [ + { id: "config", label: "Config" }, + { id: "build", label: "Build" }, + { id: "release", label: "Release" }, + { id: "sdk", label: "SDK" } + ]; + } + connectedCallback() { + super.connectedCallback(), this.wsUrl && this.connectWs(); + } + disconnectedCallback() { + super.disconnectedCallback(), this.ws && (this.ws.close(), this.ws = null); + } + connectWs() { + this.ws = Ve(this.wsUrl, (s) => { + this.lastEvent = s.channel ?? s.type ?? "", this.requestUpdate(); + }), this.ws.onopen = () => { + this.wsConnected = !0; + }, this.ws.onclose = () => { + this.wsConnected = !1; + }; + } + handleTabClick(s) { + this.activeTab = s; + } + handleRefresh() { + var e; + const s = (e = this.shadowRoot) == null ? void 0 : e.querySelector(".content"); + if (s) { + const t = s.firstElementChild; + t && "reload" in t && t.reload(); + } + } + renderContent() { + switch (this.activeTab) { + case "config": + return l``; + case "build": + return l``; + case "release": + return l``; + case "sdk": + return l``; + default: + return c; + } + } + render() { + const s = this.wsUrl ? this.wsConnected ? "connected" : "disconnected" : "idle"; + return l` +
+ Build + +
+ +
+ ${this.tabs.map( + (e) => l` + + ` + )} +
+ +
${this.renderContent()}
+ + + `; + } +}; +P.styles = F` + :host { + display: flex; + flex-direction: column; + font-family: system-ui, -apple-system, sans-serif; + height: 100%; + background: #fafafa; + } + + /* H — Header */ + .header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + background: #fff; + border-bottom: 1px solid #e5e7eb; + } + + .title { + font-weight: 700; + font-size: 1rem; + colour: #111827; + } + + .refresh-btn { + padding: 0.375rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + background: #fff; + font-size: 0.8125rem; + cursor: pointer; + transition: background 0.15s; + } + + .refresh-btn:hover { + background: #f3f4f6; + } + + /* H-L — Tabs */ + .tabs { + display: flex; + gap: 0; + background: #fff; + border-bottom: 1px solid #e5e7eb; + padding: 0 1rem; + } + + .tab { + padding: 0.625rem 1rem; + font-size: 0.8125rem; + font-weight: 500; + colour: #6b7280; + cursor: pointer; + border-bottom: 2px solid transparent; + transition: all 0.15s; + background: none; + border-top: none; + border-left: none; + border-right: none; + } + + .tab:hover { + colour: #374151; + } + + .tab.active { + colour: #6366f1; + border-bottom-colour: #6366f1; + } + + /* C — Content */ + .content { + flex: 1; + padding: 1rem; + overflow-y: auto; + } + + /* F — Footer / Status bar */ + .footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 1rem; + background: #fff; + border-top: 1px solid #e5e7eb; + font-size: 0.75rem; + colour: #9ca3af; + } + + .ws-status { + display: flex; + align-items: center; + gap: 0.375rem; + } + + .ws-dot { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + } + + .ws-dot.connected { + background: #22c55e; + } + + .ws-dot.disconnected { + background: #ef4444; + } + + .ws-dot.idle { + background: #d1d5db; + } + `; +N([ + z({ attribute: "api-url" }) +], P.prototype, "apiUrl", 2); +N([ + z({ attribute: "ws-url" }) +], P.prototype, "wsUrl", 2); +N([ + h() +], P.prototype, "activeTab", 2); +N([ + h() +], P.prototype, "wsConnected", 2); +N([ + h() +], P.prototype, "lastEvent", 2); +P = N([ + K("core-build-panel") +], P); +export { + Y as BuildApi, + v as BuildArtifacts, + C as BuildConfig, + P as BuildPanel, + $ as BuildRelease, + m as BuildSdk, + Ve as connectBuildEvents +}; diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..68c2d04 --- /dev/null +++ b/ui/index.html @@ -0,0 +1,79 @@ + + + + + + Core Build — Demo + + + + +

Core Build — Custom Element Demo

+ +
+ +
+ +

Standalone Elements

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 0000000..5cbacca --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,1213 @@ +{ + "name": "@core/build-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@core/build-ui", + "version": "0.1.0", + "dependencies": { + "lit": "^3.2.0" + }, + "devDependencies": { + "typescript": "^5.7.0", + "vite": "^6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", + "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lit": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", + "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..69ad059 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,18 @@ +{ + "name": "@core/build-ui", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "lit": "^3.2.0" + }, + "devDependencies": { + "typescript": "^5.7.0", + "vite": "^6.0.0" + } +} diff --git a/ui/src/build-artifacts.ts b/ui/src/build-artifacts.ts new file mode 100644 index 0000000..6bbcaff --- /dev/null +++ b/ui/src/build-artifacts.ts @@ -0,0 +1,278 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { LitElement, html, css, nothing } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { BuildApi } from './shared/api.js'; + +interface ArtifactInfo { + name: string; + path: string; + size: number; +} + +/** + * — Shows dist/ contents and provides build trigger. + * Includes a confirmation dialogue before triggering a build. + */ +@customElement('core-build-artifacts') +export class BuildArtifacts extends LitElement { + static styles = css` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + } + + .toolbar-info { + font-size: 0.8125rem; + colour: #6b7280; + } + + button.build { + padding: 0.5rem 1.25rem; + background: #6366f1; + colour: #fff; + border: none; + border-radius: 0.375rem; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: background 0.15s; + } + + button.build:hover { + background: #4f46e5; + } + + button.build:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .confirm { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: 0.375rem; + margin-bottom: 1rem; + font-size: 0.8125rem; + } + + .confirm-text { + flex: 1; + colour: #92400e; + } + + button.confirm-yes { + padding: 0.375rem 1rem; + background: #dc2626; + colour: #fff; + border: none; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + button.confirm-yes:hover { + background: #b91c1c; + } + + button.confirm-no { + padding: 0.375rem 0.75rem; + background: #fff; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + .list { + display: flex; + flex-direction: column; + gap: 0.375rem; + } + + .artifact { + border: 1px solid #e5e7eb; + border-radius: 0.375rem; + padding: 0.625rem 1rem; + background: #fff; + display: flex; + justify-content: space-between; + align-items: center; + } + + .artifact-name { + font-size: 0.875rem; + font-family: monospace; + font-weight: 500; + colour: #111827; + } + + .artifact-size { + font-size: 0.75rem; + colour: #6b7280; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .loading { + text-align: center; + padding: 2rem; + colour: #6b7280; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-bottom: 1rem; + } + + .success { + padding: 0.75rem; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 0.375rem; + font-size: 0.875rem; + colour: #166534; + margin-bottom: 1rem; + } + `; + + @property({ attribute: 'api-url' }) apiUrl = ''; + + @state() private artifacts: ArtifactInfo[] = []; + @state() private distExists = false; + @state() private loading = true; + @state() private error = ''; + @state() private building = false; + @state() private confirmBuild = false; + @state() private buildSuccess = ''; + + private api!: BuildApi; + + connectedCallback() { + super.connectedCallback(); + this.api = new BuildApi(this.apiUrl); + this.reload(); + } + + async reload() { + this.loading = true; + this.error = ''; + try { + const data = await this.api.artifacts(); + this.artifacts = data.artifacts ?? []; + this.distExists = data.exists ?? false; + } catch (e: any) { + this.error = e.message ?? 'Failed to load artifacts'; + } finally { + this.loading = false; + } + } + + private handleBuildClick() { + this.confirmBuild = true; + this.buildSuccess = ''; + } + + private handleCancelBuild() { + this.confirmBuild = false; + } + + private async handleConfirmBuild() { + this.confirmBuild = false; + this.building = true; + this.error = ''; + this.buildSuccess = ''; + try { + const result = await this.api.build(); + this.buildSuccess = `Build complete — ${result.artifacts?.length ?? 0} artifact(s) produced (${result.version})`; + await this.reload(); + } catch (e: any) { + this.error = e.message ?? 'Build failed'; + } finally { + this.building = false; + } + } + + private formatSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + } + + render() { + if (this.loading) { + return html`
Loading artifacts\u2026
`; + } + + return html` +
+ + ${this.distExists + ? `${this.artifacts.length} file(s) in dist/` + : 'No dist/ directory'} + + +
+ + ${this.confirmBuild + ? html` +
+ This will run a full build and overwrite dist/. Continue? + + +
+ ` + : nothing} + + ${this.error ? html`
${this.error}
` : nothing} + ${this.buildSuccess ? html`
${this.buildSuccess}
` : nothing} + + ${this.artifacts.length === 0 + ? html`
${this.distExists ? 'dist/ is empty.' : 'Run a build to create artifacts.'}
` + : html` +
+ ${this.artifacts.map( + (a) => html` +
+ ${a.name} + ${this.formatSize(a.size)} +
+ `, + )} +
+ `} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'core-build-artifacts': BuildArtifacts; + } +} diff --git a/ui/src/build-config.ts b/ui/src/build-config.ts new file mode 100644 index 0000000..4c9d5c5 --- /dev/null +++ b/ui/src/build-config.ts @@ -0,0 +1,347 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { LitElement, html, css, nothing } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { BuildApi } from './shared/api.js'; + +interface TargetConfig { + os: string; + arch: string; +} + +interface BuildConfigData { + config: { + version: number; + project: { + name: string; + description: string; + main: string; + binary: string; + }; + build: { + type: string; + cgo: boolean; + flags: string[]; + ldflags: string[]; + env: string[]; + }; + targets: TargetConfig[]; + sign: any; + }; + has_config: boolean; + path: string; +} + +interface DiscoverData { + types: string[]; + primary: string; + dir: string; +} + +/** + * — Displays .core/build.yaml fields and project type detection. + */ +@customElement('core-build-config') +export class BuildConfig extends LitElement { + static styles = css` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .section { + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + padding: 1rem; + background: #fff; + margin-bottom: 1rem; + } + + .section-title { + font-size: 0.75rem; + font-weight: 700; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + margin-bottom: 0.75rem; + } + + .field { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 0.375rem 0; + border-bottom: 1px solid #f3f4f6; + } + + .field:last-child { + border-bottom: none; + } + + .field-label { + font-size: 0.8125rem; + font-weight: 500; + colour: #374151; + } + + .field-value { + font-size: 0.8125rem; + font-family: monospace; + colour: #6b7280; + } + + .badge { + display: inline-block; + font-size: 0.6875rem; + font-weight: 600; + padding: 0.125rem 0.5rem; + border-radius: 1rem; + } + + .badge.present { + background: #dcfce7; + colour: #166534; + } + + .badge.absent { + background: #fef3c7; + colour: #92400e; + } + + .badge.type-go { + background: #dbeafe; + colour: #1e40af; + } + + .badge.type-wails { + background: #f3e8ff; + colour: #6b21a8; + } + + .badge.type-node { + background: #dcfce7; + colour: #166534; + } + + .badge.type-php { + background: #fef3c7; + colour: #92400e; + } + + .badge.type-docker { + background: #e0e7ff; + colour: #3730a3; + } + + .targets { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + margin-top: 0.25rem; + } + + .target-badge { + font-size: 0.75rem; + padding: 0.125rem 0.5rem; + background: #f3f4f6; + border-radius: 0.25rem; + font-family: monospace; + colour: #374151; + } + + .flags { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + } + + .flag { + font-size: 0.75rem; + padding: 0.0625rem 0.375rem; + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 0.25rem; + font-family: monospace; + colour: #6b7280; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .loading { + text-align: center; + padding: 2rem; + colour: #6b7280; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + } + `; + + @property({ attribute: 'api-url' }) apiUrl = ''; + + @state() private configData: BuildConfigData | null = null; + @state() private discoverData: DiscoverData | null = null; + @state() private loading = true; + @state() private error = ''; + + private api!: BuildApi; + + connectedCallback() { + super.connectedCallback(); + this.api = new BuildApi(this.apiUrl); + this.reload(); + } + + async reload() { + this.loading = true; + this.error = ''; + try { + const [configData, discoverData] = await Promise.all([ + this.api.config(), + this.api.discover(), + ]); + this.configData = configData; + this.discoverData = discoverData; + } catch (e: any) { + this.error = e.message ?? 'Failed to load configuration'; + } finally { + this.loading = false; + } + } + + render() { + if (this.loading) { + return html`
Loading configuration\u2026
`; + } + if (this.error) { + return html`
${this.error}
`; + } + if (!this.configData) { + return html`
No configuration available.
`; + } + + const cfg = this.configData.config; + const disc = this.discoverData; + + return html` + +
+
Project Detection
+
+ Config file + + ${this.configData.has_config ? 'Present' : 'Using defaults'} + +
+ ${disc + ? html` +
+ Primary type + ${disc.primary || 'none'} +
+ ${disc.types.length > 1 + ? html` +
+ Detected types + ${disc.types.join(', ')} +
+ ` + : nothing} +
+ Directory + ${disc.dir} +
+ ` + : nothing} +
+ + +
+
Project
+ ${cfg.project.name + ? html` +
+ Name + ${cfg.project.name} +
+ ` + : nothing} + ${cfg.project.binary + ? html` +
+ Binary + ${cfg.project.binary} +
+ ` + : nothing} +
+ Main + ${cfg.project.main} +
+
+ + +
+
Build Settings
+ ${cfg.build.type + ? html` +
+ Type override + ${cfg.build.type} +
+ ` + : nothing} +
+ CGO + ${cfg.build.cgo ? 'Enabled' : 'Disabled'} +
+ ${cfg.build.flags && cfg.build.flags.length > 0 + ? html` +
+ Flags +
+ ${cfg.build.flags.map((f: string) => html`${f}`)} +
+
+ ` + : nothing} + ${cfg.build.ldflags && cfg.build.ldflags.length > 0 + ? html` +
+ LD flags +
+ ${cfg.build.ldflags.map((f: string) => html`${f}`)} +
+
+ ` + : nothing} +
+ + +
+
Targets
+
+ ${cfg.targets.map( + (t: TargetConfig) => html`${t.os}/${t.arch}`, + )} +
+
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'core-build-config': BuildConfig; + } +} diff --git a/ui/src/build-panel.ts b/ui/src/build-panel.ts new file mode 100644 index 0000000..65bedec --- /dev/null +++ b/ui/src/build-panel.ts @@ -0,0 +1,257 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { LitElement, html, css, nothing } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { connectBuildEvents, type BuildEvent } from './shared/events.js'; + +// Side-effect imports to register child elements +import './build-config.js'; +import './build-artifacts.js'; +import './build-release.js'; +import './build-sdk.js'; + +type TabId = 'config' | 'build' | 'release' | 'sdk'; + +/** + * — Top-level HLCRF panel with tabs. + * + * Arranges child elements in HLCRF layout: + * - H: Title bar with refresh button + * - H-L: Navigation tabs + * - C: Active tab content (one of the child elements) + * - F: Status bar (connection state, last event) + */ +@customElement('core-build-panel') +export class BuildPanel extends LitElement { + static styles = css` + :host { + display: flex; + flex-direction: column; + font-family: system-ui, -apple-system, sans-serif; + height: 100%; + background: #fafafa; + } + + /* H — Header */ + .header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + background: #fff; + border-bottom: 1px solid #e5e7eb; + } + + .title { + font-weight: 700; + font-size: 1rem; + colour: #111827; + } + + .refresh-btn { + padding: 0.375rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + background: #fff; + font-size: 0.8125rem; + cursor: pointer; + transition: background 0.15s; + } + + .refresh-btn:hover { + background: #f3f4f6; + } + + /* H-L — Tabs */ + .tabs { + display: flex; + gap: 0; + background: #fff; + border-bottom: 1px solid #e5e7eb; + padding: 0 1rem; + } + + .tab { + padding: 0.625rem 1rem; + font-size: 0.8125rem; + font-weight: 500; + colour: #6b7280; + cursor: pointer; + border-bottom: 2px solid transparent; + transition: all 0.15s; + background: none; + border-top: none; + border-left: none; + border-right: none; + } + + .tab:hover { + colour: #374151; + } + + .tab.active { + colour: #6366f1; + border-bottom-colour: #6366f1; + } + + /* C — Content */ + .content { + flex: 1; + padding: 1rem; + overflow-y: auto; + } + + /* F — Footer / Status bar */ + .footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 1rem; + background: #fff; + border-top: 1px solid #e5e7eb; + font-size: 0.75rem; + colour: #9ca3af; + } + + .ws-status { + display: flex; + align-items: center; + gap: 0.375rem; + } + + .ws-dot { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + } + + .ws-dot.connected { + background: #22c55e; + } + + .ws-dot.disconnected { + background: #ef4444; + } + + .ws-dot.idle { + background: #d1d5db; + } + `; + + @property({ attribute: 'api-url' }) apiUrl = ''; + @property({ attribute: 'ws-url' }) wsUrl = ''; + + @state() private activeTab: TabId = 'config'; + @state() private wsConnected = false; + @state() private lastEvent = ''; + + private ws: WebSocket | null = null; + + connectedCallback() { + super.connectedCallback(); + if (this.wsUrl) { + this.connectWs(); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.ws) { + this.ws.close(); + this.ws = null; + } + } + + private connectWs() { + this.ws = connectBuildEvents(this.wsUrl, (event: BuildEvent) => { + this.lastEvent = event.channel ?? event.type ?? ''; + this.requestUpdate(); + }); + this.ws.onopen = () => { + this.wsConnected = true; + }; + this.ws.onclose = () => { + this.wsConnected = false; + }; + } + + private handleTabClick(tab: TabId) { + this.activeTab = tab; + } + + private handleRefresh() { + const content = this.shadowRoot?.querySelector('.content'); + if (content) { + const child = content.firstElementChild; + if (child && 'reload' in child) { + (child as any).reload(); + } + } + } + + private renderContent() { + switch (this.activeTab) { + case 'config': + return html``; + case 'build': + return html``; + case 'release': + return html``; + case 'sdk': + return html``; + default: + return nothing; + } + } + + private tabs: { id: TabId; label: string }[] = [ + { id: 'config', label: 'Config' }, + { id: 'build', label: 'Build' }, + { id: 'release', label: 'Release' }, + { id: 'sdk', label: 'SDK' }, + ]; + + render() { + const wsState = this.wsUrl + ? this.wsConnected + ? 'connected' + : 'disconnected' + : 'idle'; + + return html` +
+ Build + +
+ +
+ ${this.tabs.map( + (tab) => html` + + `, + )} +
+ +
${this.renderContent()}
+ + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'core-build-panel': BuildPanel; + } +} diff --git a/ui/src/build-release.ts b/ui/src/build-release.ts new file mode 100644 index 0000000..3ea413d --- /dev/null +++ b/ui/src/build-release.ts @@ -0,0 +1,307 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { LitElement, html, css, nothing } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { BuildApi } from './shared/api.js'; + +/** + * — Version display, changelog preview, and release trigger. + * Includes confirmation dialogue and dry-run support for safety. + */ +@customElement('core-build-release') +export class BuildRelease extends LitElement { + static styles = css` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .version-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + background: #fff; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + margin-bottom: 1rem; + } + + .version-label { + font-size: 0.75rem; + font-weight: 600; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + } + + .version-value { + font-size: 1.25rem; + font-weight: 700; + font-family: monospace; + colour: #111827; + } + + .actions { + display: flex; + gap: 0.5rem; + } + + button { + padding: 0.5rem 1rem; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + transition: background 0.15s; + } + + button.release { + background: #6366f1; + colour: #fff; + border: none; + font-weight: 500; + } + + button.release:hover { + background: #4f46e5; + } + + button.release:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + button.dry-run { + background: #fff; + colour: #6366f1; + border: 1px solid #6366f1; + } + + button.dry-run:hover { + background: #eef2ff; + } + + .confirm { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: #fef2f2; + border: 1px solid #fecaca; + border-radius: 0.375rem; + margin-bottom: 1rem; + font-size: 0.8125rem; + } + + .confirm-text { + flex: 1; + colour: #991b1b; + } + + button.confirm-yes { + padding: 0.375rem 1rem; + background: #dc2626; + colour: #fff; + border: none; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + button.confirm-no { + padding: 0.375rem 0.75rem; + background: #fff; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + } + + .changelog-section { + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + background: #fff; + } + + .changelog-header { + padding: 0.75rem 1rem; + border-bottom: 1px solid #e5e7eb; + font-size: 0.75rem; + font-weight: 700; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + } + + .changelog-content { + padding: 1rem; + font-size: 0.875rem; + line-height: 1.6; + white-space: pre-wrap; + font-family: system-ui, -apple-system, sans-serif; + colour: #374151; + max-height: 400px; + overflow-y: auto; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .loading { + text-align: center; + padding: 2rem; + colour: #6b7280; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-bottom: 1rem; + } + + .success { + padding: 0.75rem; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 0.375rem; + font-size: 0.875rem; + colour: #166534; + margin-bottom: 1rem; + } + `; + + @property({ attribute: 'api-url' }) apiUrl = ''; + + @state() private version = ''; + @state() private changelog = ''; + @state() private loading = true; + @state() private error = ''; + @state() private releasing = false; + @state() private confirmRelease = false; + @state() private releaseSuccess = ''; + + private api!: BuildApi; + + connectedCallback() { + super.connectedCallback(); + this.api = new BuildApi(this.apiUrl); + this.reload(); + } + + async reload() { + this.loading = true; + this.error = ''; + try { + const [versionData, changelogData] = await Promise.all([ + this.api.version(), + this.api.changelog(), + ]); + this.version = versionData.version ?? ''; + this.changelog = changelogData.changelog ?? ''; + } catch (e: any) { + this.error = e.message ?? 'Failed to load release information'; + } finally { + this.loading = false; + } + } + + private handleReleaseClick() { + this.confirmRelease = true; + this.releaseSuccess = ''; + } + + private handleCancelRelease() { + this.confirmRelease = false; + } + + private async handleConfirmRelease() { + this.confirmRelease = false; + await this.doRelease(false); + } + + private async handleDryRun() { + await this.doRelease(true); + } + + private async doRelease(dryRun: boolean) { + this.releasing = true; + this.error = ''; + this.releaseSuccess = ''; + try { + const result = await this.api.release(dryRun); + const prefix = dryRun ? 'Dry run complete' : 'Release published'; + this.releaseSuccess = `${prefix} — ${result.version} (${result.artifacts?.length ?? 0} artifact(s))`; + await this.reload(); + } catch (e: any) { + this.error = e.message ?? 'Release failed'; + } finally { + this.releasing = false; + } + } + + render() { + if (this.loading) { + return html`
Loading release information\u2026
`; + } + + return html` + ${this.error ? html`
${this.error}
` : nothing} + ${this.releaseSuccess ? html`
${this.releaseSuccess}
` : nothing} + +
+
+
Current Version
+
${this.version || 'unknown'}
+
+
+ + +
+
+ + ${this.confirmRelease + ? html` +
+ This will publish ${this.version} to all configured targets. This action cannot be undone. Continue? + + +
+ ` + : nothing} + + ${this.changelog + ? html` +
+
Changelog
+
${this.changelog}
+
+ ` + : html`
No changelog available.
`} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'core-build-release': BuildRelease; + } +} diff --git a/ui/src/build-sdk.ts b/ui/src/build-sdk.ts new file mode 100644 index 0000000..7b2e5b2 --- /dev/null +++ b/ui/src/build-sdk.ts @@ -0,0 +1,335 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +import { LitElement, html, css, nothing } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { BuildApi } from './shared/api.js'; + +/** + * — OpenAPI diff results and SDK generation controls. + */ +@customElement('core-build-sdk') +export class BuildSdk extends LitElement { + static styles = css` + :host { + display: block; + font-family: system-ui, -apple-system, sans-serif; + } + + .section { + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + padding: 1rem; + background: #fff; + margin-bottom: 1rem; + } + + .section-title { + font-size: 0.75rem; + font-weight: 700; + colour: #6b7280; + text-transform: uppercase; + letter-spacing: 0.025em; + margin-bottom: 0.75rem; + } + + .diff-form { + display: flex; + gap: 0.5rem; + align-items: flex-end; + margin-bottom: 1rem; + } + + .diff-field { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.25rem; + } + + .diff-field label { + font-size: 0.75rem; + font-weight: 500; + colour: #6b7280; + } + + .diff-field input { + padding: 0.375rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + font-family: monospace; + } + + .diff-field input:focus { + outline: none; + border-colour: #6366f1; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); + } + + button { + padding: 0.375rem 1rem; + border-radius: 0.375rem; + font-size: 0.8125rem; + cursor: pointer; + transition: background 0.15s; + } + + button.primary { + background: #6366f1; + colour: #fff; + border: none; + } + + button.primary:hover { + background: #4f46e5; + } + + button.primary:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + button.secondary { + background: #fff; + colour: #374151; + border: 1px solid #d1d5db; + } + + button.secondary:hover { + background: #f3f4f6; + } + + .diff-result { + padding: 0.75rem; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-top: 0.75rem; + } + + .diff-result.breaking { + background: #fef2f2; + border: 1px solid #fecaca; + colour: #991b1b; + } + + .diff-result.safe { + background: #f0fdf4; + border: 1px solid #bbf7d0; + colour: #166534; + } + + .diff-summary { + font-weight: 600; + margin-bottom: 0.5rem; + } + + .diff-changes { + list-style: disc; + padding-left: 1.25rem; + margin: 0; + } + + .diff-changes li { + font-size: 0.8125rem; + margin-bottom: 0.25rem; + font-family: monospace; + } + + .generate-form { + display: flex; + gap: 0.5rem; + align-items: center; + } + + .generate-form select { + padding: 0.375rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 0.375rem; + font-size: 0.8125rem; + background: #fff; + } + + .empty { + text-align: center; + padding: 2rem; + colour: #9ca3af; + font-size: 0.875rem; + } + + .error { + colour: #dc2626; + padding: 0.75rem; + background: #fef2f2; + border-radius: 0.375rem; + font-size: 0.875rem; + margin-bottom: 1rem; + } + + .success { + padding: 0.75rem; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 0.375rem; + font-size: 0.875rem; + colour: #166534; + margin-bottom: 1rem; + } + + .loading { + text-align: center; + padding: 1rem; + colour: #6b7280; + font-size: 0.875rem; + } + `; + + @property({ attribute: 'api-url' }) apiUrl = ''; + + @state() private basePath = ''; + @state() private revisionPath = ''; + @state() private diffResult: any = null; + @state() private diffing = false; + @state() private diffError = ''; + + @state() private selectedLanguage = ''; + @state() private generating = false; + @state() private generateError = ''; + @state() private generateSuccess = ''; + + private api!: BuildApi; + + connectedCallback() { + super.connectedCallback(); + this.api = new BuildApi(this.apiUrl); + } + + async reload() { + // Reset state + this.diffResult = null; + this.diffError = ''; + this.generateError = ''; + this.generateSuccess = ''; + } + + private async handleDiff() { + if (!this.basePath.trim() || !this.revisionPath.trim()) { + this.diffError = 'Both base and revision spec paths are required.'; + return; + } + + this.diffing = true; + this.diffError = ''; + this.diffResult = null; + try { + this.diffResult = await this.api.sdkDiff(this.basePath.trim(), this.revisionPath.trim()); + } catch (e: any) { + this.diffError = e.message ?? 'Diff failed'; + } finally { + this.diffing = false; + } + } + + private async handleGenerate() { + this.generating = true; + this.generateError = ''; + this.generateSuccess = ''; + try { + const result = await this.api.sdkGenerate(this.selectedLanguage || undefined); + const lang = result.language || 'all languages'; + this.generateSuccess = `SDK generated successfully for ${lang}.`; + } catch (e: any) { + this.generateError = e.message ?? 'Generation failed'; + } finally { + this.generating = false; + } + } + + render() { + return html` + +
+
OpenAPI Diff
+
+
+ + (this.basePath = (e.target as HTMLInputElement).value)} + /> +
+
+ + (this.revisionPath = (e.target as HTMLInputElement).value)} + /> +
+ +
+ + ${this.diffError ? html`
${this.diffError}
` : nothing} + + ${this.diffResult + ? html` +
+
${this.diffResult.Summary}
+ ${this.diffResult.Changes && this.diffResult.Changes.length > 0 + ? html` +
    + ${this.diffResult.Changes.map( + (change: string) => html`
  • ${change}
  • `, + )} +
+ ` + : nothing} +
+ ` + : nothing} +
+ + +
+
SDK Generation
+ + ${this.generateError ? html`
${this.generateError}
` : nothing} + ${this.generateSuccess ? html`
${this.generateSuccess}
` : nothing} + +
+ + +
+
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'core-build-sdk': BuildSdk; + } +} diff --git a/ui/src/index.ts b/ui/src/index.ts new file mode 100644 index 0000000..9912a86 --- /dev/null +++ b/ui/src/index.ts @@ -0,0 +1,11 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +// Bundle entry — exports all Build custom elements. + +export { BuildPanel } from './build-panel.js'; +export { BuildConfig } from './build-config.js'; +export { BuildArtifacts } from './build-artifacts.js'; +export { BuildRelease } from './build-release.js'; +export { BuildSdk } from './build-sdk.js'; +export { BuildApi } from './shared/api.js'; +export { connectBuildEvents } from './shared/events.js'; diff --git a/ui/src/shared/api.ts b/ui/src/shared/api.ts new file mode 100644 index 0000000..10d35e6 --- /dev/null +++ b/ui/src/shared/api.ts @@ -0,0 +1,74 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +/** + * BuildApi provides a typed fetch wrapper for the /api/v1/build/* endpoints. + */ +export class BuildApi { + constructor(private baseUrl: string = '') {} + + private get base(): string { + return `${this.baseUrl}/api/v1/build`; + } + + private async request(path: string, opts?: RequestInit): Promise { + const res = await fetch(`${this.base}${path}`, opts); + const json = await res.json(); + if (!json.success) { + throw new Error(json.error?.message ?? 'Request failed'); + } + return json.data as T; + } + + // -- Build ------------------------------------------------------------------ + + config() { + return this.request('/config'); + } + + discover() { + return this.request('/discover'); + } + + build() { + return this.request('/build', { method: 'POST' }); + } + + artifacts() { + return this.request('/artifacts'); + } + + // -- Release ---------------------------------------------------------------- + + version() { + return this.request('/release/version'); + } + + changelog(from?: string, to?: string) { + const params = new URLSearchParams(); + if (from) params.set('from', from); + if (to) params.set('to', to); + const qs = params.toString(); + return this.request(`/release/changelog${qs ? `?${qs}` : ''}`); + } + + release(dryRun = false) { + const qs = dryRun ? '?dry_run=true' : ''; + return this.request(`/release${qs}`, { method: 'POST' }); + } + + // -- SDK -------------------------------------------------------------------- + + sdkDiff(base: string, revision: string) { + const params = new URLSearchParams({ base, revision }); + return this.request(`/sdk/diff?${params.toString()}`); + } + + sdkGenerate(language?: string) { + const body = language ? JSON.stringify({ language }) : undefined; + return this.request('/sdk/generate', { + method: 'POST', + headers: body ? { 'Content-Type': 'application/json' } : undefined, + body, + }); + } +} diff --git a/ui/src/shared/events.ts b/ui/src/shared/events.ts new file mode 100644 index 0000000..1c02b94 --- /dev/null +++ b/ui/src/shared/events.ts @@ -0,0 +1,39 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +export interface BuildEvent { + type: string; + channel?: string; + data?: any; + timestamp?: string; +} + +/** + * Connects to a WebSocket endpoint and dispatches build events to a handler. + * Returns the WebSocket instance for lifecycle management. + */ +export function connectBuildEvents( + wsUrl: string, + handler: (event: BuildEvent) => void, +): WebSocket { + const ws = new WebSocket(wsUrl); + + ws.onmessage = (e: MessageEvent) => { + try { + const event: BuildEvent = JSON.parse(e.data); + if ( + event.type?.startsWith?.('build.') || + event.type?.startsWith?.('release.') || + event.type?.startsWith?.('sdk.') || + event.channel?.startsWith?.('build.') || + event.channel?.startsWith?.('release.') || + event.channel?.startsWith?.('sdk.') + ) { + handler(event); + } + } catch { + // Ignore malformed messages + } + }; + + return ws; +} diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 0000000..e7540a6 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2021", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2021", "DOM", "DOM.Iterable"], + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "experimentalDecorators": true, + "useDefineForClassFields": false, + "declaration": true, + "sourceMap": true, + "isolatedModules": true + }, + "include": ["src"] +} diff --git a/ui/vite.config.ts b/ui/vite.config.ts new file mode 100644 index 0000000..ffd5372 --- /dev/null +++ b/ui/vite.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, 'src/index.ts'), + name: 'CoreBuild', + fileName: 'core-build', + formats: ['es'], + }, + outDir: resolve(__dirname, '../pkg/api/ui/dist'), + emptyOutDir: true, + rollupOptions: { + output: { + entryFileNames: 'core-build.js', + }, + }, + }, +});