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
67 changes: 0 additions & 67 deletions docs/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,6 @@ title: Changelog

## [Unreleased](https://github.com/lets-cli/lets/releases/tag/v0.0.X)

* `[Dependency]` update go to `1.26`
* `[Added]` Show similar command suggestions on typos.
* `[Changed]` Exit code 2 on unknown command.
* `[Added]` Expose `LETS_OS` and `LETS_ARCH` environment variables at command runtime.
* `[Removed]` Drop deprecated `eval_env` directive. Use `env` with `sh` execution mode instead.
* `[Added]` When a command or its `depends` chain fails, print an indented tree to stderr showing the full chain with the failing command highlighted
* `[Added]` Support `env_file` in global config and commands. File names are expanded after `env` is resolved, and values loaded from env files override values from `env`.
* `[Changed]` Migrate the LSP YAML parser from the CGO-based tree-sitter bindings to pure-Go [`gotreesitter`](https://github.com/odvcencio/gotreesitter), removing the C toolchain requirement from normal builds and release packaging.

## [0.0.59](https://github.com/lets-cli/lets/releases/tag/v0.0.59)

* `[Fixed]` Fixed indentation issues for long commands in help output. Command names are now properly padded for consistent alignment.
* `[Refactoring]` Refactored `maxCommandNameLen` to use `slices.MaxFunc` with proper handling for empty command lists.

## [0.0.58](https://github.com/lets-cli/lets/releases/tag/v0.0.58)

* `[Added]` `group` directive for commands. Organize commands into groups for better readability in help output. See [config reference for group](/docs/config#group).

```yaml
commands:
build:
group: Development
cmd: npm run build

test:
group: Development
cmd: npm test

deploy:
group: Operations
cmd: ./deploy.sh
```

## [0.0.57](https://github.com/lets-cli/lets/releases/tag/v0.0.57)

* `[Dependency]` update go to `1.24`
* `[Added]` support custom top-level keywords that start with `x-`
* `[Added]` check for invalid top-level keywords during config parsing
* `[Added]` support YAML aliases in `env` - env will be merged aliases mapping

## [0.0.56](https://github.com/lets-cli/lets/releases/tag/v0.0.56)

This tag is not released due to build issues

## [0.0.55](https://github.com/lets-cli/lets/releases/tag/v0.0.55)

* `[Added]` `lets self` command that is ment to be a new home for all lets own commands such as `completions` (soon) or `lsp`
* `[Added]` `lets self lsp` command that starts built-in `lsp` server with go to definition support and completions
* [`Development`] Since `lsp` implementation uses `https://tree-sitter.github.io` (C library with go bindings) as a internal parser `lets` now build with `CGO_ENABLED=1`. If you are building on local machine, you do not have to specify `CGO_ENABLED` env variable. But you may have to install some build system dependencies in case compilatino fails.
* `[CI]` reworked release pipeline now supports go cross compilation
* `[Improvment]` split commands in help message into `Commands` and `Internal commands`
* `[Dependency]` update go to `1.23`
* `[Dependency]` update goreleaser to `1.63.x`
* `[Dependency]` update golangci-lint to `2.x` (also applied some lint fixes across codebase)

## [0.0.54](https://github.com/lets-cli/lets/releases/tag/v0.0.54)

* `[Fixed]` `lets --init` now properly creates `lets.yaml`. Issue [#263](https://github.com/lets-cli/lets/issues/263)
* `[Dependency]` update go to `1.22`
* `[Fixed]` ensure `init` script does not get called twice. Issue [#256](https://github.com/lets-cli/lets/issues/256)
* `[Fixed]` do not fail if `sh` in env is empty. Issue [#235](https://github.com/lets-cli/lets/issues/235)
* `[Fixed]` support `arm64` in `lets --upgrade`. Issue [#254](https://github.com/lets-cli/lets/issues/254)

## [0.0.53](https://github.com/lets-cli/lets/releases/tag/v0.0.53)

* `[Fixed]` change `SHELL` env to `LETS_SHELL` because setting system variable `SHELL` to just `bash` without full path to binary cases and error in some cases.

## [0.0.52](https://github.com/lets-cli/lets/releases/tag/v0.0.52)

* `[Dependency]` update and pin goreleaser
Expand Down
149 changes: 58 additions & 91 deletions docs/docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,25 @@ id: config
title: Config reference
---

- [Top-level directives:](#top-level-directives)
- [Version](#version)
- [Shell](#shell)
- [Global env](#global-env)
- [Global before](#global-before)
- [Global init](#global-init)
- [Conditional init](#conditional-init)
- [Mixins](#mixins)
- [Ignored mixins](#ignored-mixins)
- [Remote mixins `(experimental)`](#remote-mixins-experimental)
- [Commands](#commands)
- [Command directives:](#command-directives)
- [Short syntax](#short-syntax)
- [`cmd`](#cmd)
- [`description`](#description)
- [`work_dir`](#work_dir)
- [`shell`](#shell-1)
- [`after`](#after)
- [`depends`](#depends)
- [Override arguments in depends command](#override-arguments-in-depends-command)
- [`options`](#options)
- [`env`](#env)
- [`checksum`](#checksum)
- [`persist_checksum`](#persist_checksum)
- [`ref`](#ref)
- [`args`](#args)
- [`group`](#group)
- [Aliasing:](#aliasing)
- [Env aliasing](#env-aliasing)
* [shell](#shell)
* [mixins](#mixins)
* [env](#global-env)
* [eval_env](#global-eval_env)
* [init](#global-init)
* [before](#global-before)
* [commands](#commands)
* [description](#description)
* [cmd](#cmd)
* [work_dir](#work_dir)
* [after](#after)
* [depends](#depends)
* [options](#options)
* [env](#env)
* [eval_env](#eval_env)
* [checksum](#checksum)
* [persist_checksum](#persist_checksum)
* [ref](#ref)
* [args](#args)


## Top-level directives:
Expand Down Expand Up @@ -843,6 +833,31 @@ commands:
docker run --rm myrepo/app${LETS_CHECKSUM} python -m app
```

### `checksum_cmd`

`key: checksum_cmd`

`type: string`

Use `checksum_cmd` when checksum should come from a shell command instead of a list of files.

The command runs with the same effective `shell` and `work_dir` as the command itself, so command-level overrides apply here too.

Result then can be accessed via `LETS_CHECKSUM` env variable.

Example:

```yaml
shell: sh

commands:
build-image:
shell: bash
work_dir: backend
checksum_cmd: |
[[ -f package-lock.json ]] && sha1sum package-lock.json | cut -d' ' -f1
cmd: docker build -t myrepo/app:${LETS_CHECKSUM} .
```

### `persist_checksum`

Expand All @@ -852,7 +867,7 @@ commands:

This feature is useful when you want to know that something has changed between two executions of a command.

`persist_checksum` can be used only if `checksum` declared for command.
`persist_checksum` can be used only if `checksum` or `checksum_cmd` declared for command.

If set to `true`, each run all calculated checksums will be stored to disk.

Expand All @@ -877,6 +892,19 @@ commands:
- Readme.md
```

`checksum_cmd` can be persisted too:

```yaml
commands:
build-image:
persist_checksum: true
checksum_cmd: git rev-parse HEAD
cmd: |
if [[ ${LETS_CHECKSUM_CHANGED} == true ]]; then
docker build -t myrepo/app:${LETS_CHECKSUM} .
fi
```

Resulting env will be:

* `LETS_CHECKSUM_DEPS` - checksum of deps files
Expand Down Expand Up @@ -937,64 +965,3 @@ commands:

`args` is used only with [ref](#ref) and allows to set additional positional args to referenced command. See [ref](#ref) example.


### `group`

`key: group`

`type: string`

Commands can be organized into groups for better readability in the help output. To assign a command to a group, use the `group` key:

```yaml
commands:
build:
group: Build & Deploy
description: Build the project
cmd: npm run build

deploy:
group: Build & Deploy
description: Deploy the project
cmd: npm run deploy

test:
group: Testing
description: Run tests
cmd: npm test
```

When you run `lets help`, commands will be listed under their respective groups, making it easier to find related commands.

```
Commands:

Build & Deploy
build Build the project
deploy Deploy the project

Testing
test Run tests
```


## Aliasing:

Lets supports YAML aliasing in various places in the config

### Env aliasing

You can define any mapping and alias it in `env` configuration:

```yaml
shell: bash

.default-env: &default-env
FOO: BAR

env:
<<: *default-env
HELLO: WORLD
```

This will merge `env` and `.default-env`. Any environment variables declarations after `<<: ` will override variables defined in aliased map.
15 changes: 15 additions & 0 deletions internal/checksum/checksum.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"encoding/hex"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"

"github.com/lets-cli/lets/internal/set"
"github.com/lets-cli/lets/internal/util"
Expand Down Expand Up @@ -103,6 +105,19 @@ func getChecksumsKeys(mapping map[string][]string) []string {
return keys
}

func CalculateChecksumFromCmd(shell string, workDir string, script string) (string, error) {
cmd := exec.Command(shell, "-c", script)
Copy link

Choose a reason for hiding this comment

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

security (go.lang.security.audit.dangerous-exec-command): Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

Source: opengrep

cmd.Dir = workDir

out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("can not calculate checksum from cmd: %s: %w", script, err)
}

res := string(out)
return strings.TrimSpace(res), nil
}

// CalculateChecksumFromSources calculates checksum from checksumSources.
func CalculateChecksumFromSources(workDir string, checksumSources map[string][]string) (map[string]string, error) {
checksumMap := make(map[string]string)
Expand Down
19 changes: 19 additions & 0 deletions internal/checksum/checksum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package checksum
import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/lets-cli/lets/internal/test"
Expand Down Expand Up @@ -136,3 +137,21 @@ func TestCalculateChecksumFromListOrMap(t *testing.T) {
)
}
}

func TestCalculateChecksumFromCmdUsesWorkDir(t *testing.T) {
tempDir := t.TempDir()
checksumFilePath := filepath.Join(tempDir, "checksum.txt")

if err := os.WriteFile(checksumFilePath, []byte("checksum-from-workdir"), 0o600); err != nil {
t.Fatalf("can not write checksum file. Error: %s", err)
}

checksumResult, err := CalculateChecksumFromCmd("sh", tempDir, "cat checksum.txt")
if err != nil {
t.Fatalf("checksum command failed. Error: %s", err)
}

if checksumResult != "checksum-from-workdir" {
t.Fatalf("wrong checksum output. Expect: %s, got: %s", "checksum-from-workdir", checksumResult)
}
}
25 changes: 21 additions & 4 deletions internal/config/config/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Command struct {
Depends *Deps
ChecksumMap map[string]string
PersistChecksum bool
ChecksumCmd string
// args from 'lets run --debug' will become [--debug]
Args []string

Expand Down Expand Up @@ -73,7 +74,8 @@ func (c *Command) UnmarshalYAML(unmarshal func(any) error) error {
After string
Ref string
Checksum *Checksum
PersistChecksum bool `yaml:"persist_checksum"`
ChecksumCmd string `yaml:"checksum_cmd"`
PersistChecksum bool `yaml:"persist_checksum"`
}

if err := unmarshal(&cmd); err != nil {
Expand Down Expand Up @@ -120,9 +122,13 @@ func (c *Command) UnmarshalYAML(unmarshal func(any) error) error {
c.ChecksumSources = *cmd.Checksum
}

if cmd.ChecksumCmd != "" {
c.ChecksumCmd = cmd.ChecksumCmd
}

c.PersistChecksum = cmd.PersistChecksum
if len(c.ChecksumSources) == 0 && c.PersistChecksum {
return errors.New("'persist_checksum' must be used with 'checksum'")
if len(c.ChecksumSources) == 0 && c.ChecksumCmd == "" && c.PersistChecksum {
return errors.New("'persist_checksum' must be used with 'checksum' or 'checksum_cmd'")
}

if cmd.Ref != "" {
Expand Down Expand Up @@ -189,6 +195,7 @@ func (c *Command) Clone() *Command {
Depends: c.Depends.Clone(),
ChecksumMap: cloneMap(c.ChecksumMap),
PersistChecksum: c.PersistChecksum,
ChecksumCmd: c.ChecksumCmd,
ChecksumSources: cloneMapSlice(c.ChecksumSources),
persistedChecksums: cloneMap(c.persistedChecksums),
Args: cloneSlice(c.Args),
Expand Down Expand Up @@ -227,7 +234,17 @@ func (c *Command) Help() string {
return strings.TrimSuffix(buf.String(), "\n")
}

func (c *Command) ChecksumCalculator(workDir string) error {
func (c *Command) ChecksumCalculator(shell, workDir string) error {
if c.ChecksumCmd != "" {
checksumResult, err := checksum.CalculateChecksumFromCmd(shell, workDir, c.ChecksumCmd)
if err != nil {
return err
}
c.ChecksumMap = make(map[string]string, 1)
c.ChecksumMap[checksum.DefaultChecksumKey] = checksumResult
return nil
}

if len(c.ChecksumSources) == 0 {
return nil
}
Expand Down
Loading
Loading