Skip to content

Commit 5355805

Browse files
Merge pull request #11 from actionforge/actrun.sh
Add support for running shared graphs
2 parents 2975216 + 0fa8eee commit 5355805

18 files changed

Lines changed: 316 additions & 63 deletions

actrun.sh

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/bin/bash
2+
# actrun - ActionForge graph runner
3+
#
4+
# This script is deployed at:
5+
# https://www.actionforge.dev/actrun.sh
6+
#
7+
# This script is deployed from:
8+
# https://github.com/actionforge/actrun-cli/blob/main/actrun.sh
9+
#
10+
# Usage:
11+
# curl -fsSL https://www.actionforge.dev/actrun.sh | bash -s -- <file.act> [options]
12+
# curl -fsSL https://www.actionforge.dev/actrun.sh | bash -s -- <shared-url> [options]
13+
#
14+
# Examples:
15+
# curl -fsSL https://www.actionforge.dev/actrun.sh | bash -s -- hello-world.act
16+
# curl -fsSL https://www.actionforge.dev/actrun.sh | bash -s -- workflow.act --verbose
17+
# curl -fsSL https://www.actionforge.dev/actrun.sh | bash -s -- https://app.actionforge.dev/shared/wispy-paper-a49c664e.act
18+
# curl -fsSL https://www.actionforge.dev/actrun.sh | bash -s -- --help
19+
#
20+
set -e
21+
22+
DOWNLOAD_BASE="https://www.actionforge.dev/download"
23+
API_URL="https://app.actionforge.dev/api/v2/releases/list"
24+
SHARE_API_URL="https://app.actionforge.dev/api/v2/share/graph/read"
25+
CACHE_DIR="${HOME}/.cache/actrun"
26+
27+
# Detect OS
28+
case "$(uname -s)" in
29+
Linux*) OS="linux" ;;
30+
Darwin*) OS="macos" ;;
31+
MINGW*|MSYS*|CYGWIN*) OS="windows" ;;
32+
*) echo "❌ Unsupported OS: $(uname -s)" >&2; exit 1 ;;
33+
esac
34+
35+
# Detect architecture
36+
case "$(uname -m)" in
37+
x86_64|amd64) ARCH="x64" ;;
38+
arm64|aarch64) ARCH="arm64" ;;
39+
*) echo "❌ Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
40+
esac
41+
42+
# Get latest version from API (find highest version, excluding prereleases)
43+
VERSION=$(curl -fsSL "$API_URL" | \
44+
grep -o '"version":"v[0-9]*\.[0-9]*\.[0-9]*"' | \
45+
cut -d'"' -f4 | sort -uV | tail -1)
46+
47+
if [ -z "$VERSION" ]; then
48+
echo "❌ Failed to fetch latest version" >&2
49+
exit 1
50+
fi
51+
52+
# Check cache
53+
CACHE_VERSION_DIR="$CACHE_DIR/$VERSION"
54+
if [ "$OS" = "windows" ]; then
55+
CACHED_BINARY="$CACHE_VERSION_DIR/actrun.exe"
56+
else
57+
CACHED_BINARY="$CACHE_VERSION_DIR/actrun"
58+
fi
59+
60+
if [ -x "$CACHED_BINARY" ]; then
61+
echo "✅ Using cached actrun $VERSION"
62+
63+
# Process shared URL if first argument matches the pattern
64+
if [ $# -gt 0 ] && [[ "$1" =~ ^https://app\.actionforge\.dev/shared/([a-zA-Z0-9_-]+\.act)$ ]]; then
65+
share_id="${BASH_REMATCH[1]}"
66+
echo "🔗 Fetching shared graph: $share_id"
67+
68+
graph_tmp=$(mktemp -t "actrun-shared-XXXXXX.act")
69+
trap "rm -f '$graph_tmp'" EXIT
70+
71+
if ! curl -fsSL "${SHARE_API_URL}?id=${share_id}" -o "$graph_tmp"; then
72+
echo "❌ Failed to fetch shared graph: $share_id" >&2
73+
exit 1
74+
fi
75+
76+
shift
77+
exec "$CACHED_BINARY" "$graph_tmp" "$@"
78+
fi
79+
80+
exec "$CACHED_BINARY" "$@"
81+
fi
82+
83+
# Determine file extension
84+
case "$OS" in
85+
linux) EXT="tar.gz" ;;
86+
windows) EXT="zip" ;;
87+
macos) EXT="pkg" ;;
88+
esac
89+
90+
FILENAME="actrun-${VERSION}.cli-${ARCH}-${OS}.${EXT}"
91+
URL="${DOWNLOAD_BASE}/${FILENAME}"
92+
93+
echo "⬇️ Downloading $FILENAME..."
94+
95+
TMP_DIR=$(mktemp -d)
96+
cleanup() {
97+
rm -rf "$TMP_DIR"
98+
}
99+
trap cleanup EXIT
100+
101+
curl -fsSL "$URL" -o "$TMP_DIR/$FILENAME"
102+
103+
echo "📦 Unpacking..."
104+
105+
# Extract and find binary
106+
case "$OS" in
107+
linux)
108+
tar -xzf "$TMP_DIR/$FILENAME" -C "$TMP_DIR"
109+
BINARY="$TMP_DIR/actrun"
110+
;;
111+
windows)
112+
unzip -q "$TMP_DIR/$FILENAME" -d "$TMP_DIR"
113+
BINARY="$TMP_DIR/actrun.exe"
114+
;;
115+
macos)
116+
pkgutil --expand-full "$TMP_DIR/$FILENAME" "$TMP_DIR/pkg_expanded"
117+
BINARY=$(find "$TMP_DIR/pkg_expanded" -name "actrun" -type f -perm +111 2>/dev/null | head -1)
118+
if [ -z "$BINARY" ]; then
119+
BINARY=$(find "$TMP_DIR/pkg_expanded" -name "actrun" -type f | head -1)
120+
fi
121+
;;
122+
esac
123+
124+
if [ ! -f "$BINARY" ]; then
125+
echo "❌ Failed to find actrun binary" >&2
126+
exit 1
127+
fi
128+
129+
# Cache the binary
130+
mkdir -p "$CACHE_VERSION_DIR"
131+
cp "$BINARY" "$CACHED_BINARY"
132+
chmod +x "$CACHED_BINARY"
133+
134+
echo "✅ Unpacked actrun $VERSION"
135+
136+
# Process shared URL if first argument matches the pattern
137+
if [ $# -gt 0 ] && [[ "$1" =~ ^https://app\.actionforge\.dev/shared/([a-zA-Z0-9_-]+\.act)$ ]]; then
138+
share_id="${BASH_REMATCH[1]}"
139+
echo "🔗 Fetching shared graph: $share_id"
140+
141+
graph_tmp=$(mktemp -t "actrun-shared-XXXXXX.act")
142+
trap "rm -rf '$TMP_DIR'; rm -f '$graph_tmp'" EXIT
143+
144+
if ! curl -fsSL "${SHARE_API_URL}?id=${share_id}" -o "$graph_tmp"; then
145+
echo "❌ Failed to fetch shared graph: $share_id" >&2
146+
exit 1
147+
fi
148+
149+
shift
150+
exec "$CACHED_BINARY" "$graph_tmp" "$@"
151+
fi
152+
153+
exec "$CACHED_BINARY" "$@"

cmd/cmd_root.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ var (
4040
)
4141

4242
var cmdRoot = &cobra.Command{
43-
Use: "actrun [filename] [flags]",
43+
Use: "actrun [filename|url] [flags]",
4444
Short: "actrun is a tool for running action graphs.",
4545
Version: build.GetFulllVersionInfo(),
4646
Args: cobra.ArbitraryArgs,
@@ -172,12 +172,20 @@ func cmdRootRun(cmd *cobra.Command, args []string) {
172172
return
173173
}
174174

175-
err := core.RunGraphFromFile(context.Background(), finalGraphFile, core.RunOpts{
175+
var err error
176+
opts := core.RunOpts{
176177
ConfigFile: finalConfigFile,
177178
OverrideSecrets: nil,
178179
OverrideInputs: nil,
179180
Args: finalGraphArgs,
180-
}, nil)
181+
}
182+
183+
if core.IsSharedGraphURL(finalGraphFile) {
184+
err = core.RunGraphFromURL(context.Background(), finalGraphFile, opts, nil)
185+
} else {
186+
err = core.RunGraphFromFile(context.Background(), finalGraphFile, opts, nil)
187+
}
188+
181189
if err != nil {
182190
core.PrintError(finalGraphFile, err)
183191
os.Exit(1)

core/graph.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"io"
9+
"net/http"
10+
"net/url"
811
"os"
912
"path/filepath"
13+
"regexp"
1014
"sort"
1115
"strings"
1216
"sync"
@@ -1038,3 +1042,68 @@ func RunGraphFromFile(ctx context.Context, graphFile string, opts RunOpts, debug
10381042

10391043
return nil
10401044
}
1045+
1046+
// Matches URLs like https://app.actionforge.dev/shared/<id>.act
1047+
var sharedURLPattern = regexp.MustCompile(`^https://app\.actionforge\.dev/shared/([a-zA-Z0-9_-]+\.act)$`)
1048+
1049+
const shareAPIURL = "https://app.actionforge.dev/api/v2/share/graph/read"
1050+
1051+
func IsSharedGraphURL(graphURL string) bool {
1052+
return sharedURLPattern.MatchString(graphURL)
1053+
}
1054+
1055+
func ParseSharedGraphURL(graphURL string) (string, bool) {
1056+
matches := sharedURLPattern.FindStringSubmatch(graphURL)
1057+
if len(matches) != 2 {
1058+
return "", false
1059+
}
1060+
return matches[1], true
1061+
}
1062+
1063+
// RunGraphFromURL fetches and runs a graph from a shared URL.
1064+
// Only urls from app.actionforge.dev are accepted
1065+
func RunGraphFromURL(ctx context.Context, graphURL string, opts RunOpts, debugCb DebugCallback) error {
1066+
parsedURL, err := url.Parse(graphURL)
1067+
if err != nil {
1068+
return CreateErr(nil, err, "invalid URL format")
1069+
}
1070+
1071+
if parsedURL.Host != "app.actionforge.dev" {
1072+
return CreateErr(nil, nil, "invalid shared graph URL: only URLs from app.actionforge.dev are accepted, got '%s'", parsedURL.Host).
1073+
SetHint("Double-check the URL - shared graphs must be hosted on app.actionforge.dev")
1074+
}
1075+
1076+
shareID, ok := ParseSharedGraphURL(graphURL)
1077+
if !ok {
1078+
return CreateErr(nil, nil, "invalid shared graph URL: expected format https://app.actionforge.dev/shared/<id>.act")
1079+
}
1080+
1081+
apiURL := fmt.Sprintf("%s?id=%s", shareAPIURL, url.QueryEscape(shareID))
1082+
1083+
utils.LogOut.Debugf("fetching shared graph from: %s\n", apiURL)
1084+
1085+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
1086+
if err != nil {
1087+
return CreateErr(nil, err, "failed to create request for shared graph")
1088+
}
1089+
1090+
client := &http.Client{}
1091+
resp, err := client.Do(req)
1092+
if err != nil {
1093+
return CreateErr(nil, err, "failed to fetch shared graph")
1094+
}
1095+
defer resp.Body.Close()
1096+
1097+
if resp.StatusCode != http.StatusOK {
1098+
return CreateErr(nil, nil, "failed to fetch shared graph: server returned status %d", resp.StatusCode)
1099+
}
1100+
1101+
graphContent, err := io.ReadAll(resp.Body)
1102+
if err != nil {
1103+
return CreateErr(nil, err, "failed to read shared graph content")
1104+
}
1105+
1106+
graphName := shareID
1107+
1108+
return RunGraphFromString(ctx, graphName, string(graphContent), opts, debugCb)
1109+
}

tests_e2e/references/reference_app.sh_l10

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
build hasn't expired yet
22
Error: unknown flag: --flag_doesnt_exist
33
Usage:
4-
actrun [filename] [flags]
4+
actrun [filename|url] [flags]
55
actrun [command]
66

77
Available Commands:

tests_e2e/references/reference_app.sh_l12

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ hint:
2323

2424
stack trace:
2525
github.com/actionforge/actrun-cli/core.RunGraphFromFile
26-
graph.go:1031
26+
graph.go:1035
2727
github.com/actionforge/actrun-cli/cmd.cmdRootRun
28-
cmd_root.go:175
28+
cmd_root.go:186
2929
github.com/spf13/cobra.(*Command).execute
3030
command.go:-1
3131
github.com/spf13/cobra.(*Command).ExecuteC
3232
command.go:-1
3333
github.com/spf13/cobra.(*Command).Execute
3434
command.go:-1
3535
github.com/actionforge/actrun-cli/cmd.Execute
36-
cmd_root.go:190
36+
cmd_root.go:198
3737
main.main
3838
main.go:26
3939
runtime.main

tests_e2e/references/reference_contexts_env.sh_l26

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
build hasn't expired yet
22
Error: unable to load env file: doesnt_exist
33
Usage:
4-
actrun [filename] [flags]
4+
actrun [filename|url] [flags]
55
actrun [command]
66

77
Available Commands:

tests_e2e/references/reference_dir-walk.sh_l56

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,21 @@ github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteImpl
4040
github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteEntry
4141
start@v1.go:45
4242
github.com/actionforge/actrun-cli/core.RunGraph
43-
graph.go:426
43+
graph.go:430
4444
github.com/actionforge/actrun-cli/core.RunGraphFromString
45-
graph.go:1016
45+
graph.go:1020
4646
github.com/actionforge/actrun-cli/core.RunGraphFromFile
47-
graph.go:1034
47+
graph.go:1038
4848
github.com/actionforge/actrun-cli/cmd.cmdRootRun
49-
cmd_root.go:175
49+
cmd_root.go:186
5050
github.com/spf13/cobra.(*Command).execute
5151
command.go:-1
5252
github.com/spf13/cobra.(*Command).ExecuteC
5353
command.go:-1
5454
github.com/spf13/cobra.(*Command).Execute
5555
command.go:-1
5656
github.com/actionforge/actrun-cli/cmd.Execute
57-
cmd_root.go:190
57+
cmd_root.go:198
5858
main.main
5959
main.go:26
6060
runtime.main

tests_e2e/references/reference_error_no_output.sh_l8

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,21 @@ github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteImpl
5151
github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteEntry
5252
start@v1.go:45
5353
github.com/actionforge/actrun-cli/core.RunGraph
54-
graph.go:426
54+
graph.go:430
5555
github.com/actionforge/actrun-cli/core.RunGraphFromString
56-
graph.go:1016
56+
graph.go:1020
5757
github.com/actionforge/actrun-cli/core.RunGraphFromFile
58-
graph.go:1034
58+
graph.go:1038
5959
github.com/actionforge/actrun-cli/cmd.cmdRootRun
60-
cmd_root.go:175
60+
cmd_root.go:186
6161
github.com/spf13/cobra.(*Command).execute
6262
command.go:-1
6363
github.com/spf13/cobra.(*Command).ExecuteC
6464
command.go:-1
6565
github.com/spf13/cobra.(*Command).Execute
6666
command.go:-1
6767
github.com/actionforge/actrun-cli/cmd.Execute
68-
cmd_root.go:190
68+
cmd_root.go:198
6969
main.main
7070
main.go:26
7171
runtime.main

tests_e2e/references/reference_group-error.sh_l8

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteImpl
250250
github.com/actionforge/actrun-cli/nodes.(*StartNode).ExecuteEntry
251251
start@v1.go:45
252252
github.com/actionforge/actrun-cli/core.RunGraph
253-
graph.go:426
253+
graph.go:430
254254
github.com/actionforge/actrun-cli/core.RunGraphFromString
255-
graph.go:1016
255+
graph.go:1020
256256

0 commit comments

Comments
 (0)