Skip to content

Commit 43f996d

Browse files
authored
Merge pull request #1 from AxeForging/feat/config-slug-parse
feat: add config, parse commands and transform slug
2 parents 052910f + 54df492 commit 43f996d

12 files changed

Lines changed: 1265 additions & 5 deletions

README.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,189 @@ pipekit cache-key composite linux amd64 "$(pipekit transform hash --file go.sum)
569569
570570
---
571571
572+
### `config` - Environment Configuration
573+
574+
Resolve environment-specific configuration from structured maps. Replaces the ~80 lines of bash typically needed for environment mapping in CI workflows.
575+
576+
<details>
577+
<summary><strong>resolve</strong> - Resolve config with alias support</summary>
578+
579+
```sh
580+
# Given a config file with dev/staging/production keys:
581+
pipekit config resolve envs.json --env prod --to-github
582+
# Normalizes "prod" → "production", exports all values to $GITHUB_ENV
583+
584+
# From stdin
585+
echo '{"dev": {"project_id": "my-dev"}, "production": {"project_id": "my-prod"}}' \
586+
| pipekit config resolve --env develop --uppercase-keys
587+
# "develop" → "dev", outputs: export PROJECT_ID="my-dev"
588+
589+
# YAML config
590+
pipekit config resolve envs.yaml --env staging --format yaml --to-github-output
591+
592+
# With custom aliases
593+
pipekit config resolve envs.json --env preview \
594+
--aliases '{"preview": "staging", "canary": "production"}'
595+
596+
# Output as compact JSON
597+
pipekit config resolve envs.json --env prod --json
598+
# {"project_id":"my-prod","region":"eu-west1"}
599+
```
600+
601+
**Built-in aliases:** `dev`/`develop`/`development`/`test`/`testing``dev`, `stage`/`staging``staging`, `prod`/`production``production`
602+
603+
**Flags:**
604+
605+
| Flag | Description |
606+
|------|-------------|
607+
| `--env, -e` | Environment name (required) |
608+
| `--format, -f` | Config format: `json`, `yaml` (default: json) |
609+
| `--aliases` | Custom aliases as JSON |
610+
| `--json` | Output as compact JSON instead of key-value pairs |
611+
| `--uppercase-keys, -u` | Convert keys to UPPER_SNAKE_CASE |
612+
| `--prefix, -p` | Add prefix to all keys |
613+
| `--to-github` | Write to `$GITHUB_ENV` |
614+
| `--to-github-output` | Write to `$GITHUB_OUTPUT` |
615+
616+
</details>
617+
618+
<details>
619+
<summary><strong>branch-env</strong> - Map branches to environments</summary>
620+
621+
```sh
622+
# Map current branch to an environment
623+
pipekit config branch-env main --mapping '{"main":"production","develop":"dev","release/*":"staging"}'
624+
# production
625+
626+
# Works with refs/heads/ prefix (auto-stripped)
627+
pipekit config branch-env refs/heads/release/v1.2.0 \
628+
--mapping '{"main":"production","release/*":"staging"}'
629+
# staging
630+
631+
# Auto-detect from $GITHUB_REF
632+
pipekit config branch-env --mapping '{"main":"production","develop":"dev"}' --to-github
633+
# Writes TARGET_ENV=production to $GITHUB_ENV
634+
635+
# Custom output key
636+
pipekit config branch-env develop --mapping '{"develop":"dev"}' \
637+
--output-key DEPLOY_ENV --to-github-output
638+
```
639+
640+
**Flags:**
641+
642+
| Flag | Description |
643+
|------|-------------|
644+
| `--mapping, -m` | Branch-to-env JSON mapping (required) |
645+
| `--output-key` | Output variable name (default: `TARGET_ENV`) |
646+
| `--to-github` | Write to `$GITHUB_ENV` |
647+
| `--to-github-output` | Write to `$GITHUB_OUTPUT` |
648+
649+
</details>
650+
651+
---
652+
653+
### `parse` - Structured Data Extraction
654+
655+
Extract code blocks and structured data from markdown text. Useful for parsing GitHub issue bodies, PR comments, and other markdown sources in CI workflows.
656+
657+
<details>
658+
<summary><strong>extract-block</strong> - Extract fenced code blocks</summary>
659+
660+
```sh
661+
# Extract all code blocks as JSON
662+
echo "$ISSUE_BODY" | pipekit parse extract-block
663+
# [{"language":"yaml","content":"foo: bar","index":0},{"language":"json","content":"{...}","index":1}]
664+
665+
# Filter by language
666+
echo "$COMMENT" | pipekit parse extract-block --language yaml
667+
# Only yaml blocks
668+
669+
# Get raw content of a specific block
670+
echo "$COMMENT" | pipekit parse extract-block --language python --index 0 --content-only
671+
# print("hello")
672+
673+
# Write first block to GITHUB_OUTPUT
674+
echo "$ISSUE_BODY" | pipekit parse extract-block --language yaml --to-github-output
675+
```
676+
677+
Supports both `` ``` `` and `~~~` fences, with any language tag (yaml, json, python, bash, etc.).
678+
679+
**Flags:**
680+
681+
| Flag | Description |
682+
|------|-------------|
683+
| `--language, -l` | Filter by language tag (case-insensitive) |
684+
| `--index, -i` | Return only the Nth block (0-based) |
685+
| `--content-only` | Output raw content without JSON wrapping |
686+
| `--to-github-output` | Write content to `$GITHUB_OUTPUT` |
687+
| `--output-key` | Variable name for `--to-github-output` (default: `PARSED_BLOCK`) |
688+
689+
</details>
690+
691+
<details>
692+
<summary><strong>extract-yaml</strong> - Extract and parse YAML blocks</summary>
693+
694+
```sh
695+
# Parse YAML blocks from an issue body
696+
echo "$ISSUE_BODY" | pipekit parse extract-yaml
697+
# [{"env":"production","replicas":3}]
698+
699+
# Export parsed values as env vars
700+
echo "$ISSUE_BODY" | pipekit parse extract-yaml --to-github -u
701+
# Writes UPPER_SNAKE_CASE keys to $GITHUB_ENV
702+
703+
# Get a specific YAML block
704+
echo "$COMMENT" | pipekit parse extract-yaml --index 0
705+
706+
# Write to GITHUB_OUTPUT as JSON
707+
echo "$ISSUE_BODY" | pipekit parse extract-yaml --to-github-output
708+
```
709+
710+
Matches blocks tagged as `yaml`, `yml`, or untagged blocks that parse as valid YAML. Invalid YAML blocks are silently skipped.
711+
712+
**Flags:**
713+
714+
| Flag | Description |
715+
|------|-------------|
716+
| `--index, -i` | Return only the Nth YAML block (0-based) |
717+
| `--to-env` | Export top-level keys as shell export statements |
718+
| `--to-github` | Write top-level keys to `$GITHUB_ENV` |
719+
| `--to-github-output` | Write parsed JSON to `$GITHUB_OUTPUT` |
720+
| `--uppercase-keys, -u` | Convert keys to UPPER_SNAKE_CASE |
721+
| `--output-key` | Variable name for `--to-github-output` (default: `PARSED_YAML`) |
722+
723+
</details>
724+
725+
---
726+
727+
### `transform slug` - URL-safe Slug Generation
728+
729+
Generate URL-safe slugs from branch names or arbitrary strings. Useful for preview deployment names, Cloudflare Worker names, and unique resource identifiers.
730+
731+
<details>
732+
<summary><strong>Examples</strong></summary>
733+
734+
```sh
735+
# Branch name to slug
736+
echo "feature/my-cool-feature" | pipekit transform slug
737+
# feature-my-cool-feature
738+
739+
# Strips refs/heads/ automatically
740+
echo "refs/heads/release/v1.2.3" | pipekit transform slug
741+
# release-v123
742+
743+
# With max length (default: 63, matching k8s label limits)
744+
echo "feature/very-long-branch-name-that-exceeds-limits" | pipekit transform slug --max-length 20
745+
746+
# With prefix
747+
echo "$GITHUB_HEAD_REF" | pipekit transform slug --prefix "preview-"
748+
# preview-feature-my-thing
749+
```
750+
751+
</details>
752+
753+
---
754+
572755
## Exit Codes
573756
574757
| Code | Meaning |

actions/config.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package actions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/AxeForging/pipekit/domain"
10+
"github.com/AxeForging/pipekit/services"
11+
12+
"github.com/urfave/cli"
13+
)
14+
15+
// ConfigCommand returns the config command group.
16+
func ConfigCommand() cli.Command {
17+
outputFlags := []cli.Flag{
18+
cli.BoolFlag{Name: "uppercase-keys, u", Usage: "convert all keys to UPPER_SNAKE_CASE"},
19+
cli.StringFlag{Name: "prefix, p", Usage: "add prefix to all keys"},
20+
cli.BoolFlag{Name: "to-github", Usage: "write to $GITHUB_ENV"},
21+
cli.BoolFlag{Name: "to-github-output", Usage: "write to $GITHUB_OUTPUT"},
22+
cli.BoolFlag{Name: "json", Usage: "output resolved config as compact JSON"},
23+
}
24+
25+
return cli.Command{
26+
Name: "config",
27+
Usage: "resolve environment configuration from structured maps",
28+
Subcommands: []cli.Command{
29+
{
30+
Name: "resolve",
31+
Usage: "resolve environment config from a JSON/YAML map with alias support",
32+
ArgsUsage: "CONFIG_FILE",
33+
Flags: append(outputFlags,
34+
cli.StringFlag{Name: "env, e", Usage: "environment name (supports aliases like dev, staging, prod)", Required: true},
35+
cli.StringFlag{Name: "format, f", Value: "json", Usage: "config format: json, yaml"},
36+
cli.StringFlag{Name: "aliases", Usage: "custom aliases as JSON (e.g. '{\"preview\": \"staging\"}')"},
37+
),
38+
Action: func(c *cli.Context) error {
39+
r, err := getInputReader(c)
40+
if err != nil {
41+
return cli.NewExitError("config file required: "+err.Error(), 1)
42+
}
43+
44+
var customAliases map[string]string
45+
if aliasStr := c.String("aliases"); aliasStr != "" {
46+
if err := json.Unmarshal([]byte(aliasStr), &customAliases); err != nil {
47+
return cli.NewExitError("invalid aliases JSON: "+err.Error(), 1)
48+
}
49+
}
50+
51+
envName := c.String("env")
52+
format := c.String("format")
53+
54+
if c.Bool("json") {
55+
jsonStr, normalized, err := services.ResolveConfigJSON(r, envName, format, customAliases)
56+
if err != nil {
57+
return cli.NewExitError(err.Error(), 1)
58+
}
59+
60+
kvs := []domain.KeyValue{{Key: "json_map", Value: jsonStr}}
61+
62+
if c.Bool("to-github") {
63+
return services.WriteToGitHubEnv(kvs)
64+
}
65+
if c.Bool("to-github-output") {
66+
return services.WriteToGitHubOutput(kvs)
67+
}
68+
69+
fmt.Println(jsonStr)
70+
fmt.Fprintf(os.Stderr, "resolved environment: %s\n", normalized)
71+
return nil
72+
}
73+
74+
kvs, normalized, err := services.ResolveConfig(r, envName, format, customAliases)
75+
if err != nil {
76+
return cli.NewExitError(err.Error(), 1)
77+
}
78+
79+
kvs = services.TransformKeys(kvs, c.Bool("uppercase-keys"), c.String("prefix"), false)
80+
81+
fmt.Fprintf(os.Stderr, "resolved environment: %s\n", normalized)
82+
83+
if c.Bool("to-github") {
84+
return services.WriteToGitHubEnv(kvs)
85+
}
86+
if c.Bool("to-github-output") {
87+
return services.WriteToGitHubOutput(kvs)
88+
}
89+
90+
return services.WriteToShell(os.Stdout, kvs)
91+
},
92+
},
93+
{
94+
Name: "branch-env",
95+
Usage: "map a git branch name to an environment using a JSON mapping",
96+
ArgsUsage: "BRANCH",
97+
Flags: []cli.Flag{
98+
cli.StringFlag{Name: "mapping, m", Usage: `branch-to-env JSON mapping (e.g. '{"main":"production","develop":"dev","release/*":"staging"}')`, Required: true},
99+
cli.BoolFlag{Name: "to-github", Usage: "write TARGET_ENV to $GITHUB_ENV"},
100+
cli.BoolFlag{Name: "to-github-output", Usage: "write TARGET_ENV to $GITHUB_OUTPUT"},
101+
cli.StringFlag{Name: "output-key", Value: "TARGET_ENV", Usage: "output variable name"},
102+
},
103+
Action: func(c *cli.Context) error {
104+
branch := c.Args().First()
105+
if branch == "" {
106+
branch = os.Getenv("GITHUB_REF")
107+
if branch == "" {
108+
branch = os.Getenv("GITHUB_HEAD_REF")
109+
}
110+
}
111+
if branch == "" {
112+
return cli.NewExitError("branch name required (as argument or via $GITHUB_REF)", 1)
113+
}
114+
branch = strings.TrimPrefix(branch, "refs/heads/")
115+
116+
env, err := services.BranchToEnv(branch, c.String("mapping"))
117+
if err != nil {
118+
return cli.NewExitError(err.Error(), 1)
119+
}
120+
121+
outputKey := c.String("output-key")
122+
kvs := []domain.KeyValue{{Key: outputKey, Value: env}}
123+
124+
if c.Bool("to-github") {
125+
return services.WriteToGitHubEnv(kvs)
126+
}
127+
if c.Bool("to-github-output") {
128+
return services.WriteToGitHubOutput(kvs)
129+
}
130+
131+
fmt.Println(env)
132+
return nil
133+
},
134+
},
135+
},
136+
}
137+
}

0 commit comments

Comments
 (0)