Skip to content

feat: add furyctl get aliases command for tool management#618

Draft
nutellinoit wants to merge 11 commits intomainfrom
feat/aliases-command
Draft

feat: add furyctl get aliases command for tool management#618
nutellinoit wants to merge 11 commits intomainfrom
feat/aliases-command

Conversation

@nutellinoit
Copy link
Copy Markdown
Member

@nutellinoit nutellinoit commented Jun 26, 2025

Summary 💡

Adds furyctl tools command supporting multiple shell integration formats (aliases, functions, mise).

Closes: #591

Description 📝

Command Structure:

  • furyctl tools aliases - Generate bash aliases
  • furyctl tools functions - Generate bash functions (higher precedence than aliases)
  • furyctl tools mise - Generate/update mise.toml configuration

Core Features:

  • Strict configuration validation (fails without furyctl.yaml)
  • SD-based version selection from kfd.yaml
  • Automatic dependency download (no manual steps required)
  • Project-specific tooling with --outdir flag

Implementation:

  1. Validates furyctl configuration exists
  2. Downloads distribution and dependencies automatically
  3. Parses tool versions from distribution's kfd.yaml
  4. Generates appropriate output format
  5. Suppresses INFO logs for clean eval output

Usage Examples:

# View aliases
furyctl tools aliases --outdir $PWD

# Set functions (override version managers)
eval "$(furyctl tools functions --outdir $PWD)"

# Generate mise configuration
furyctl tools mise --outdir $PWD

Key Technical Changes:

  • Shared tool discovery logic in cmd/tools/shared.go
  • TOML parsing for mise integration using github.com/pelletier/go-toml/v2
  • Concurrent map access fixes in configuration loading
  • Functions provide higher shell precedence than aliases or version managers
  • Project-specific vs global tooling patterns

Breaking Changes 💔

None.

Tests performed 🧪

  • Unit tests for all three output formats
  • Integration testing with mise workflows
  • Linting and build verification
  • Manual testing with various flag combinations

@nutellinoit nutellinoit force-pushed the feat/aliases-command branch from 355b238 to e3afc42 Compare June 27, 2025 08:33
@nutellinoit nutellinoit marked this pull request as ready for review June 27, 2025 10:37
Copy link
Copy Markdown
Member

@ralgozino ralgozino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments, the mise integration is not working for me but could be something on my end, I'll investigate and report back.

Comment thread cmd/tools/aliases.go Outdated
Comment thread cmd/tools/mise.go Outdated
Comment thread cmd/tools.go Outdated
Comment thread cmd/tools/mise.go Outdated
Comment thread cmd/tools/mise.go Outdated
Comment thread cmd/tools/shared.go Outdated
Comment thread cmd/tools/shared.go Outdated
Comment thread README.md
Comment thread cmd/get.go Outdated
- Add new tools command with aliases, functions, and mise subcommands
- Implement mise integration for tool version management
- Add comprehensive documentation and test coverage
- Update linting configuration and Makefile
- Add README section for tool aliases usage
- Include internal improvements for file getter functionality
@nutellinoit nutellinoit force-pushed the feat/aliases-command branch from 5e3bf12 to 0c86d80 Compare July 14, 2025 14:27
nutellinoit and others added 10 commits July 14, 2025 16:45
…patibility

Update comment to emphasize that aliases point to tools with versions
compatible with SIGHUP Distribution configuration rather than just
scanning the .furyctl/bin directory.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Signed-off-by: Samuele Chiocca <samuele@sighup.io>
Replace custom FilePermissions constant with iox.FullRWPermAccess
which has the same value (0o600) but is already defined in the
internal/x/io package.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Signed-off-by: Samuele Chiocca <samuele@sighup.io>
Remove the dot prefix from .mise.toml to match the current
standard mise.toml filename format.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Signed-off-by: Samuele Chiocca <samuele@sighup.io>
Add --mise-file flag to allow custom mise configuration file paths.
This supports:
- Different directories for mise.toml
- Legacy .mise.toml format
- Global mise config (~/.config/mise/config.toml)

Replace hard-coded "mise.toml" constant with configurable parameter
passed to updateMiseConfig and RevertMiseConfig functions.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Signed-off-by: Samuele Chiocca <samuele@sighup.io>
Add proper section order preservation to maintain the original
structure and ordering of mise.toml files:

- Add SectionOrder field to MiseConfig struct
- Extract section order during file parsing using regex
- Preserve original section order when saving the file
- Add automatic 'mise fmt' execution after saving
- Update comments to reflect actual behavior

This prevents variables that depend on previous sections from
breaking and maintains user's preferred file organization.
Both adding and removing tools preserve the original section order.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Signed-off-by: Samuele Chiocca <samuele@sighup.io>
Remove all logrus.SetLevel() calls that suppress user's debug logs
during distribution download, configuration validation, and
dependency downloads.

Users who set debug logging should see all debug information,
not have it suppressed for "clean output". This gives users
full control over their logging preferences.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Signed-off-by: Samuele Chiocca <samuele@sighup.io>
Revert the get command short description back to the original
wording as the change was flagged as potentially unwanted
in the PR review.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Signed-off-by: Samuele Chiocca <samuele@sighup.io>
Update all RevertMiseConfig test calls to include the new miseFile
parameter that was added to support configurable mise file paths.

Co-authored-by: Ramiro Algozino <ramiro@sighup.io>
Fix whitespace and formatting issues in order preservation code:
- Add blank line after variable declarations (wsl linter)
- Fix comment placement in error handling blocks (wsl linter)
- Add blank line before return statement in multi-line blocks (wsl linter)
- Remove trailing whitespace in control structures (gci linter)

Co-Authored-By: ralgozino <ralgozino@users.noreply.github.com>
Remove extra blank lines after variable assignments to resolve WSL
(White Space Linter) violations. These fixes were automatically
applied during the make format-go process.

Co-Authored-By: Ramiro Algozino <ramiro@sighup.io>
@nutellinoit nutellinoit requested a review from ralgozino July 14, 2025 15:14
Comment thread cmd/tools/aliases_test.go
Comment on lines +27 to +46
func TestAliasesCmd_FlagValidation(t *testing.T) {
t.Parallel()

cmd := tools.NewAliasesCmd()

// Test that command can be created and has expected flags.
assert.NotNil(t, cmd)

// Verify required flags exist.
configFlag := cmd.Flags().Lookup("config")
assert.NotNil(t, configFlag)
assert.Equal(t, "furyctl.yaml", configFlag.DefValue)

binPathFlag := cmd.Flags().Lookup("bin-path")
assert.NotNil(t, binPathFlag)

skipDepsFlag := cmd.Flags().Lookup("skip-deps-download")
assert.NotNil(t, skipDepsFlag)
assert.Equal(t, "false", skipDepsFlag.DefValue)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure how much this kind of test makes sense. it's testing more cobra than our actual code, a better test would be to actually use test the flags in the actual code

Comment on lines +29 to +48
func TestFunctionsCmd_FlagValidation(t *testing.T) {
t.Parallel()

cmd := tools.NewFunctionsCmd()

// Test that command can be created and has expected flags.
assert.NotNil(t, cmd)

// Verify required flags exist.
configFlag := cmd.Flags().Lookup("config")
assert.NotNil(t, configFlag)
assert.Equal(t, "furyctl.yaml", configFlag.DefValue)

binPathFlag := cmd.Flags().Lookup("bin-path")
assert.NotNil(t, binPathFlag)

skipDepsFlag := cmd.Flags().Lookup("skip-deps-download")
assert.NotNil(t, skipDepsFlag)
assert.Equal(t, "false", skipDepsFlag.DefValue)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this

Comment thread cmd/tools/mise_test.go
Comment on lines +305 to +321
func TestMiseCmd_RevertFlagValidation(t *testing.T) {
t.Parallel()

cmd := tools.NewMiseCmd()

// Verify revert flag exists.
revertFlag := cmd.Flags().Lookup("revert")
assert.NotNil(t, revertFlag)
assert.Equal(t, "false", revertFlag.DefValue)
assert.Equal(t, "Remove furyctl-managed tools from mise.toml instead of adding them", revertFlag.Usage)

// Verify force flag exists.
forceFlag := cmd.Flags().Lookup("force")
assert.NotNil(t, forceFlag)
assert.Equal(t, "false", forceFlag.DefValue)
assert.Equal(t, "Skip confirmation prompt when reverting tools", forceFlag.Usage)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this

Comment thread cmd/tools/mise_test.go
}
}

func TestRevertMiseConfig_NoFile(t *testing.T) { //nolint:paralleltest // Test changes working directory
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these tests not using t.parallel() and are annotated with nolint:paralleltest instead?

Comment thread cmd/tools/shared.go
Comment on lines +122 to +177
if needsDownload {
if flags.SkipDepsDownload {
return nil, ErrNoToolsFound
}

// Download distribution and dependencies.
var distrodl *dist.Downloader
if flags.DistroLocation == "" {
distrodl = dist.NewCachingDownloader(client, outDir, typedGitProtocol, "")
} else {
distrodl = dist.NewDownloader(client, typedGitProtocol, "")
}

// Validate base requirements.
depsvl := dependencies.NewValidator(executor, binPath, furyctlPath, false)
if err := depsvl.ValidateBaseReqs(); err != nil {
return nil, fmt.Errorf("error while validating requirements: %w", err)
}

// Download the distribution.
res, err := distrodl.Download(flags.DistroLocation, furyctlPath)
if err != nil {
return nil, fmt.Errorf("error while downloading distribution: %w", err)
}

basePath := path.Join(outDir, ".furyctl", res.MinimalConf.Metadata.Name)

// Validate the furyctl.yaml file.
if err := config.Validate(furyctlPath, res.RepoPath); err != nil {
return nil, fmt.Errorf("error while validating configuration file: %w", err)
}

// Download the dependencies.
depsdl := dependencies.NewCachingDownloader(client, outDir, basePath, binPath, typedGitProtocol)

if _, err := depsdl.DownloadTools(res.DistroManifest); err != nil {
return nil, fmt.Errorf("error while downloading tools: %w", err)
}

distroManifest = res.DistroManifest
} else {
// Load the distribution to get tool versions from kfd.yaml.
var distrodl *dist.Downloader
if flags.DistroLocation == "" {
distrodl = dist.NewCachingDownloader(client, outDir, typedGitProtocol, "")
} else {
distrodl = dist.NewDownloader(client, typedGitProtocol, "")
}

res, err := distrodl.Download(flags.DistroLocation, furyctlPath)
if err != nil {
return nil, fmt.Errorf("error while downloading distribution: %w", err)
}

distroManifest = res.DistroManifest
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this big if/else block could probably be refactored in a better way with proper functions that say what they are doing

Comment thread cmd/tools_test.go
Comment on lines +48 to +60
case "aliases":
assert.Equal(t, "Generate bash aliases for downloaded tools", subcmd.Short)
assert.Contains(t, subcmd.Long, "bash aliases")

case "functions":
assert.Equal(t, "Generate bash functions for downloaded tools", subcmd.Short)
assert.Contains(t, subcmd.Long, "bash functions")
assert.Contains(t, subcmd.Long, "Functions have higher")

case "mise":
assert.Equal(t, "Generate or update mise.toml with downloaded tool paths", subcmd.Short)
assert.Contains(t, subcmd.Long, "mise.toml")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testing for descriptions seems overly verbose, do we really need it?

@stefanoghinelli stefanoghinelli marked this pull request as draft October 7, 2025 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: furyctl cmd <namecommand> <arguments...>

3 participants