Skip to content

Commit 1c467ba

Browse files
Codelaxremyleone
andauthored
feat: wasm build (#3024)
Co-authored-by: Rémy Léone <[email protected]>
1 parent 29c3c3a commit 1c467ba

39 files changed

+1837
-11
lines changed

.github/workflows/wasm.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: WebAssembly
2+
3+
on:
4+
pull_request:
5+
merge_group:
6+
7+
jobs:
8+
build-and-test:
9+
strategy:
10+
matrix:
11+
go-version: [1.19.x]
12+
platform: [ubuntu-latest]
13+
runs-on: ${{ matrix.platform }}
14+
steps:
15+
- name: Install Go
16+
uses: actions/setup-go@v3
17+
with:
18+
go-version: ${{ matrix.go-version }}
19+
- name: Install pnpm
20+
uses: pnpm/action-setup@v2
21+
with:
22+
version: 6.0.2
23+
- name: Checkout
24+
uses: actions/checkout@v3
25+
with:
26+
fetch-depth: 1
27+
- name: Build
28+
run: ./scripts/build-wasm.sh
29+
- name: Run npm package tests
30+
run: ./scripts/run-tests-wasm.sh

cmd/scw-wasm/args.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//go:build wasm && js
2+
3+
package main
4+
5+
import "os"
6+
7+
type Args struct {
8+
callback string
9+
targetObject string
10+
}
11+
12+
func getArgs() Args {
13+
args := Args{}
14+
if len(os.Args) > 0 {
15+
args.callback = os.Args[0]
16+
}
17+
if len(os.Args) > 1 {
18+
args.targetObject = os.Args[1]
19+
}
20+
21+
return args
22+
}

cmd/scw-wasm/async.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//go:build wasm && js
2+
3+
package main
4+
5+
import (
6+
"fmt"
7+
"syscall/js"
8+
)
9+
10+
type fn func(this js.Value, args []js.Value) (any, error)
11+
12+
var (
13+
jsErr js.Value = js.Global().Get("Error")
14+
jsObject js.Value = js.Global().Get("Object")
15+
jsPromise js.Value = js.Global().Get("Promise")
16+
)
17+
18+
func asyncFunc(innerFunc fn) js.Func {
19+
return js.FuncOf(func(this js.Value, args []js.Value) any {
20+
handler := js.FuncOf(func(_ js.Value, promFn []js.Value) any {
21+
resolve, reject := promFn[0], promFn[1]
22+
23+
go func() {
24+
defer func() {
25+
if r := recover(); r != nil {
26+
reject.Invoke(jsErr.New(fmt.Sprint("panic:", r)))
27+
}
28+
}()
29+
30+
res, err := innerFunc(this, args)
31+
if err != nil {
32+
errContent := jsObject.New()
33+
errContent.Set("cause", err.Error())
34+
reject.Invoke(jsErr.New(res, errContent))
35+
} else {
36+
resolve.Invoke(res)
37+
}
38+
}()
39+
40+
return nil
41+
})
42+
43+
return jsPromise.New(handler)
44+
})
45+
}

cmd/scw-wasm/main.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//go:build wasm && js
2+
3+
package main
4+
5+
import (
6+
"bytes"
7+
"fmt"
8+
"io"
9+
"syscall/js"
10+
11+
"github.com/scaleway/scaleway-cli/v2/internal/core"
12+
"github.com/scaleway/scaleway-cli/v2/internal/namespaces"
13+
)
14+
15+
var commands *core.Commands
16+
17+
func getCommands() *core.Commands {
18+
if commands == nil {
19+
commands = namespaces.GetCommands()
20+
}
21+
return commands
22+
}
23+
24+
func runCommand(args []string, stdout io.Writer, stderr io.Writer) chan int {
25+
ret := make(chan int, 1)
26+
go func() {
27+
exitCode, _, _ := core.Bootstrap(&core.BootstrapConfig{
28+
Args: args,
29+
Commands: getCommands(),
30+
BuildInfo: &core.BuildInfo{},
31+
Stdout: stdout,
32+
Stderr: stderr,
33+
Stdin: nil,
34+
})
35+
ret <- exitCode
36+
}()
37+
38+
return ret
39+
}
40+
41+
func wasmRun(this js.Value, args []js.Value) (any, error) {
42+
cliArgs := []string{"scw"}
43+
stdout := bytes.NewBuffer(nil)
44+
stderr := bytes.NewBuffer(nil)
45+
46+
for argIndex, arg := range args {
47+
if arg.Type() != js.TypeString {
48+
return nil, fmt.Errorf("invalid argument at index %d", argIndex)
49+
}
50+
cliArgs = append(cliArgs, arg.String())
51+
}
52+
53+
exitCodeChan := runCommand(cliArgs, stdout, stderr)
54+
exitCode := <-exitCodeChan
55+
if exitCode != 0 {
56+
errBody := stderr.String()
57+
return js.ValueOf(errBody), fmt.Errorf("exit code: %d", exitCode)
58+
}
59+
60+
outBody := stdout.String()
61+
62+
return js.ValueOf(outBody), nil
63+
}
64+
65+
func main() {
66+
args := getArgs()
67+
68+
if args.targetObject != "" {
69+
cliPackage := js.ValueOf(map[string]any{})
70+
cliPackage.Set("run", asyncFunc(wasmRun))
71+
js.Global().Set(args.targetObject, cliPackage)
72+
}
73+
74+
if args.callback != "" {
75+
givenCallback := js.Global().Get(args.callback)
76+
if !givenCallback.IsUndefined() {
77+
givenCallback.Invoke()
78+
}
79+
}
80+
<-make(chan struct{}, 0)
81+
}

internal/config/config.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"runtime"
89
"text/template"
910

1011
"gopkg.in/yaml.v3"
@@ -57,6 +58,14 @@ type Config struct {
5758
// returns a new empty config if file doesn't exist
5859
// return error if fail to load config file
5960
func LoadConfig(configPath string) (*Config, error) {
61+
if runtime.GOARCH == "wasm" {
62+
return &Config{
63+
Alias: alias.EmptyConfig(),
64+
Output: DefaultOutput,
65+
path: configPath,
66+
}, nil
67+
}
68+
6069
file, err := os.ReadFile(configPath)
6170
if err != nil {
6271
if os.IsNotExist(err) {
@@ -83,6 +92,10 @@ func LoadConfig(configPath string) (*Config, error) {
8392

8493
// Save marshal config to config file
8594
func (c *Config) Save() error {
95+
if runtime.GOARCH == "wasm" {
96+
return nil
97+
}
98+
8699
file, err := c.HumanConfig()
87100
if err != nil {
88101
return err

internal/core/client.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,32 @@ import (
55
"errors"
66
"fmt"
77
"net/http"
8+
"runtime"
89
"strings"
910

1011
"github.com/scaleway/scaleway-sdk-go/logger"
1112
"github.com/scaleway/scaleway-sdk-go/scw"
1213
"github.com/scaleway/scaleway-sdk-go/validation"
1314
)
1415

16+
func createWasmClient(httpClient *http.Client, buildInfo *BuildInfo) (*scw.Client, error) {
17+
opts := []scw.ClientOption{
18+
scw.WithDefaultRegion(scw.RegionFrPar),
19+
scw.WithDefaultZone(scw.ZoneFrPar1),
20+
scw.WithUserAgent(buildInfo.GetUserAgent()),
21+
scw.WithProfile(scw.LoadEnvProfile()),
22+
scw.WithHTTPClient(httpClient),
23+
}
24+
25+
return scw.NewClient(opts...)
26+
}
27+
1528
// createClient creates a Scaleway SDK client.
1629
func createClient(ctx context.Context, httpClient *http.Client, buildInfo *BuildInfo, profileName string) (*scw.Client, error) {
30+
if runtime.GOARCH == "wasm" {
31+
return createWasmClient(httpClient, buildInfo)
32+
}
33+
1734
profile := scw.LoadEnvProfile()
1835

1936
// Default path is based on the following priority order:

internal/core/cobra_utils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
2323
if !cmd.AllowAnonymousClient && !meta.isClientFromBootstrapConfig {
2424
client, err := createClient(ctx, meta.httpClient, meta.BuildInfo, ExtractProfileName(ctx))
2525
if err != nil {
26-
return err
26+
return fmt.Errorf("failed to create client: %w", err)
2727
}
2828
meta.Client = client
2929
err = validateClient(meta.Client)
3030
if err != nil {
31-
return err
31+
return fmt.Errorf("failed to validate client: %w", err)
3232
}
3333
}
3434

internal/core/shell.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
//go:build !freebsd
2-
// +build !freebsd
1+
//go:build !freebsd && !wasm
32

43
// shell is disabled on freebsd as current version of github.com/pkg/[email protected] is not compiling
54
package core

internal/core/shell_disabled.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
//go:build freebsd
2-
// +build freebsd
1+
//go:build freebsd || wasm
32

43
package core
54

internal/gotty/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build !wasm
2+
13
package gotty
24

35
import (

0 commit comments

Comments
 (0)