Files
onyx/tools/ods/cmd/root.go
roshan a8033533d1 refactor(ods): unify launchers and protect shared infrastructure (#16246)
## Description

Make ODS reuse shared development infrastructure by default, with
`--isolated` for separate datasets.

- Use the same service specifications and launchers for `stack`, `web
dev`, and `backend` commands. Remove the duplicate `stack
--frontend-only` option.
- Enforce project ownership across checkouts and resolve database
commands to the actual managed database.
- Stop services before pending migrations, preserve them when already at
head, and reject manual migrations beneath managed services.
- Harden startup cancellation, shutdown grace, descendant cleanup,
terminal input, and frontend API validation. Preserve Windows foreground
commands.
- Consolidate environment helpers, protect symlinked environment files,
propagate restore errors, and update help and documentation.

This PR contains only ODS and its development-stack skill. It does not
include the plugins implementation.

## How Has This Been Tested?

- Targeted command and regression tests using fake tools and disposable
Git repositories.
- Race tests for runtime targeting, ownership, migration ordering,
process cleanup, and terminal input.
- `go vet ./...`, pre-commit checks, native builds, and Windows
cross-builds.
- The full Go suite was run. Its remaining failures are the existing
macOS devcontainer SSH-mount fixture expectations in
`TestDevcontainerCommands` and `TestRunDevRebuild`.

No real database migrations, restores, or development-stack restarts
ran. Native Windows execution remains untested.

## Additional Options

- [ ] [Optional] Please cherry-pick this PR to the latest release
version.
- [x] [Optional] Override Linear Check

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
ODS now shares development infrastructure across worktrees by default,
with `--isolated` for separate datasets. `stack`, `web dev`, and
`backend` use the same service specs and launchers, and the duplicate
`stack --frontend-only` option is removed.

- Direct launches run the same dependency-prepare step as stack startup,
so `backend` and `web dev` work with fresh or stale Python environments.
- A stopped preview discards its former project state before retargeting
to another API owner.
- Enforces project ownership across checkouts and resolves database
commands to the managed database. New tests reject conflicting targets
before any mutation.
- Stops services before pending migrations, preserves them when already
at head, and rejects manual migrations beneath managed services.
Migration probe failures fail closed.
- Hardens startup cancellation, shutdown grace, and descendant cleanup;
ODS keeps the terminal foreground group so Ctrl-C still honors shutdown
timeouts, and tmux pane identifiers no longer change service identity.
- Database passwords reach tools through the child environment only,
never in process arguments; Windows foreground commands are preserved.
- Consolidates environment helpers, protects symlinked env files,
propagates restore errors, and refreshes help and documentation. Test
commits exercise the new migration-target and preview-launch paths.

<sup>Written for commit 14500cb051.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/onyx-dot-app/onyx/pull/16246?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="View guided diff"
src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a>
<a
href="https://www.cubic.dev/action/auto-fix/pr/onyx-dot-app/onyx/16246?returnTo=https%3A%2F%2Fgithub.com%2Fonyx-dot-app%2Fonyx%2Fpull%2F16246&source=description"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img
alt="Turn on auto-fix"
src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added the global `--isolated` option for separate development stacks;
shared infrastructure is the default, with clearer project ownership and
status information.
* Added `ods web dev` previews that can use a healthy managed API on
macOS and Linux, plus foreground startup on Windows.
* Expanded command guidance for development workflows, database
targeting, and dump formats.
* **Bug Fixes**
* Pending migrations now stop managed application services first;
healthy services remain running when no migrations are needed.
* Database operations verify project ownership and target selection
before making changes. Restore errors now fail the command, SQL restores
stop on errors, and temporary files are cleaned up after failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-10-19 20:07:02 +00:00

116 lines
3.6 KiB
Go

package cmd
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
Version string
Commit string
// rootProject is the --project flag, read by commands that derive their
// own project name when it is empty.
rootProject string
rootIsolated bool
)
func flagProjectName() string { return rootProject }
// RootOptions holds options for the root command.
type RootOptions struct {
Debug bool
Project string
Isolated bool
}
// NewRootCommand creates the root command.
func NewRootCommand() *cobra.Command {
opts := &RootOptions{}
cmd := &cobra.Command{
Use: "ods ",
Short: "Developer utilities for working on onyx.app",
Run: rootCmd,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if opts.Debug {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.InfoLevel)
}
log.SetFormatter(&log.TextFormatter{
DisableTimestamp: true,
})
rootProject = opts.Project
rootIsolated = opts.Isolated
},
Version: fmt.Sprintf("%s\ncommit %s", Version, Commit),
}
cmd.PersistentFlags().BoolVar(&opts.Debug, "debug", false, "run in debug mode")
cmd.PersistentFlags().StringVar(&opts.Project, "project", "", "Infrastructure project (default: saved shared or isolated target)")
cmd.PersistentFlags().BoolVar(&opts.Isolated, "isolated", false, "Use this worktree's separate infrastructure and data")
// Add subcommands
cmd.AddCommand(NewAuditCommand())
cmd.AddCommand(NewBackendCommand())
cmd.AddCommand(NewCheckGetattrCommand())
cmd.AddCommand(NewCheckLazyImportsCommand())
cmd.AddCommand(NewCherryPickCommand())
cmd.AddCommand(NewDBCommand())
cmd.AddCommand(NewDeployCommand())
cmd.AddCommand(NewOpenAPICommand())
cmd.AddCommand(NewComposeCommand())
cmd.AddCommand(NewGenerateComposeCommand())
cmd.AddCommand(NewEnvCommand())
cmd.AddCommand(NewObjectStoreCommand())
cmd.AddCommand(NewFmtCommand())
cmd.AddCommand(NewLintCommand())
cmd.AddCommand(NewLogsCommand())
cmd.AddCommand(NewPullCommand())
cmd.AddCommand(NewRunCICommand())
cmd.AddCommand(NewScreenshotDiffCommand())
cmd.AddCommand(NewStackCommand())
cmd.AddCommand(NewTestCommand())
cmd.AddCommand(NewCoverageCommand())
cmd.AddCommand(NewTypeCoverageCommand())
cmd.AddCommand(NewDesktopCommand())
cmd.AddCommand(NewDevCommand())
cmd.AddCommand(NewWebCommand())
cmd.AddCommand(NewWhoisCommand())
cmd.AddCommand(NewTraceCommand())
cmd.AddCommand(NewUpdateBaseDigestsCommand())
cmd.AddCommand(NewInstallSkillCommand())
cmd.AddCommand(NewReleaseCommand())
groups := map[string][]string{
"local": {"stack", "backend", "web", "desktop", "dev"},
"infra": {"compose", "env", "logs", "pull", "db", "object-store"},
"quality": {"test", "coverage", "type-coverage", "fmt", "lint", "check-getattr", "check-lazy-imports", "audit"},
"ci": {"run-ci", "trace", "screenshot-diff"},
"maintenance": {"generate-compose", "openapi", "update-base-digests", "install-skill"},
"operations": {"deploy", "release", "cherry-pick", "whois"},
}
for _, group := range []struct{ id, title string }{{"local", "Local development:"}, {"infra", "Infrastructure and data:"}, {"quality", "Checks and tests:"}, {"ci", "CI diagnostics:"}, {"maintenance", "Generation and setup:"}, {"operations", "Release and operations:"}} {
cmd.AddGroup(&cobra.Group{ID: group.id, Title: group.title})
}
for _, child := range cmd.Commands() {
for group, names := range groups {
for _, name := range names {
if child.Name() == name {
child.GroupID = group
}
}
}
}
cmd.SetHelpCommandGroupID("maintenance")
cmd.SetCompletionCommandGroupID("maintenance")
return cmd
}
func rootCmd(cmd *cobra.Command, args []string) {
_ = cmd.Help()
}