ide

package
v0.0.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package ide implements the project/file/process-management backend for the browser playground's "Projects" mode: scaffolding, editing, and running multi-file SPL applications shaped like examples/app, as real directories on disk. It is imported by cmd/interpreter's --playground mode, parameterized by a RunnerConfig supplying the interpreter command and scaffold kind.

Index

Constants

View Source
const (
	ScaffoldMinimal = "minimal"
	ScaffoldApp     = "app"
)

Scaffold kinds. Both run under cmd/interpreter: "minimal" is a bare single-file script with no database/template dependency; "app" mirrors examples/app and showcases the richer SQLite + builtins/template surface.

Variables

This section is empty.

Functions

func DeleteFile

func DeleteFile(projectDir, relPath string) error

DeleteFile removes a project file or directory (recursively).

func DetectRepoRoot

func DetectRepoRoot() (string, error)

DetectRepoRoot locates the interpreter repo root (the directory holding the root go.mod for "github.com/oarkflow/interpreter") by walking up from this source file's own compile-time path. This only works for ordinary `go build`/`go run` (which embed real source paths); a binary built with -trimpath won't have a usable path here, so callers should fall back to RunnerConfig.RepoRoot (or BinaryPath) in that case.

Note for cmd/interpreter (and any other nested package with its own go.mod): it is a separate Go module, so `go build` for it must run with cmd.Dir set to that module's own directory (e.g. filepath.Join(DetectRepoRoot(), "cmd", "interpreter")) with BuildPackage ".", not "./cmd/interpreter" from the root.

func ReadFile

func ReadFile(projectDir, relPath string) (string, error)

ReadFile reads a project file as text. Returns an error for binary content or files over maxTextFileBytes - the browser editor only handles text.

func RenameFile

func RenameFile(projectDir, fromRel, toRel string) error

RenameFile moves a project file/dir from one relative path to another.

func SafeJoin

func SafeJoin(baseDir, requestedPath string) (string, error)

SafeJoin resolves requestedPath against baseDir and guarantees the result stays inside baseDir - rejecting absolute paths, ".." traversal, and symlink escapes. baseDir must already be an absolute, existing directory. Centralizes the containment check that every file-tree/read/write/delete/ rename endpoint needs, rather than re-implementing an ad hoc ".." check per handler.

func Scaffold

func Scaffold(kind, projectDir, moduleName string) error

Scaffold copies the embedded template tree for kind into projectDir (which must already exist) and rewrites spl.mod's module field to moduleName.

func ValidScaffoldKind

func ValidScaffoldKind(kind string) bool

ValidScaffoldKind reports whether kind is a known scaffold.

func WriteFile

func WriteFile(projectDir, relPath, content string) error

WriteFile creates or overwrites a project file, creating parent directories as needed.

Types

type AuthChecker

type AuthChecker func(r *http.Request) bool

AuthChecker reports whether an incoming request is authenticated. pkg/playgroundserver passes in its own existing authManager-backed check so pkg/ide doesn't need to depend on that package-specific type. A nil AuthChecker means "no auth required" (e.g. PLAYGROUND_AUTH_SECRET unset).

type FileNode

type FileNode struct {
	Path     string      `json:"path"` // relative to the project root, forward-slash separated
	Name     string      `json:"name"`
	Type     string      `json:"type"` // "file" or "dir"
	Size     int64       `json:"size,omitempty"`
	Children []*FileNode `json:"children,omitempty"`
}

FileNode is one entry in a project's file tree.

func FileTree

func FileTree(projectDir string) (*FileNode, error)

FileTree walks projectDir and returns its structure, skipping the interpreter's own runtime artifacts (spl.lock is kept - it's meaningful project state) and any dotfile directories such as a stray .git.

type LogLine

type LogLine struct {
	Stream string    `json:"stream"`
	Line   string    `json:"line"`
	Time   time.Time `json:"ts"`
}

LogLine is one captured stdout/stderr line from a managed subprocess.

type ManagedProcess

type ManagedProcess struct {
	// contains filtered or unexported fields
}

ManagedProcess is one project's subprocess and its captured logs. Every project runs as its own OS process (never in-process via pkg/eval): the interpreter's StartCLI/listen() shutdown-hook registry is a process-wide global (see object.RegisterShutdownHook), so two projects' servers sharing one Go process would corrupt each other's graceful shutdown.

type ProcessManager

type ProcessManager struct {
	// contains filtered or unexported fields
}

ProcessManager starts/stops/restarts one subprocess per project and fans out its logs to SSE subscribers.

func NewProcessManager

func NewProcessManager(cfg RunnerConfig, registry *Registry) *ProcessManager

func (*ProcessManager) Restart

func (m *ProcessManager) Restart(project *Project) (ProcessStatus, error)

Restart stops (if running) then starts a project.

func (*ProcessManager) Start

func (m *ProcessManager) Start(project *Project) (ProcessStatus, error)

Start launches project's main.spl as a subprocess. If already running, returns an error - call Restart instead.

func (*ProcessManager) Status

func (m *ProcessManager) Status(projectID string) ProcessStatus

Status returns the current status for a project, StateIdle if it has never been started.

func (*ProcessManager) Stop

func (m *ProcessManager) Stop(projectID string) (ProcessStatus, error)

Stop sends SIGTERM to the process group, waits up to the configured grace period, then SIGKILLs it. Mirrors killProcessGroup's pattern elsewhere in this codebase (untrusted.go), adapted from a one-shot worker's hard timeout to a user-triggered graceful-then-forced stop.

func (*ProcessManager) StopAll

func (m *ProcessManager) StopAll(ctx context.Context)

StopAll stops every currently-running managed process, used when the playground server itself is shutting down so no orphaned child SPL processes survive it.

func (*ProcessManager) Subscribe

func (m *ProcessManager) Subscribe(projectID string) ([]LogLine, chan LogLine, func())

Subscribe returns the log backlog plus a live channel for a project.

type ProcessState

type ProcessState string

ProcessState is the lifecycle state of one project's managed subprocess.

const (
	StateIdle     ProcessState = "idle"
	StateStarting ProcessState = "starting"
	StateRunning  ProcessState = "running"
	StateStopping ProcessState = "stopping"
	StateStopped  ProcessState = "stopped"
	StateCrashed  ProcessState = "crashed"
)

type ProcessStatus

type ProcessStatus struct {
	State         ProcessState `json:"state"`
	Port          int          `json:"port,omitempty"`
	PID           int          `json:"pid,omitempty"`
	StartedAt     *time.Time   `json:"started_at,omitempty"`
	UptimeMS      int64        `json:"uptime_ms,omitempty"`
	LastExitError string       `json:"last_exit_error,omitempty"`
}

ProcessStatus is the JSON-friendly snapshot returned by the status/start/ stop/restart HTTP endpoints.

type Project

type Project struct {
	ID           string    `json:"id"`
	Slug         string    `json:"slug"`
	Name         string    `json:"name"`
	ScaffoldKind string    `json:"scaffold_kind"`
	Dir          string    `json:"dir"` // relative to the workspace root
	Port         int       `json:"port"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
}

Project is one IDE-managed SPL application, scaffolded under a Registry's workspace root.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry tracks project metadata in a single JSON index file at <workspaceRoot>/.spl-ide/projects.json. Actual project files live at <workspaceRoot>/<project.Dir>/...

func NewRegistry

func NewRegistry(workspaceRoot string) (*Registry, error)

NewRegistry loads (or creates) the project index under workspaceRoot, which is created if it does not already exist.

func (*Registry) Create

func (r *Registry) Create(name, scaffoldKind string, scaffold func(dir string) error) (*Project, error)

Create registers a new project with a unique slug/dir and calls scaffold(absoluteProjectDir) to populate its files. If scaffold returns an error, the partially-created directory and registry entry are removed.

func (*Registry) Delete

func (r *Registry) Delete(id string) error

Delete removes a project's registry entry and its on-disk directory.

func (*Registry) Get

func (r *Registry) Get(id string) (*Project, bool)

Get returns a copy of the project with the given id.

func (*Registry) List

func (r *Registry) List() []*Project

List returns all projects in creation order.

func (*Registry) ProjectDir

func (r *Registry) ProjectDir(p *Project) string

ProjectDir returns the absolute directory for a project.

func (*Registry) Root

func (r *Registry) Root() string

Root returns the absolute workspace root directory.

func (*Registry) UpdatePort

func (r *Registry) UpdatePort(id string, port int) error

UpdatePort persists the last-assigned port for a project.

type RunnerConfig

type RunnerConfig struct {
	// Variant is a human-readable label (e.g. "full").
	Variant string
	// BinaryPath, if set, is used directly instead of building - an escape
	// hatch for deployments that ship a prebuilt cmd/interpreter.
	BinaryPath string
	// RepoRoot is used as cmd.Dir for the one-time `go build`. cmd/interpreter
	// is its own Go module, so this should point at its directory (e.g.
	// filepath.Join(DetectRepoRoot(), "cmd", "interpreter")) with
	// BuildPackage ".", not the bare repo root with "./cmd/interpreter". If
	// empty, it's auto-detected from this package's own compiled source
	// location (the bare repo root - only correct if BuildPackage is a
	// root-module package).
	RepoRoot string
	// BuildPackage is the package to build, relative to RepoRoot, e.g. "."
	// when RepoRoot already points at cmd/interpreter.
	BuildPackage string
	// CacheDir is where the built binary is placed. If empty, defaults to
	// <workspaceRoot>/.spl-ide/bin.
	CacheDir string
	// ScaffoldKind is the scaffold this binary's "create project" defaults
	// to (it may still allow the other kind explicitly).
	ScaffoldKind string
	// GraceStop is how long Stop waits after SIGTERM before SIGKILL.
	GraceStop time.Duration
	// StartupProbe is how long Start waits for the assigned port to accept
	// a connection before giving up and reporting StateCrashed.
	StartupProbe time.Duration
}

RunnerConfig parameterizes ProcessManager: which interpreter binary to run projects with and which scaffold kind those projects default to.

Projects are plain SPL scaffolds with no go.mod of their own, and the interpreter's own relative-path config resolution (VIEWS_DIR, PUBLIC_DIR, DB_PATH, ...) requires the child process's OS working directory to be the project directory - so `go run <import path>` with cmd.Dir set to the project dir does not work (Go's module resolution needs a go.mod at or above the process's cwd, which a scaffolded project doesn't have). Instead, ProcessManager builds a real binary once (via `go build`, run with cmd.Dir set to the interpreter repo) and caches it, then execs that binary directly with cmd.Dir set to the project directory.

type Server

type Server struct {
	Registry  *Registry
	Processes *ProcessManager
	Tooling   *ToolingService
	Cfg       RunnerConfig
	Auth      AuthChecker
}

Server wires together the Registry, ProcessManager, and ToolingService behind the /api/projects/* HTTP surface used by cmd/interpreter's --playground mode.

func NewServer

func NewServer(cfg RunnerConfig, workspaceRoot string, auth AuthChecker) (*Server, error)

NewServer creates a Server rooted at workspaceRoot.

func (*Server) Routes

func (s *Server) Routes(mux *http.ServeMux)

Routes registers every /api/projects/... endpoint onto mux, using Go's stdlib method+wildcard ServeMux patterns (Go 1.22+).

type ToolingService

type ToolingService struct {
	// contains filtered or unexported fields
}

ToolingService caches one tooling.WorkspaceIndex per project so the browser editor's completion/hover/diagnostics requests get real, import-aware, multi-file results - the same engine cmd/spltool's LSP server (used by the VS Code extension) already relies on, just exposed over HTTP instead of JSON-RPC.

func NewToolingService

func NewToolingService() *ToolingService

func (*ToolingService) Completions

func (s *ToolingService) Completions(projectID, projectDir, path, src, prefix string) []tooling.CompletionItem

func (*ToolingService) Diagnostics

func (s *ToolingService) Diagnostics(projectID, projectDir, path, src string) []tooling.Diagnostic

func (*ToolingService) Drop

func (s *ToolingService) Drop(projectID string)

Drop discards a project's entire tooling index, e.g. when the project itself is deleted.

func (*ToolingService) Forget

func (s *ToolingService) Forget(projectID, projectDir, path string)

Forget removes a file from a project's index (e.g. after a delete/rename).

func (*ToolingService) Hover

func (s *ToolingService) Hover(projectID, projectDir, path, src string, line, col int) string

func (*ToolingService) Touch

func (s *ToolingService) Touch(projectID, projectDir, path, src string)

Touch refreshes a project's index for one file's current (possibly unsaved) contents - called on every file write and on every completion/ hover/diagnostics request that carries an in-editor buffer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL