mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
refactor(core): extract workspace paths
This commit is contained in:
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// TestAuthListRun_NotConfigured_ReturnsExitZero pins the contract that
|
||||
@@ -69,9 +69,9 @@ func TestAuthListRun_JSONMode_NotConfigured_WritesStdoutOnly(t *testing.T) {
|
||||
func TestAuthListRun_NotConfigured_AgentWorkspace_RoutesToBindHelp(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
prev := core.CurrentWorkspace()
|
||||
t.Cleanup(func() { core.SetCurrentWorkspace(prev) })
|
||||
core.SetCurrentWorkspace(core.WorkspaceOpenClaw)
|
||||
prev := workspace.CurrentWorkspace()
|
||||
t.Cleanup(func() { workspace.SetCurrentWorkspace(prev) })
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceOpenClaw)
|
||||
|
||||
f, _, stderr, _ := cmdutil.TestFactory(t, nil)
|
||||
if err := authListRun(&ListOptions{Factory: f}); err != nil {
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"regexp"
|
||||
|
||||
larkauth "github.com/larksuite/cli/internal/auth"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
var loginScopeCacheSafeChars = regexp.MustCompile(`[^a-zA-Z0-9._-]`)
|
||||
@@ -25,7 +25,7 @@ type loginScopeCacheRecord struct {
|
||||
// loginScopeCacheDir returns the directory used to persist auth login --no-wait
|
||||
// requested scopes keyed by device_code.
|
||||
func loginScopeCacheDir() string {
|
||||
return filepath.Join(core.GetConfigDir(), "cache", "auth_login_scopes")
|
||||
return filepath.Join(workspace.GetConfigDir(), "cache", "auth_login_scopes")
|
||||
}
|
||||
|
||||
// loginScopeCachePath returns the cache file path for a given device_code.
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// BindOptions holds all inputs for config bind.
|
||||
@@ -128,8 +129,8 @@ func configBindRun(opts *BindOptions) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
core.SetCurrentWorkspace(core.Workspace(source))
|
||||
targetConfigPath := core.GetConfigPath()
|
||||
workspace.SetCurrentWorkspace(workspace.Workspace(source))
|
||||
targetConfigPath := workspace.GetConfigPath()
|
||||
|
||||
existing, err := reconcileExistingBinding(opts, source, targetConfigPath)
|
||||
if err != nil {
|
||||
@@ -186,12 +187,12 @@ func finalizeSource(opts *BindOptions) (string, error) {
|
||||
}
|
||||
|
||||
var detected string
|
||||
switch core.DetectWorkspaceFromEnv(os.Getenv) {
|
||||
case core.WorkspaceOpenClaw:
|
||||
switch workspace.DetectWorkspaceFromEnv(os.Getenv) {
|
||||
case workspace.WorkspaceOpenClaw:
|
||||
detected = "openclaw"
|
||||
case core.WorkspaceHermes:
|
||||
case workspace.WorkspaceHermes:
|
||||
detected = "hermes"
|
||||
case core.WorkspaceLarkChannel:
|
||||
case workspace.WorkspaceLarkChannel:
|
||||
detected = "lark-channel"
|
||||
}
|
||||
|
||||
@@ -407,7 +408,7 @@ func priorLang(previousConfigBytes []byte) i18n.Lang {
|
||||
func commitBinding(opts *BindOptions, appConfig *core.AppConfig, previousConfigBytes []byte, source, configPath string) error {
|
||||
multi := &core.MultiAppConfig{Apps: []core.AppConfig{*appConfig}}
|
||||
|
||||
if err := vfs.MkdirAll(core.GetConfigDir(), 0700); err != nil {
|
||||
if err := vfs.MkdirAll(workspace.GetConfigDir(), 0700); err != nil {
|
||||
return errs.NewInternalError(errs.SubtypeFileIO, "failed to create workspace directory: %v", err).WithCause(err)
|
||||
}
|
||||
data, err := json.MarshalIndent(multi, "", " ")
|
||||
@@ -503,13 +504,13 @@ func tuiSelectSource(opts *BindOptions) (string, error) {
|
||||
var source string
|
||||
|
||||
// Pre-select based on detected env signals
|
||||
detected := core.DetectWorkspaceFromEnv(os.Getenv)
|
||||
detected := workspace.DetectWorkspaceFromEnv(os.Getenv)
|
||||
switch detected {
|
||||
case core.WorkspaceOpenClaw:
|
||||
case workspace.WorkspaceOpenClaw:
|
||||
source = "openclaw"
|
||||
case core.WorkspaceHermes:
|
||||
case workspace.WorkspaceHermes:
|
||||
source = "hermes"
|
||||
case core.WorkspaceLarkChannel:
|
||||
case workspace.WorkspaceLarkChannel:
|
||||
source = "lark-channel"
|
||||
default:
|
||||
source = "openclaw" // default first option
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/i18n"
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// wantErrDetail is the normalized comparison shape for a typed error's wire
|
||||
@@ -81,8 +82,8 @@ func assertEnvelope(t *testing.T, stdout []byte, want map[string]any) {
|
||||
// Must be called at the start of any test that may trigger configBindRun (which sets workspace).
|
||||
func saveWorkspace(t *testing.T) {
|
||||
t.Helper()
|
||||
orig := core.CurrentWorkspace()
|
||||
t.Cleanup(func() { core.SetCurrentWorkspace(orig) })
|
||||
orig := workspace.CurrentWorkspace()
|
||||
t.Cleanup(func() { workspace.SetCurrentWorkspace(orig) })
|
||||
}
|
||||
|
||||
// ── Command flag parsing tests (aligned with config_test.go pattern) ──
|
||||
@@ -640,7 +641,7 @@ func TestConfigBindRun_LarkChannel_Success(t *testing.T) {
|
||||
// Brand is not in the stdout envelope — read it back from the persisted
|
||||
// workspace config to verify accounts.app.tenant flowed through to the
|
||||
// stored AppConfig.Brand field.
|
||||
core.SetCurrentWorkspace(core.WorkspaceLarkChannel)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLarkChannel)
|
||||
multi, err := core.LoadMultiAppConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("load workspace config: %v", err)
|
||||
@@ -687,7 +688,7 @@ func TestConfigBindRun_LarkChannel_LarkTenant(t *testing.T) {
|
||||
if err := configBindRun(&BindOptions{Factory: f, Source: "lark-channel"}); err != nil {
|
||||
t.Fatalf("expected success, got error: %v", err)
|
||||
}
|
||||
core.SetCurrentWorkspace(core.WorkspaceLarkChannel)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLarkChannel)
|
||||
multi, err := core.LoadMultiAppConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("load workspace config: %v", err)
|
||||
@@ -802,7 +803,7 @@ func TestConfigShowRun_WorkspaceField(t *testing.T) {
|
||||
configDir := t.TempDir()
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", configDir)
|
||||
|
||||
core.SetCurrentWorkspace(core.WorkspaceLocal)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLocal)
|
||||
|
||||
multi := &core.MultiAppConfig{
|
||||
Apps: []core.AppConfig{{
|
||||
@@ -828,7 +829,7 @@ func TestConfigShowRun_AgentWorkspaceNotBound(t *testing.T) {
|
||||
saveWorkspace(t)
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
core.SetCurrentWorkspace(core.WorkspaceOpenClaw)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceOpenClaw)
|
||||
|
||||
f, _, _, _ := cmdutil.TestFactory(t, nil)
|
||||
err := configShowRun(&ConfigShowOptions{Factory: f})
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/i18n"
|
||||
"github.com/larksuite/cli/internal/keychain"
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// ConfigInitOptions holds all inputs for config init.
|
||||
@@ -122,7 +123,7 @@ func guardAgentWorkspace(opts *ConfigInitOptions) error {
|
||||
if opts.ForceInit {
|
||||
return nil
|
||||
}
|
||||
ws := core.DetectWorkspaceFromEnv(os.Getenv)
|
||||
ws := workspace.DetectWorkspaceFromEnv(os.Getenv)
|
||||
if ws.IsLocal() {
|
||||
return nil
|
||||
}
|
||||
@@ -346,7 +347,7 @@ func configInitRun(opts *ConfigInitOptions) error {
|
||||
if err := saveInitConfig(opts.ProfileName, existing, f, opts.AppID, secret, brand, opts.Lang); err != nil {
|
||||
return wrapSaveConfigError(err)
|
||||
}
|
||||
output.PrintSuccess(f.IOStreams.ErrOut, fmt.Sprintf("Configuration saved to %s", core.GetConfigPath()))
|
||||
output.PrintSuccess(f.IOStreams.ErrOut, fmt.Sprintf("Configuration saved to %s", workspace.GetConfigPath()))
|
||||
printLangPreferenceConfirmation(opts)
|
||||
output.PrintJson(f.IOStreams.Out, map[string]interface{}{"appId": opts.AppID, "appSecret": "****", "brand": brand})
|
||||
if err := runProbe(opts.Ctx, f, opts.AppID, opts.appSecret, brand); err != nil {
|
||||
@@ -521,7 +522,7 @@ func configInitRun(opts *ConfigInitOptions) error {
|
||||
if err := saveInitConfig(opts.ProfileName, existing, f, resolvedAppId, storedSecret, parseBrand(resolvedBrand), opts.Lang); err != nil {
|
||||
return wrapSaveConfigError(err)
|
||||
}
|
||||
output.PrintSuccess(f.IOStreams.ErrOut, fmt.Sprintf("Configuration saved to %s", core.GetConfigPath()))
|
||||
output.PrintSuccess(f.IOStreams.ErrOut, fmt.Sprintf("Configuration saved to %s", workspace.GetConfigPath()))
|
||||
printLangPreferenceConfirmation(opts)
|
||||
if appSecretInput != "" {
|
||||
if err := runProbe(opts.Ctx, f, resolvedAppId, appSecretInput, parseBrand(resolvedBrand)); err != nil {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -66,7 +67,7 @@ func configShowRun(opts *ConfigShowOptions) error {
|
||||
users = strings.Join(userStrs, ", ")
|
||||
}
|
||||
output.PrintJson(f.IOStreams.Out, map[string]interface{}{
|
||||
"workspace": core.CurrentWorkspace().Display(),
|
||||
"workspace": workspace.CurrentWorkspace().Display(),
|
||||
"profile": app.ProfileName(),
|
||||
"appId": app.AppId,
|
||||
"appSecret": "****",
|
||||
@@ -74,6 +75,6 @@ func configShowRun(opts *ConfigShowOptions) error {
|
||||
"lang": app.Lang,
|
||||
"users": users,
|
||||
})
|
||||
fmt.Fprintf(f.IOStreams.ErrOut, "\nConfig file path: %s\n", core.GetConfigPath())
|
||||
fmt.Fprintf(f.IOStreams.ErrOut, "\nConfig file path: %s\n", workspace.GetConfigPath())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/internal/transport"
|
||||
"github.com/larksuite/cli/internal/update"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// DoctorOptions holds inputs for the doctor command.
|
||||
@@ -240,7 +241,7 @@ func finishDoctor(f *cmdutil.Factory, checks []checkResult) error {
|
||||
|
||||
result := map[string]interface{}{
|
||||
"ok": allOK,
|
||||
"workspace": core.CurrentWorkspace().Display(),
|
||||
"workspace": workspace.CurrentWorkspace().Display(),
|
||||
"checks": checks,
|
||||
}
|
||||
output.PrintJson(f.IOStreams.Out, result)
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/event"
|
||||
"github.com/larksuite/cli/internal/event/bus"
|
||||
"github.com/larksuite/cli/internal/event/transport"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// NewCmdBus creates the hidden `event _bus` daemon subcommand, forked by the consume client; fork argv lives in consume/startup.go.
|
||||
@@ -35,7 +35,7 @@ func NewCmdBus(f *cmdutil.Factory) *cobra.Command {
|
||||
}
|
||||
|
||||
// Sanitize AppID: an unsanitized value could escape events/ via ".." or separators.
|
||||
eventsDir := filepath.Join(core.GetConfigDir(), "events", event.SanitizeAppID(cfg.AppID))
|
||||
eventsDir := filepath.Join(workspace.GetConfigDir(), "events", event.SanitizeAppID(cfg.AppID))
|
||||
|
||||
logger, err := bus.SetupBusLogger(eventsDir)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
|
||||
"github.com/larksuite/cli/extension/platform"
|
||||
"github.com/larksuite/cli/internal/cmdpolicy"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/hook"
|
||||
internalplatform "github.com/larksuite/cli/internal/platform"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// userPolicyFileName is the conventional filename for the user-layer Rule.
|
||||
@@ -261,7 +261,7 @@ func splitCSV(s string) []string {
|
||||
// userPolicyPath returns the path of <baseConfigDir>/policy.yml.
|
||||
//
|
||||
// The base directory honours LARKSUITE_CLI_CONFIG_DIR (via
|
||||
// core.GetBaseConfigDir) so that test isolation, container deployments
|
||||
// workspace.GetBaseConfigDir) so that test isolation, container deployments
|
||||
// and per-Agent config overrides all see a consistent policy location.
|
||||
// Using vfs.UserHomeDir directly here would silently bypass the env
|
||||
// override and route every test through the real ~/.lark-cli.
|
||||
@@ -271,7 +271,7 @@ func splitCSV(s string) []string {
|
||||
// the home dir can't be resolved, and the resolver already treats a
|
||||
// missing file as "no policy".
|
||||
func userPolicyPath() (string, error) {
|
||||
return filepath.Join(core.GetBaseConfigDir(), userPolicyFileName), nil
|
||||
return filepath.Join(workspace.GetBaseConfigDir(), userPolicyFileName), nil
|
||||
}
|
||||
|
||||
// warnPolicyError writes a one-line stderr warning when the user policy
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
"github.com/larksuite/cli/internal/build"
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -68,9 +68,9 @@ func TestOfferRootUpgrade(t *testing.T) {
|
||||
// workspace detection; pin the process-global workspace to Local so
|
||||
// statePath() resolves under LARKSUITE_CLI_CONFIG_DIR rather than a stale
|
||||
// subdir inherited from a prior test in the package.
|
||||
origWS := core.CurrentWorkspace()
|
||||
t.Cleanup(func() { core.SetCurrentWorkspace(origWS) })
|
||||
core.SetCurrentWorkspace(core.WorkspaceLocal)
|
||||
origWS := workspace.CurrentWorkspace()
|
||||
t.Cleanup(func() { workspace.SetCurrentWorkspace(origWS) })
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLocal)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/errclass"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
var safeIDChars = regexp.MustCompile(`[^a-zA-Z0-9._-]`)
|
||||
@@ -129,7 +130,7 @@ func refreshWithLock(httpClient *http.Client, opts UATCallOptions, stored *Store
|
||||
// 2. Cross-process lock using flock
|
||||
// We use the same underlying storage directory resolution as keychain_other.go
|
||||
// to ensure locks are isolated properly alongside other sensitive data.
|
||||
configDir := core.GetConfigDir()
|
||||
configDir := workspace.GetConfigDir()
|
||||
|
||||
lockDir := filepath.Join(configDir, "locks")
|
||||
if err := vfs.MkdirAll(lockDir, 0700); err != nil {
|
||||
|
||||
@@ -114,7 +114,7 @@ func Shared() *Logger {
|
||||
|
||||
func defaultRuntimeDir() string {
|
||||
if dir := os.Getenv("LARKSUITE_CLI_CONFIG_DIR"); dir != "" {
|
||||
// Deliberately unvalidated, mirroring core.GetBaseConfigDir. Routing this
|
||||
// Deliberately unvalidated, mirroring workspace.GetBaseConfigDir. Routing this
|
||||
// through validate.SafeEnvDirPath would also resolve symlinks, changing
|
||||
// the directory the CLI reports and uses on any host where the path
|
||||
// crosses one — applying it moved four packages' expectations from /var
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
_ "github.com/larksuite/cli/internal/security/contentsafety" // register content safety provider
|
||||
"github.com/larksuite/cli/internal/transport"
|
||||
_ "github.com/larksuite/cli/internal/vfs/localfileio" // register default FileIO provider
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// NewDefault creates a production Factory with cached closures.
|
||||
@@ -48,14 +49,14 @@ func NewDefault(streams *IOStreams, inv InvocationContext) *Factory {
|
||||
// Workspace detection: determines which config subtree to use.
|
||||
// Must run before any config or credential load, since those paths are
|
||||
// workspace-scoped. Default is WorkspaceLocal — existing behavior unchanged.
|
||||
ws := core.DetectWorkspaceFromEnv(os.Getenv)
|
||||
core.SetCurrentWorkspace(ws)
|
||||
ws := workspace.DetectWorkspaceFromEnv(os.Getenv)
|
||||
workspace.SetCurrentWorkspace(ws)
|
||||
|
||||
// Auth diagnostics: install the one logger the whole process shares, now
|
||||
// that the workspace is known. authlog cannot resolve the workspace-aware
|
||||
// directory itself (core imports keychain, which imports authlog), so this
|
||||
// is the only place that can supply it.
|
||||
authlog.SetShared(authlog.New(authlog.Options{RuntimeDir: core.GetRuntimeDir}))
|
||||
authlog.SetShared(authlog.New(authlog.Options{RuntimeDir: workspace.GetRuntimeDir}))
|
||||
|
||||
// Phase 0: FileIO provider (no dependency)
|
||||
f.FileIOProvider = fileio.GetProvider()
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/keychain"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// Identity represents the caller identity for API requests.
|
||||
@@ -182,19 +182,6 @@ func (c *CliConfig) CanBot() bool {
|
||||
return c.SupportedIdentities == 0 || c.SupportedIdentities&identityBotBit != 0
|
||||
}
|
||||
|
||||
// GetConfigDir returns the config directory path for the current workspace.
|
||||
// When workspace is local (default), this returns the same path as before
|
||||
// (LARKSUITE_CLI_CONFIG_DIR or ~/.lark-cli) — fully backward-compatible.
|
||||
// When workspace is openclaw/hermes, returns base/openclaw or base/hermes.
|
||||
func GetConfigDir() string {
|
||||
return GetRuntimeDir()
|
||||
}
|
||||
|
||||
// GetConfigPath returns the config file path for the current workspace.
|
||||
func GetConfigPath() string {
|
||||
return filepath.Join(GetConfigDir(), "config.json")
|
||||
}
|
||||
|
||||
// ErrMalformedConfig marks a config-load failure caused by malformed file
|
||||
// content (unparseable JSON, structurally empty) rather than a missing or
|
||||
// unreadable file. Callers classify with errors.Is rather than sniffing the
|
||||
@@ -203,7 +190,7 @@ var ErrMalformedConfig = errors.New("malformed config")
|
||||
|
||||
// LoadMultiAppConfig loads multi-app config from disk.
|
||||
func LoadMultiAppConfig() (*MultiAppConfig, error) {
|
||||
data, err := vfs.ReadFile(GetConfigPath())
|
||||
data, err := vfs.ReadFile(workspace.GetConfigPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -220,7 +207,7 @@ func LoadMultiAppConfig() (*MultiAppConfig, error) {
|
||||
|
||||
// SaveMultiAppConfig saves config to disk.
|
||||
func SaveMultiAppConfig(config *MultiAppConfig) error {
|
||||
dir := GetConfigDir()
|
||||
dir := workspace.GetConfigDir()
|
||||
if err := vfs.MkdirAll(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -228,7 +215,7 @@ func SaveMultiAppConfig(config *MultiAppConfig) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return validate.AtomicWrite(GetConfigPath(), append(data, '\n'), 0600)
|
||||
return validate.AtomicWrite(workspace.GetConfigPath(), append(data, '\n'), 0600)
|
||||
}
|
||||
|
||||
// RequireConfig loads the single-app config using the default profile resolution.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// isMalformedConfigError reports whether a config load failure indicates a
|
||||
@@ -69,8 +70,8 @@ const (
|
||||
// NotConfiguredError returns the canonical "not configured" error, with a
|
||||
// hint that depends on the active workspace:
|
||||
//
|
||||
// - WorkspaceLocal → suggest `config init --new` (creates a new app).
|
||||
// - WorkspaceOpenClaw / WorkspaceHermes → point at `config bind --help`
|
||||
// - workspace.WorkspaceLocal → suggest `config init --new` (creates a new app).
|
||||
// - workspace.WorkspaceOpenClaw / workspace.WorkspaceHermes → point at `config bind --help`
|
||||
// rather than a ready-to-run command, because binding is policy-laden:
|
||||
// the user must pick an identity preset (bot-only vs user-default),
|
||||
// and re-binding may overwrite an existing one. The help text walks
|
||||
@@ -79,7 +80,7 @@ const (
|
||||
// All "config not loaded yet" call sites should use this helper rather than
|
||||
// hand-rolling a hint, so AI agents always get a workspace-correct next step.
|
||||
func NotConfiguredError() error {
|
||||
ws := CurrentWorkspace()
|
||||
ws := workspace.CurrentWorkspace()
|
||||
if ws.IsLocal() {
|
||||
return errs.NewConfigError(errs.SubtypeNotConfigured, "not configured").
|
||||
WithHint("%s", localInitHint)
|
||||
@@ -97,7 +98,7 @@ func NotConfiguredError() error {
|
||||
// Agent → `config bind --help` so the AI reads the binding workflow and
|
||||
// confirms identity preset with the user before running the actual command.
|
||||
func reconfigureHint() string {
|
||||
if CurrentWorkspace().IsLocal() {
|
||||
if workspace.CurrentWorkspace().IsLocal() {
|
||||
return "please run `lark-cli config init` to reconfigure"
|
||||
}
|
||||
return agentBindHint
|
||||
@@ -108,7 +109,7 @@ func reconfigureHint() string {
|
||||
// workspaces a missing profile typically means the binding was wiped while
|
||||
// the workspace marker remained — re-binding is the correct fix, not init.
|
||||
func NoActiveProfileError() error {
|
||||
ws := CurrentWorkspace()
|
||||
ws := workspace.CurrentWorkspace()
|
||||
if ws.IsLocal() {
|
||||
return errs.NewConfigError(errs.SubtypeNotConfigured, "no active profile").
|
||||
WithHint("%s", localInitHint)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// saveAndRestoreWorkspace ensures package-level currentWorkspace is reset
|
||||
@@ -17,13 +18,13 @@ import (
|
||||
// accident.
|
||||
func saveAndRestoreWorkspace(t *testing.T) {
|
||||
t.Helper()
|
||||
prev := CurrentWorkspace()
|
||||
t.Cleanup(func() { SetCurrentWorkspace(prev) })
|
||||
prev := workspace.CurrentWorkspace()
|
||||
t.Cleanup(func() { workspace.SetCurrentWorkspace(prev) })
|
||||
}
|
||||
|
||||
func TestNotConfiguredError_Local(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceLocal)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLocal)
|
||||
|
||||
err := NotConfiguredError()
|
||||
var cfgErr *errs.ConfigError
|
||||
@@ -46,7 +47,7 @@ func TestNotConfiguredError_Local(t *testing.T) {
|
||||
|
||||
func TestNotConfiguredError_OpenClaw(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceOpenClaw)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceOpenClaw)
|
||||
|
||||
err := NotConfiguredError()
|
||||
var cfgErr *errs.ConfigError
|
||||
@@ -74,7 +75,7 @@ func TestNotConfiguredError_OpenClaw(t *testing.T) {
|
||||
|
||||
func TestNotConfiguredError_Hermes(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceHermes)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceHermes)
|
||||
|
||||
err := NotConfiguredError()
|
||||
var cfgErr *errs.ConfigError
|
||||
@@ -94,7 +95,7 @@ func TestNotConfiguredError_Hermes(t *testing.T) {
|
||||
|
||||
func TestNoActiveProfileError_Local(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceLocal)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLocal)
|
||||
|
||||
err := NoActiveProfileError()
|
||||
var cfgErr *errs.ConfigError
|
||||
@@ -108,7 +109,7 @@ func TestNoActiveProfileError_Local(t *testing.T) {
|
||||
|
||||
func TestNoActiveProfileError_AgentSuggestsBind(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceOpenClaw)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceOpenClaw)
|
||||
|
||||
err := NoActiveProfileError()
|
||||
var cfgErr *errs.ConfigError
|
||||
@@ -122,7 +123,7 @@ func TestNoActiveProfileError_AgentSuggestsBind(t *testing.T) {
|
||||
|
||||
func TestReconfigureHint_Local(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceLocal)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLocal)
|
||||
|
||||
got := reconfigureHint()
|
||||
if !strings.Contains(got, "config init") {
|
||||
@@ -132,7 +133,7 @@ func TestReconfigureHint_Local(t *testing.T) {
|
||||
|
||||
func TestReconfigureHint_Agent(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceHermes)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceHermes)
|
||||
|
||||
got := reconfigureHint()
|
||||
if !strings.Contains(got, "config bind --help") {
|
||||
@@ -142,7 +143,7 @@ func TestReconfigureHint_Agent(t *testing.T) {
|
||||
|
||||
func TestLoadOrNotConfigured_FileMissing_ReturnsNotConfigured(t *testing.T) {
|
||||
saveAndRestoreWorkspace(t)
|
||||
SetCurrentWorkspace(WorkspaceLocal)
|
||||
workspace.SetCurrentWorkspace(workspace.WorkspaceLocal)
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
_, err := LoadOrNotConfigured()
|
||||
|
||||
@@ -17,13 +17,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/event"
|
||||
"github.com/larksuite/cli/internal/event/busdiscover"
|
||||
"github.com/larksuite/cli/internal/event/protocol"
|
||||
"github.com/larksuite/cli/internal/event/source"
|
||||
"github.com/larksuite/cli/internal/event/transport"
|
||||
"github.com/larksuite/cli/internal/lockfile"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,7 +73,7 @@ func (b *Bus) Run(ctx context.Context) error {
|
||||
|
||||
// alive.lock before bind: closes the cleanup-TOCTOU race where two newly forked
|
||||
// buses each unlink and rebind the socket. Brief retry covers stop-then-restart.
|
||||
eventsDir := filepath.Join(core.GetConfigDir(), "events", event.SanitizeAppID(b.appID))
|
||||
eventsDir := filepath.Join(workspace.GetConfigDir(), "events", event.SanitizeAppID(b.appID))
|
||||
pidHandle, pidErr := acquireAliveLock(eventsDir)
|
||||
if pidErr != nil {
|
||||
if errors.Is(pidErr, lockfile.ErrHeld) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
type Process struct {
|
||||
@@ -22,7 +22,7 @@ type Scanner interface {
|
||||
}
|
||||
|
||||
func Default() Scanner {
|
||||
return &fsScanner{eventsDir: filepath.Join(core.GetConfigDir(), "events")}
|
||||
return &fsScanner{eventsDir: filepath.Join(workspace.GetConfigDir(), "events")}
|
||||
}
|
||||
|
||||
type fsScanner struct {
|
||||
|
||||
@@ -17,12 +17,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/event"
|
||||
"github.com/larksuite/cli/internal/event/protocol"
|
||||
"github.com/larksuite/cli/internal/event/transport"
|
||||
"github.com/larksuite/cli/internal/lockfile"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -64,7 +64,7 @@ func EnsureBus(ctx context.Context, tr transport.IPC, appID, profileName, domain
|
||||
// ErrHeld = another consume is forking; let dial retry catch its bus.
|
||||
pid, forkErr := forkBus(tr, appID, profileName, domain)
|
||||
if forkErr != nil && !errors.Is(forkErr, lockfile.ErrHeld) {
|
||||
eventsRoot := filepath.Join(core.GetConfigDir(), "events")
|
||||
eventsRoot := filepath.Join(workspace.GetConfigDir(), "events")
|
||||
return nil, errs.NewInternalError(errs.SubtypeUnknown,
|
||||
"failed to start event bus daemon: %s", forkErr).
|
||||
WithCause(forkErr).
|
||||
@@ -86,7 +86,7 @@ func EnsureBus(ctx context.Context, tr transport.IPC, appID, profileName, domain
|
||||
}
|
||||
}
|
||||
|
||||
logPath := filepath.Join(core.GetConfigDir(), "events", event.SanitizeAppID(appID), "bus.log")
|
||||
logPath := filepath.Join(workspace.GetConfigDir(), "events", event.SanitizeAppID(appID), "bus.log")
|
||||
fmt.Fprintln(errOut, "[event] event bus exited unexpectedly.")
|
||||
fmt.Fprintln(errOut, "[event] please check app credentials (lark-cli config show) and retry.")
|
||||
fmt.Fprintf(errOut, "[event] logs: %s\n", logPath)
|
||||
@@ -125,7 +125,7 @@ func probeAndDialBus(tr transport.IPC, addr string) (net.Conn, error) {
|
||||
|
||||
// forkBus holds bus.fork.lock until the spawned daemon is dial-able, so concurrent callers can't race past the empty-socket gap and fork independent buses.
|
||||
func forkBus(tr transport.IPC, appID, profileName, domain string) (int, error) {
|
||||
lockPath := filepath.Join(core.GetConfigDir(), "events", event.SanitizeAppID(appID), "bus.fork.lock")
|
||||
lockPath := filepath.Join(workspace.GetConfigDir(), "events", event.SanitizeAppID(appID), "bus.fork.lock")
|
||||
if err := vfs.MkdirAll(filepath.Dir(lockPath), 0700); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/event"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
const dialTimeout = 5 * time.Second // matches winio.DialPipe for cross-platform symmetry
|
||||
@@ -36,7 +36,7 @@ func (t *unixTransport) Dial(addr string) (net.Conn, error) {
|
||||
|
||||
// Address: NOT os.UserHomeDir — honours LARKSUITE_CLI_CONFIG_DIR override.
|
||||
func (t *unixTransport) Address(appID string) string {
|
||||
return filepath.Join(core.GetConfigDir(), "events", event.SanitizeAppID(appID), "bus.sock")
|
||||
return filepath.Join(workspace.GetConfigDir(), "events", event.SanitizeAppID(appID), "bus.sock")
|
||||
}
|
||||
|
||||
func (t *unixTransport) Cleanup(addr string) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// safeIDChars strips path-traversal chars from app IDs.
|
||||
@@ -34,7 +34,7 @@ func ForSubscribe(appID string) (*LockFile, error) {
|
||||
if appID == "" {
|
||||
return nil, fmt.Errorf("app ID must not be empty")
|
||||
}
|
||||
dir := filepath.Join(core.GetConfigDir(), "locks")
|
||||
dir := filepath.Join(workspace.GetConfigDir(), "locks")
|
||||
if err := vfs.MkdirAll(dir, 0700); err != nil {
|
||||
return nil, fmt.Errorf("create lock dir: %w", err)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ import (
|
||||
|
||||
"github.com/larksuite/cli/brand"
|
||||
"github.com/larksuite/cli/internal/build"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/meta"
|
||||
"github.com/larksuite/cli/internal/transport"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -96,7 +96,7 @@ func metaTTL() time.Duration {
|
||||
// --- cache path helpers ---
|
||||
|
||||
func cacheDir() string {
|
||||
return filepath.Join(core.GetConfigDir(), "cache")
|
||||
return filepath.Join(workspace.GetConfigDir(), "cache")
|
||||
}
|
||||
|
||||
func cachePath() string {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
|
||||
extcs "github.com/larksuite/cli/extension/contentsafety"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// regexProvider implements extcs.Provider using regex rules from config file.
|
||||
@@ -76,6 +76,6 @@ func (p *regexProvider) loadOrCreate(errOut io.Writer) (*Config, error) {
|
||||
|
||||
func init() {
|
||||
extcs.Register(®exProvider{
|
||||
configDir: core.GetConfigDir(),
|
||||
configDir: workspace.GetConfigDir(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,7 +31,7 @@ type SkillsState struct {
|
||||
}
|
||||
|
||||
func statePath() string {
|
||||
return filepath.Join(core.GetBaseConfigDir(), stateFile)
|
||||
return filepath.Join(workspace.GetBaseConfigDir(), stateFile)
|
||||
}
|
||||
|
||||
func ReadState() (*SkillsState, bool, error) {
|
||||
@@ -58,7 +58,7 @@ func ReadState() (*SkillsState, bool, error) {
|
||||
func WriteState(state SkillsState) error {
|
||||
state.ensureNonNilSlices()
|
||||
|
||||
if err := vfs.MkdirAll(core.GetBaseConfigDir(), 0o700); err != nil {
|
||||
if err := vfs.MkdirAll(workspace.GetBaseConfigDir(), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(state, "", " ")
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// storageRoot is the per-domain local-storage directory name under the config dir.
|
||||
@@ -47,7 +47,7 @@ func appKeyPath(appID, key string) string {
|
||||
|
||||
// Root returns the Spark application storage root under the CLI config directory.
|
||||
func Root() string {
|
||||
return filepath.Join(core.GetConfigDir(), storageRoot)
|
||||
return filepath.Join(workspace.GetConfigDir(), storageRoot)
|
||||
}
|
||||
|
||||
// Path returns the storage path for one application key.
|
||||
|
||||
@@ -23,13 +23,13 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/envvars"
|
||||
"github.com/larksuite/cli/internal/secaudit"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
// ConfigFileName is the fixed config file name under core.GetConfigDir().
|
||||
// ConfigFileName is the fixed config file name under workspace.GetConfigDir().
|
||||
const (
|
||||
ConfigFileName = "proxy_config.json"
|
||||
)
|
||||
@@ -48,7 +48,7 @@ type Config struct {
|
||||
|
||||
// Path returns the absolute path to the proxy plugin config file.
|
||||
func Path() string {
|
||||
return filepath.Join(core.GetConfigDir(), ConfigFileName)
|
||||
return filepath.Join(workspace.GetConfigDir(), ConfigFileName)
|
||||
}
|
||||
|
||||
// loadOnce guards one-time proxy config loading for process-wide transport reuse.
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/transport"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -163,7 +163,7 @@ func IsCIEnv() bool {
|
||||
// --- state file I/O ---
|
||||
|
||||
func statePath() string {
|
||||
return filepath.Join(core.GetConfigDir(), stateFile)
|
||||
return filepath.Join(workspace.GetConfigDir(), stateFile)
|
||||
}
|
||||
|
||||
func loadState() (*updateState, error) {
|
||||
@@ -179,7 +179,7 @@ func loadState() (*updateState, error) {
|
||||
}
|
||||
|
||||
func saveState(s *updateState) error {
|
||||
dir := core.GetConfigDir()
|
||||
dir := workspace.GetConfigDir()
|
||||
if err := vfs.MkdirAll(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package core
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -159,3 +159,16 @@ func GetRuntimeDir() string {
|
||||
}
|
||||
return filepath.Join(base, string(ws))
|
||||
}
|
||||
|
||||
// GetConfigDir returns the config directory path for the current workspace.
|
||||
// When workspace is local (default), this returns the same path as before
|
||||
// (LARKSUITE_CLI_CONFIG_DIR or ~/.lark-cli) — fully backward-compatible.
|
||||
// When workspace is openclaw/hermes, returns base/openclaw or base/hermes.
|
||||
func GetConfigDir() string {
|
||||
return GetRuntimeDir()
|
||||
}
|
||||
|
||||
// GetConfigPath returns the config file path for the current workspace.
|
||||
func GetConfigPath() string {
|
||||
return filepath.Join(GetConfigDir(), "config.json")
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package core
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/errclass"
|
||||
"github.com/larksuite/cli/internal/httpmock"
|
||||
"github.com/larksuite/cli/internal/sparkstore"
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
"github.com/larksuite/cli/shortcuts/apps/gitcred"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
@@ -533,7 +534,7 @@ func TestGitCredentialAppStorageListAppIDsSkipsNonCredentialAppDirs(t *testing.T
|
||||
if err := sparkstore.Write("app_b", gitcred.MetadataFilename, []byte("{}")); err != nil {
|
||||
t.Fatalf("Write app_b metadata: %v", err)
|
||||
}
|
||||
root := filepath.Join(core.GetConfigDir(), "spark")
|
||||
root := filepath.Join(workspace.GetConfigDir(), "spark")
|
||||
if err := os.WriteFile(filepath.Join(root, "not-an-app-dir"), []byte("x"), 0600); err != nil {
|
||||
t.Fatalf("write non-dir: %v", err)
|
||||
}
|
||||
@@ -559,7 +560,7 @@ func TestGitCredentialAppStorageListAppIDsSkipsNonCredentialAppDirs(t *testing.T
|
||||
func TestAppsGitCredentialListReturnsScanErrors(t *testing.T) {
|
||||
t.Run("storage root error", func(t *testing.T) {
|
||||
factory, stdout, _ := newAppsExecuteFactory(t)
|
||||
root := filepath.Join(core.GetConfigDir(), "spark")
|
||||
root := filepath.Join(workspace.GetConfigDir(), "spark")
|
||||
if err := os.WriteFile(root, []byte("not a dir"), 0600); err != nil {
|
||||
t.Fatalf("write storage root blocker: %v", err)
|
||||
}
|
||||
@@ -600,7 +601,7 @@ func TestListGitCredentialRecordsSortsDuplicateDecodedAppIDs(t *testing.T) {
|
||||
if err := manager.Store.Upsert(record); err != nil {
|
||||
t.Fatalf("Upsert returned error: %v", err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(core.GetConfigDir(), "spark", "app%5Fx"), 0700); err != nil {
|
||||
if err := os.Mkdir(filepath.Join(workspace.GetConfigDir(), "spark", "app%5Fx"), 0700); err != nil {
|
||||
t.Fatalf("mkdir duplicate encoded app dir: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/lockfile"
|
||||
"github.com/larksuite/cli/internal/vfs" //nolint:depguard // git credential locks live under CLI config dir and are not user file I/O.
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
var urlLocks sync.Map
|
||||
@@ -57,7 +57,7 @@ func lockURL(url string) func() {
|
||||
// Lock ordering: when both lockApp and lockURL are needed, lockApp must be
|
||||
// taken FIRST. See package comment for the full convention.
|
||||
func lockApp(appID string) (func(), error) {
|
||||
dir := filepath.Join(core.GetConfigDir(), "locks")
|
||||
dir := filepath.Join(workspace.GetConfigDir(), "locks")
|
||||
if err := vfs.MkdirAll(dir, 0700); err != nil {
|
||||
return nil, errs.NewInternalError(errs.SubtypeStorage, "create Git credential lock dir: %v", err).WithCause(err)
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/internal/vfs" //nolint:depguard // git credential metadata is CLI config-dir state, not user file I/O.
|
||||
"github.com/larksuite/cli/internal/workspace"
|
||||
)
|
||||
|
||||
type AppStorage interface {
|
||||
@@ -29,7 +29,7 @@ type Store struct {
|
||||
}
|
||||
|
||||
func NewStore() *Store {
|
||||
return &Store{path: filepath.Join(core.GetConfigDir(), MetadataFilename)}
|
||||
return &Store{path: filepath.Join(workspace.GetConfigDir(), MetadataFilename)}
|
||||
}
|
||||
|
||||
func NewAppStore(appID string, storage AppStorage) *Store {
|
||||
|
||||
Reference in New Issue
Block a user