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
- func DeleteFile(projectDir, relPath string) error
- func DetectRepoRoot() (string, error)
- func ReadFile(projectDir, relPath string) (string, error)
- func RenameFile(projectDir, fromRel, toRel string) error
- func SafeJoin(baseDir, requestedPath string) (string, error)
- func Scaffold(kind, projectDir, moduleName string) error
- func ValidScaffoldKind(kind string) bool
- func WriteFile(projectDir, relPath, content string) error
- type AuthChecker
- type FileNode
- type LogLine
- type ManagedProcess
- type ProcessManager
- func (m *ProcessManager) Restart(project *Project) (ProcessStatus, error)
- func (m *ProcessManager) Start(project *Project) (ProcessStatus, error)
- func (m *ProcessManager) Status(projectID string) ProcessStatus
- func (m *ProcessManager) Stop(projectID string) (ProcessStatus, error)
- func (m *ProcessManager) StopAll(ctx context.Context)
- func (m *ProcessManager) Subscribe(projectID string) ([]LogLine, chan LogLine, func())
- type ProcessState
- type ProcessStatus
- type Project
- type Registry
- func (r *Registry) Create(name, scaffoldKind string, scaffold func(dir string) error) (*Project, error)
- func (r *Registry) Delete(id string) error
- func (r *Registry) Get(id string) (*Project, bool)
- func (r *Registry) List() []*Project
- func (r *Registry) ProjectDir(p *Project) string
- func (r *Registry) Root() string
- func (r *Registry) UpdatePort(id string, port int) error
- type RunnerConfig
- type Server
- type ToolingService
- func (s *ToolingService) Completions(projectID, projectDir, path, src, prefix string) []tooling.CompletionItem
- func (s *ToolingService) Diagnostics(projectID, projectDir, path, src string) []tooling.Diagnostic
- func (s *ToolingService) Drop(projectID string)
- func (s *ToolingService) Forget(projectID, projectDir, path string)
- func (s *ToolingService) Hover(projectID, projectDir, path, src string, line, col int) string
- func (s *ToolingService) Touch(projectID, projectDir, path, src string)
Constants ¶
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 ¶
DeleteFile removes a project file or directory (recursively).
func DetectRepoRoot ¶
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 ¶
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 ¶
RenameFile moves a project file/dir from one relative path to another.
func SafeJoin ¶
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 ¶
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 ¶
ValidScaffoldKind reports whether kind is a known scaffold.
Types ¶
type AuthChecker ¶
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.
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.
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 ¶
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) ProjectDir ¶
ProjectDir returns the absolute directory 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.
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.