Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ jobs:
go-version: ${{ matrix.go-version }}
cache: true

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20.19.0'
cache: 'npm'
cache-dependency-path: webui/package-lock.json

- name: Install web UI dependencies
run: npm ci
working-directory: webui

- name: Build web UI
run: npm run build
working-directory: webui

- name: Download dependencies
run: go mod download

Expand Down Expand Up @@ -99,6 +114,21 @@ jobs:
go-version: '1.24'
cache: true

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20.19.0'
cache: 'npm'
cache-dependency-path: webui/package-lock.json

- name: Install web UI dependencies
run: npm ci
working-directory: webui

- name: Build web UI
run: npm run build
working-directory: webui

- name: Download dependencies
run: go mod download

Expand Down Expand Up @@ -168,6 +198,21 @@ jobs:
go-version: '1.24'
cache: true

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20.19.0'
cache: 'npm'
cache-dependency-path: webui/package-lock.json

- name: Install web UI dependencies
run: npm ci
working-directory: webui

- name: Build web UI
run: npm run build
working-directory: webui

- name: Build Linux
run: GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o xsql-linux-amd64 ./cmd/xsql

Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ jobs:
go-version: "1.24"
cache: true

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20.19.0"
cache: "npm"
cache-dependency-path: webui/package-lock.json

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
Expand All @@ -47,7 +54,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20"
node-version: "20.19.0"
registry-url: "https://registry.npmjs.org"

- name: Configure npm auth
Expand All @@ -59,4 +66,3 @@ jobs:
run: node scripts/npm-publish.js "${{ github.ref_name }}"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
npm/*/bin/xsql
npm/*/bin/xsql.exe
coverage.txt
webui/node_modules/
webui/dist/*
!webui/dist/.keep
8 changes: 5 additions & 3 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ version: 2

project_name: xsql

before:
hooks:
- go mod tidy
before:
hooks:
- npm --prefix webui ci
- npm --prefix webui run build
- go mod tidy

builds:
- id: xsql
Expand Down
49 changes: 49 additions & 0 deletions cmd/xsql/command_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,55 @@ func TestNormalizeErr(t *testing.T) {
}
}

func TestResolveWebOptions_DefaultLoopback(t *testing.T) {
resolved, xe := resolveWebOptions(&webCommandOptions{}, config.File{})
if xe != nil {
t.Fatalf("unexpected error: %v", xe)
}
if resolved.addr != "127.0.0.1:8788" {
t.Fatalf("addr=%q", resolved.addr)
}
if resolved.authRequired {
t.Fatal("loopback address should not require auth")
}
}

func TestResolveWebOptions_RemoteRequiresToken(t *testing.T) {
_, xe := resolveWebOptions(&webCommandOptions{
addr: "0.0.0.0:8788",
addrSet: true,
}, config.File{})
if xe == nil {
t.Fatal("expected error")
}
if xe.Code != errors.CodeCfgInvalid {
t.Fatalf("code=%s", xe.Code)
}
}

func TestResolveWebOptions_ConfigToken(t *testing.T) {
resolved, xe := resolveWebOptions(&webCommandOptions{
addr: "0.0.0.0:8788",
addrSet: true,
}, config.File{
Web: config.WebConfig{
HTTP: config.WebHTTPConfig{
AuthToken: "token",
AllowPlaintextToken: true,
},
},
})
if xe != nil {
t.Fatalf("unexpected error: %v", xe)
}
if !resolved.authRequired {
t.Fatal("expected authRequired=true")
}
if resolved.authToken != "token" {
t.Fatalf("authToken=%q", resolved.authToken)
}
}

func TestRun_SpecCommandSuccess(t *testing.T) {
prev := GlobalConfig
GlobalConfig = &Config{}
Expand Down
2 changes: 2 additions & 0 deletions cmd/xsql/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func run() int {
root.AddCommand(NewMCPCommand())
root.AddCommand(NewProxyCommand(&w))
root.AddCommand(NewConfigCommand(&w))
root.AddCommand(NewServeCommand(&w))
root.AddCommand(NewWebCommand(&w))

// Execute and handle errors
if err := root.Execute(); err != nil {
Expand Down
55 changes: 4 additions & 51 deletions cmd/xsql/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package main
import (
"github.com/spf13/cobra"

"github.com/zx06/xsql/internal/app"
"github.com/zx06/xsql/internal/config"
"github.com/zx06/xsql/internal/errors"
"github.com/zx06/xsql/internal/output"
)

Expand Down Expand Up @@ -32,23 +32,13 @@ func newProfileListCommand(w *output.Writer) *cobra.Command {
return err
}

cfg, cfgPath, xe := config.LoadConfig(config.Options{
result, xe := app.LoadProfiles(config.Options{
ConfigPath: GlobalConfig.ConfigStr,
})
if xe != nil {
return xe
}

profiles := make([]config.ProfileInfo, 0, len(cfg.Profiles))
for name, p := range cfg.Profiles {
profiles = append(profiles, config.ProfileToInfo(name, p))
}

result := map[string]any{
"config_path": cfgPath,
"profiles": profiles,
}

return w.WriteOK(format, result)
},
}
Expand All @@ -67,50 +57,13 @@ func newProfileShowCommand(w *output.Writer) *cobra.Command {
return err
}

cfg, cfgPath, xe := config.LoadConfig(config.Options{
result, xe := app.LoadProfileDetail(config.Options{
ConfigPath: GlobalConfig.ConfigStr,
})
}, name)
if xe != nil {
return xe
}

profile, ok := cfg.Profiles[name]
if !ok {
return errors.New(errors.CodeCfgInvalid, "profile not found", map[string]any{"name": name})
}

// Redact sensitive information: hide password
result := map[string]any{
"config_path": cfgPath,
"name": name,
"description": profile.Description,
"db": profile.DB,
"host": profile.Host,
"port": profile.Port,
"user": profile.User,
"database": profile.Database,
"unsafe_allow_write": profile.UnsafeAllowWrite,
"allow_plaintext": profile.AllowPlaintext,
}

if profile.DSN != "" {
result["dsn"] = "***"
}
if profile.Password != "" {
result["password"] = "***"
}
if profile.SSHProxy != "" {
result["ssh_proxy"] = profile.SSHProxy
if proxy, ok := cfg.SSHProxies[profile.SSHProxy]; ok {
result["ssh_host"] = proxy.Host
result["ssh_port"] = proxy.Port
result["ssh_user"] = proxy.User
if proxy.IdentityFile != "" {
result["ssh_identity_file"] = proxy.IdentityFile
}
}
}

return w.WriteOK(format, result)
},
}
Expand Down
27 changes: 4 additions & 23 deletions cmd/xsql/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"github.com/spf13/cobra"

"github.com/zx06/xsql/internal/app"
"github.com/zx06/xsql/internal/db"
"github.com/zx06/xsql/internal/errors"
"github.com/zx06/xsql/internal/output"
)

Expand Down Expand Up @@ -54,33 +52,16 @@ func runQuery(cmd *cobra.Command, args []string, flags *QueryFlags, w *output.Wr
}

p := GlobalConfig.Resolved.Profile
if p.DB == "" {
return errors.New(errors.CodeCfgInvalid, "db type is required (mysql|pg)", nil)
}

timeout := DefaultQueryTimeout
if flags.QueryTimeoutSet && flags.QueryTimeout > 0 {
timeout = time.Duration(flags.QueryTimeout) * time.Second
} else if p.QueryTimeout > 0 {
timeout = time.Duration(p.QueryTimeout) * time.Second
}
timeout := app.QueryTimeout(p, flags.QueryTimeout, flags.QueryTimeoutSet, DefaultQueryTimeout)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

conn, xe := app.ResolveConnection(ctx, app.ConnectionOptions{
result, xe := app.Query(ctx, app.QueryRequest{
Profile: p,
SQL: sql,
AllowPlaintext: flags.AllowPlaintext,
SkipHostKeyCheck: flags.SSHSkipHostKey,
})
if xe != nil {
return xe
}
defer func() { _ = conn.Close() }()

unsafeAllowWrite := flags.UnsafeAllowWrite || p.UnsafeAllowWrite
result, xe := db.Query(ctx, conn.DB, sql, db.QueryOptions{
UnsafeAllowWrite: unsafeAllowWrite,
DBType: p.DB,
UnsafeAllowWrite: flags.UnsafeAllowWrite || p.UnsafeAllowWrite,
})
if xe != nil {
return xe
Expand Down
28 changes: 4 additions & 24 deletions cmd/xsql/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"github.com/spf13/cobra"

"github.com/zx06/xsql/internal/app"
"github.com/zx06/xsql/internal/db"
"github.com/zx06/xsql/internal/errors"
"github.com/zx06/xsql/internal/output"
)

Expand Down Expand Up @@ -67,38 +65,20 @@ func runSchemaDump(cmd *cobra.Command, args []string, flags *SchemaFlags, w *out
}

p := GlobalConfig.Resolved.Profile
if p.DB == "" {
return errors.New(errors.CodeCfgInvalid, "db type is required (mysql|pg)", nil)
}

timeout := DefaultSchemaTimeout
if flags.SchemaTimeoutSet && flags.SchemaTimeout > 0 {
timeout = time.Duration(flags.SchemaTimeout) * time.Second
} else if p.SchemaTimeout > 0 {
timeout = time.Duration(p.SchemaTimeout) * time.Second
}
timeout := app.SchemaTimeout(p, flags.SchemaTimeout, flags.SchemaTimeoutSet, DefaultSchemaTimeout)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

conn, xe := app.ResolveConnection(ctx, app.ConnectionOptions{
result, xe := app.DumpSchema(ctx, app.SchemaDumpRequest{
Profile: p,
TablePattern: flags.TablePattern,
IncludeSystem: flags.IncludeSystem,
AllowPlaintext: flags.AllowPlaintext,
SkipHostKeyCheck: flags.SSHSkipHostKey,
})
if xe != nil {
return xe
}
defer func() { _ = conn.Close() }()

schemaOpts := db.SchemaOptions{
TablePattern: flags.TablePattern,
IncludeSystem: flags.IncludeSystem,
}

result, xe := db.DumpSchema(ctx, p.DB, conn.DB, schemaOpts)
if xe != nil {
return xe
}

return w.WriteOK(format, result)
}
Loading
Loading