Skip to content

Commit eaa0769

Browse files
committed
add checksum_cmd directive to command
1 parent e24933c commit eaa0769

13 files changed

Lines changed: 299 additions & 173 deletions

File tree

config/config/config_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/lithammer/dedent"
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
func ConfigFixture(t *testing.T, text string) *Config {
12+
buf := bytes.NewBufferString(text)
13+
c := NewConfig(".", ".", ".")
14+
if err := yaml.NewDecoder(buf).Decode(&c); err != nil {
15+
t.Fatalf("config fixture decode error: %s", err)
16+
}
17+
18+
return c
19+
}
20+
21+
func TestParseConfig(t *testing.T) {
22+
t.Run("append args to cmd as list", func(t *testing.T) {
23+
args := []string{"World", "--foo", `--bar='{"age": 20}'`}
24+
text := dedent.Dedent(`
25+
shell: bash
26+
commands:
27+
hello:
28+
cmd: [echo, Hello]
29+
`)
30+
cfg := ConfigFixture(t, text)
31+
cmd := cfg.Commands["hello"]
32+
cmd.Cmds.AppendArgs(args)
33+
34+
exp := `echo Hello 'World' '--foo' '--bar='{"age": 20}''`
35+
if script := cmd.Cmds.Commands[0].Script; script != exp {
36+
t.Errorf("wrong output. \nexpect %s \ngot: %s", exp, script)
37+
}
38+
})
39+
40+
t.Run("allow persist checksum with checksum cmd", func(t *testing.T) {
41+
text := dedent.Dedent(`
42+
shell: bash
43+
commands:
44+
checksum-cmd:
45+
persist_checksum: true
46+
checksum_cmd: echo checksum
47+
cmd: echo ok
48+
`)
49+
50+
cfg := ConfigFixture(t, text)
51+
cmd := cfg.Commands["checksum-cmd"]
52+
53+
if !cmd.PersistChecksum {
54+
t.Fatalf("expected persist_checksum to be enabled")
55+
}
56+
57+
if cmd.ChecksumCmd != "echo checksum" {
58+
t.Fatalf("wrong checksum_cmd. Expect: %s, got: %s", "echo checksum", cmd.ChecksumCmd)
59+
}
60+
})
61+
62+
}

docs/docs/changelog.md

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,73 +5,6 @@ title: Changelog
55

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

8-
* `[Dependency]` update go to `1.26`
9-
* `[Added]` Show similar command suggestions on typos.
10-
* `[Changed]` Exit code 2 on unknown command.
11-
* `[Added]` Expose `LETS_OS` and `LETS_ARCH` environment variables at command runtime.
12-
* `[Removed]` Drop deprecated `eval_env` directive. Use `env` with `sh` execution mode instead.
13-
* `[Added]` When a command or its `depends` chain fails, print an indented tree to stderr showing the full chain with the failing command highlighted
14-
* `[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`.
15-
* `[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.
16-
17-
## [0.0.59](https://github.com/lets-cli/lets/releases/tag/v0.0.59)
18-
19-
* `[Fixed]` Fixed indentation issues for long commands in help output. Command names are now properly padded for consistent alignment.
20-
* `[Refactoring]` Refactored `maxCommandNameLen` to use `slices.MaxFunc` with proper handling for empty command lists.
21-
22-
## [0.0.58](https://github.com/lets-cli/lets/releases/tag/v0.0.58)
23-
24-
* `[Added]` `group` directive for commands. Organize commands into groups for better readability in help output. See [config reference for group](/docs/config#group).
25-
26-
```yaml
27-
commands:
28-
build:
29-
group: Development
30-
cmd: npm run build
31-
32-
test:
33-
group: Development
34-
cmd: npm test
35-
36-
deploy:
37-
group: Operations
38-
cmd: ./deploy.sh
39-
```
40-
41-
## [0.0.57](https://github.com/lets-cli/lets/releases/tag/v0.0.57)
42-
43-
* `[Dependency]` update go to `1.24`
44-
* `[Added]` support custom top-level keywords that start with `x-`
45-
* `[Added]` check for invalid top-level keywords during config parsing
46-
* `[Added]` support YAML aliases in `env` - env will be merged aliases mapping
47-
48-
## [0.0.56](https://github.com/lets-cli/lets/releases/tag/v0.0.56)
49-
50-
This tag is not released due to build issues
51-
52-
## [0.0.55](https://github.com/lets-cli/lets/releases/tag/v0.0.55)
53-
54-
* `[Added]` `lets self` command that is ment to be a new home for all lets own commands such as `completions` (soon) or `lsp`
55-
* `[Added]` `lets self lsp` command that starts built-in `lsp` server with go to definition support and completions
56-
* [`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.
57-
* `[CI]` reworked release pipeline now supports go cross compilation
58-
* `[Improvment]` split commands in help message into `Commands` and `Internal commands`
59-
* `[Dependency]` update go to `1.23`
60-
* `[Dependency]` update goreleaser to `1.63.x`
61-
* `[Dependency]` update golangci-lint to `2.x` (also applied some lint fixes across codebase)
62-
63-
## [0.0.54](https://github.com/lets-cli/lets/releases/tag/v0.0.54)
64-
65-
* `[Fixed]` `lets --init` now properly creates `lets.yaml`. Issue [#263](https://github.com/lets-cli/lets/issues/263)
66-
* `[Dependency]` update go to `1.22`
67-
* `[Fixed]` ensure `init` script does not get called twice. Issue [#256](https://github.com/lets-cli/lets/issues/256)
68-
* `[Fixed]` do not fail if `sh` in env is empty. Issue [#235](https://github.com/lets-cli/lets/issues/235)
69-
* `[Fixed]` support `arm64` in `lets --upgrade`. Issue [#254](https://github.com/lets-cli/lets/issues/254)
70-
71-
## [0.0.53](https://github.com/lets-cli/lets/releases/tag/v0.0.53)
72-
73-
* `[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.
74-
758
## [0.0.52](https://github.com/lets-cli/lets/releases/tag/v0.0.52)
769

7710
* `[Dependency]` update and pin goreleaser

docs/docs/config.md

Lines changed: 58 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,25 @@ id: config
33
title: Config reference
44
---
55

6-
- [Top-level directives:](#top-level-directives)
7-
- [Version](#version)
8-
- [Shell](#shell)
9-
- [Global env](#global-env)
10-
- [Global before](#global-before)
11-
- [Global init](#global-init)
12-
- [Conditional init](#conditional-init)
13-
- [Mixins](#mixins)
14-
- [Ignored mixins](#ignored-mixins)
15-
- [Remote mixins `(experimental)`](#remote-mixins-experimental)
16-
- [Commands](#commands)
17-
- [Command directives:](#command-directives)
18-
- [Short syntax](#short-syntax)
19-
- [`cmd`](#cmd)
20-
- [`description`](#description)
21-
- [`work_dir`](#work_dir)
22-
- [`shell`](#shell-1)
23-
- [`after`](#after)
24-
- [`depends`](#depends)
25-
- [Override arguments in depends command](#override-arguments-in-depends-command)
26-
- [`options`](#options)
27-
- [`env`](#env)
28-
- [`checksum`](#checksum)
29-
- [`persist_checksum`](#persist_checksum)
30-
- [`ref`](#ref)
31-
- [`args`](#args)
32-
- [`group`](#group)
33-
- [Aliasing:](#aliasing)
34-
- [Env aliasing](#env-aliasing)
6+
* [shell](#shell)
7+
* [mixins](#mixins)
8+
* [env](#global-env)
9+
* [eval_env](#global-eval_env)
10+
* [init](#global-init)
11+
* [before](#global-before)
12+
* [commands](#commands)
13+
* [description](#description)
14+
* [cmd](#cmd)
15+
* [work_dir](#work_dir)
16+
* [after](#after)
17+
* [depends](#depends)
18+
* [options](#options)
19+
* [env](#env)
20+
* [eval_env](#eval_env)
21+
* [checksum](#checksum)
22+
* [persist_checksum](#persist_checksum)
23+
* [ref](#ref)
24+
* [args](#args)
3525

3626

3727
## Top-level directives:
@@ -843,6 +833,31 @@ commands:
843833
docker run --rm myrepo/app${LETS_CHECKSUM} python -m app
844834
```
845835

836+
### `checksum_cmd`
837+
838+
`key: checksum_cmd`
839+
840+
`type: string`
841+
842+
Use `checksum_cmd` when checksum should come from a shell command instead of a list of files.
843+
844+
The command runs with the same effective `shell` and `work_dir` as the command itself, so command-level overrides apply here too.
845+
846+
Result then can be accessed via `LETS_CHECKSUM` env variable.
847+
848+
Example:
849+
850+
```yaml
851+
shell: sh
852+
853+
commands:
854+
build-image:
855+
shell: bash
856+
work_dir: backend
857+
checksum_cmd: |
858+
[[ -f package-lock.json ]] && sha1sum package-lock.json | cut -d' ' -f1
859+
cmd: docker build -t myrepo/app:${LETS_CHECKSUM} .
860+
```
846861

847862
### `persist_checksum`
848863

@@ -852,7 +867,7 @@ commands:
852867

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

855-
`persist_checksum` can be used only if `checksum` declared for command.
870+
`persist_checksum` can be used only if `checksum` or `checksum_cmd` declared for command.
856871

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

@@ -877,6 +892,19 @@ commands:
877892
- Readme.md
878893
```
879894

895+
`checksum_cmd` can be persisted too:
896+
897+
```yaml
898+
commands:
899+
build-image:
900+
persist_checksum: true
901+
checksum_cmd: git rev-parse HEAD
902+
cmd: |
903+
if [[ ${LETS_CHECKSUM_CHANGED} == true ]]; then
904+
docker build -t myrepo/app:${LETS_CHECKSUM} .
905+
fi
906+
```
907+
880908
Resulting env will be:
881909

882910
* `LETS_CHECKSUM_DEPS` - checksum of deps files
@@ -937,64 +965,3 @@ commands:
937965

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

940-
941-
### `group`
942-
943-
`key: group`
944-
945-
`type: string`
946-
947-
Commands can be organized into groups for better readability in the help output. To assign a command to a group, use the `group` key:
948-
949-
```yaml
950-
commands:
951-
build:
952-
group: Build & Deploy
953-
description: Build the project
954-
cmd: npm run build
955-
956-
deploy:
957-
group: Build & Deploy
958-
description: Deploy the project
959-
cmd: npm run deploy
960-
961-
test:
962-
group: Testing
963-
description: Run tests
964-
cmd: npm test
965-
```
966-
967-
When you run `lets help`, commands will be listed under their respective groups, making it easier to find related commands.
968-
969-
```
970-
Commands:
971-
972-
Build & Deploy
973-
build Build the project
974-
deploy Deploy the project
975-
976-
Testing
977-
test Run tests
978-
```
979-
980-
981-
## Aliasing:
982-
983-
Lets supports YAML aliasing in various places in the config
984-
985-
### Env aliasing
986-
987-
You can define any mapping and alias it in `env` configuration:
988-
989-
```yaml
990-
shell: bash
991-
992-
.default-env: &default-env
993-
FOO: BAR
994-
995-
env:
996-
<<: *default-env
997-
HELLO: WORLD
998-
```
999-
1000-
This will merge `env` and `.default-env`. Any environment variables declarations after `<<: ` will override variables defined in aliased map.

internal/checksum/checksum.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"encoding/hex"
77
"fmt"
88
"os"
9+
"os/exec"
910
"path/filepath"
1011
"sort"
12+
"strings"
1113

1214
"github.com/lets-cli/lets/internal/set"
1315
"github.com/lets-cli/lets/internal/util"
@@ -103,6 +105,19 @@ func getChecksumsKeys(mapping map[string][]string) []string {
103105
return keys
104106
}
105107

108+
func CalculateChecksumFromCmd(shell string, workDir string, script string) (string, error) {
109+
cmd := exec.Command(shell, "-c", script)
110+
cmd.Dir = workDir
111+
112+
out, err := cmd.Output()
113+
if err != nil {
114+
return "", fmt.Errorf("can not calculate checksum from cmd: %s: %w", script, err)
115+
}
116+
117+
res := string(out)
118+
return strings.TrimSpace(res), nil
119+
}
120+
106121
// CalculateChecksumFromSources calculates checksum from checksumSources.
107122
func CalculateChecksumFromSources(workDir string, checksumSources map[string][]string) (map[string]string, error) {
108123
checksumMap := make(map[string]string)

internal/checksum/checksum_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package checksum
33
import (
44
"fmt"
55
"os"
6+
"path/filepath"
67
"testing"
78

89
"github.com/lets-cli/lets/internal/test"
@@ -136,3 +137,21 @@ func TestCalculateChecksumFromListOrMap(t *testing.T) {
136137
)
137138
}
138139
}
140+
141+
func TestCalculateChecksumFromCmdUsesWorkDir(t *testing.T) {
142+
tempDir := t.TempDir()
143+
checksumFilePath := filepath.Join(tempDir, "checksum.txt")
144+
145+
if err := os.WriteFile(checksumFilePath, []byte("checksum-from-workdir"), 0o600); err != nil {
146+
t.Fatalf("can not write checksum file. Error: %s", err)
147+
}
148+
149+
checksumResult, err := CalculateChecksumFromCmd("sh", tempDir, "cat checksum.txt")
150+
if err != nil {
151+
t.Fatalf("checksum command failed. Error: %s", err)
152+
}
153+
154+
if checksumResult != "checksum-from-workdir" {
155+
t.Fatalf("wrong checksum output. Expect: %s, got: %s", "checksum-from-workdir", checksumResult)
156+
}
157+
}

0 commit comments

Comments
 (0)