Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
30 changes: 30 additions & 0 deletions .github/workflows/wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: WebAssembly

on:
pull_request:
merge_group:

jobs:
build-and-test:
strategy:
matrix:
go-version: [1.19.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 6.0.2
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Build
run: ./scripts/build-wasm.sh
- name: Run npm package tests
run: ./scripts/run-tests-wasm.sh
22 changes: 22 additions & 0 deletions cmd/scw-wasm/args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//go:build wasm && js

package main

import "os"

type Args struct {
callback string
targetObject string
}

func getArgs() Args {
args := Args{}
if len(os.Args) > 0 {
args.callback = os.Args[0]
}
if len(os.Args) > 1 {
args.targetObject = os.Args[1]
}

return args
}
45 changes: 45 additions & 0 deletions cmd/scw-wasm/async.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//go:build wasm && js

package main

import (
"fmt"
"syscall/js"
)

type fn func(this js.Value, args []js.Value) (any, error)

var (
jsErr js.Value = js.Global().Get("Error")
jsObject js.Value = js.Global().Get("Object")
jsPromise js.Value = js.Global().Get("Promise")
)

func asyncFunc(innerFunc fn) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
handler := js.FuncOf(func(_ js.Value, promFn []js.Value) any {
resolve, reject := promFn[0], promFn[1]

go func() {
defer func() {
if r := recover(); r != nil {
reject.Invoke(jsErr.New(fmt.Sprint("panic:", r)))
}
}()

res, err := innerFunc(this, args)
if err != nil {
errContent := jsObject.New()
errContent.Set("cause", err.Error())
reject.Invoke(jsErr.New(res, errContent))
} else {
resolve.Invoke(res)
}
}()

return nil
})

return jsPromise.New(handler)
})
}
81 changes: 81 additions & 0 deletions cmd/scw-wasm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//go:build wasm && js

package main

import (
"bytes"
"fmt"
"io"
"syscall/js"

"github.com/scaleway/scaleway-cli/v2/internal/core"
"github.com/scaleway/scaleway-cli/v2/internal/namespaces"
)

var commands *core.Commands

func getCommands() *core.Commands {
if commands == nil {
commands = namespaces.GetCommands()
}
return commands
}

func runCommand(args []string, stdout io.Writer, stderr io.Writer) chan int {
ret := make(chan int, 1)
go func() {
exitCode, _, _ := core.Bootstrap(&core.BootstrapConfig{
Args: args,
Commands: getCommands(),
BuildInfo: &core.BuildInfo{},
Stdout: stdout,
Stderr: stderr,
Stdin: nil,
})
ret <- exitCode
}()

return ret
}

func wasmRun(this js.Value, args []js.Value) (any, error) {
cliArgs := []string{"scw"}
stdout := bytes.NewBuffer(nil)
stderr := bytes.NewBuffer(nil)

for argIndex, arg := range args {
if arg.Type() != js.TypeString {
return nil, fmt.Errorf("invalid argument at index %d", argIndex)
}
cliArgs = append(cliArgs, arg.String())
}

exitCodeChan := runCommand(cliArgs, stdout, stderr)
exitCode := <-exitCodeChan
if exitCode != 0 {
errBody := stderr.String()
return js.ValueOf(errBody), fmt.Errorf("exit code: %d", exitCode)
}

outBody := stdout.String()

return js.ValueOf(outBody), nil
}

func main() {
args := getArgs()

if args.targetObject != "" {
cliPackage := js.ValueOf(map[string]any{})
cliPackage.Set("run", asyncFunc(wasmRun))
js.Global().Set(args.targetObject, cliPackage)
}

if args.callback != "" {
givenCallback := js.Global().Get(args.callback)
if !givenCallback.IsUndefined() {
givenCallback.Invoke()
}
}
<-make(chan struct{}, 0)
}
13 changes: 13 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"text/template"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -57,6 +58,14 @@ type Config struct {
// returns a new empty config if file doesn't exist
// return error if fail to load config file
func LoadConfig(configPath string) (*Config, error) {
if runtime.GOARCH == "wasm" {
return &Config{
Alias: alias.EmptyConfig(),
Output: DefaultOutput,
path: configPath,
}, nil
}

file, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
Expand All @@ -83,6 +92,10 @@ func LoadConfig(configPath string) (*Config, error) {

// Save marshal config to config file
func (c *Config) Save() error {
if runtime.GOARCH == "wasm" {
return nil
}

file, err := c.HumanConfig()
if err != nil {
return err
Expand Down
17 changes: 17 additions & 0 deletions internal/core/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,32 @@ import (
"errors"
"fmt"
"net/http"
"runtime"
"strings"

"github.com/scaleway/scaleway-sdk-go/logger"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/validation"
)

func createWasmClient(httpClient *http.Client, buildInfo *BuildInfo) (*scw.Client, error) {
opts := []scw.ClientOption{
scw.WithDefaultRegion(scw.RegionFrPar),
scw.WithDefaultZone(scw.ZoneFrPar1),
scw.WithUserAgent(buildInfo.GetUserAgent()),
scw.WithProfile(scw.LoadEnvProfile()),
scw.WithHTTPClient(httpClient),
}

return scw.NewClient(opts...)
}

// createClient creates a Scaleway SDK client.
func createClient(ctx context.Context, httpClient *http.Client, buildInfo *BuildInfo, profileName string) (*scw.Client, error) {
if runtime.GOARCH == "wasm" {
return createWasmClient(httpClient, buildInfo)
}

profile := scw.LoadEnvProfile()

// Default path is based on the following priority order:
Expand Down
4 changes: 2 additions & 2 deletions internal/core/cobra_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
if !cmd.AllowAnonymousClient && !meta.isClientFromBootstrapConfig {
client, err := createClient(ctx, meta.httpClient, meta.BuildInfo, ExtractProfileName(ctx))
if err != nil {
return err
return fmt.Errorf("failed to create client: %w", err)
}
meta.Client = client
err = validateClient(meta.Client)
if err != nil {
return err
return fmt.Errorf("failed to validate client: %w", err)
}
}

Expand Down
3 changes: 1 addition & 2 deletions internal/core/shell.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build !freebsd
// +build !freebsd
//go:build !freebsd && !wasm

// shell is disabled on freebsd as current version of github.com/pkg/term@v1.1.0 is not compiling
package core
Expand Down
3 changes: 1 addition & 2 deletions internal/core/shell_disabled.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build freebsd
// +build freebsd
//go:build freebsd || wasm

package core

Expand Down
2 changes: 2 additions & 0 deletions internal/gotty/client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !wasm

package gotty

import (
Expand Down
3 changes: 1 addition & 2 deletions internal/gotty/resize.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//nolint
// +build !windows
//go:build !windows && !wasm

package gotty

Expand Down
2 changes: 2 additions & 0 deletions internal/interactive/prompt.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !wasm

package interactive

import (
Expand Down
52 changes: 52 additions & 0 deletions internal/interactive/prompt_disabled.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//go:build wasm

package interactive

import (
"context"
"fmt"
)

type PromptPasswordConfig struct {
Ctx context.Context
Prompt string
}

func PromptPasswordWithConfig(config *PromptPasswordConfig) (string, error) {
return "", fmt.Errorf("prompt is disabled for this build")
}

type PromptBoolConfig struct {
Ctx context.Context
Prompt string
DefaultValue bool
}

func PromptBoolWithConfig(config *PromptBoolConfig) (bool, error) {
return config.DefaultValue, nil
}

type PromptStringConfig struct {
Ctx context.Context
Prompt string
DefaultValue string
DefaultValueDoc string
ValidateFunc ValidateFunc
}

func PromptStringWithConfig(config *PromptStringConfig) (string, error) {
return config.DefaultValue, nil
}

type ReadlineConfig struct {
Ctx context.Context
Prompt string
PromptFunc func(string) string
Password bool
ValidateFunc ValidateFunc
DefaultValue string
}

func Readline(config *ReadlineConfig) (string, error) {
return "", fmt.Errorf("prompt is disabled for this build")
}
2 changes: 2 additions & 0 deletions internal/interactive/utils.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !wasm

package interactive

import (
Expand Down
23 changes: 23 additions & 0 deletions internal/interactive/utils_wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//go:build wasm

package interactive

import (
"io"
)

type ValidateFunc func(string) error

var (
// IsInteractive must be set to print anything with Printer functions (Print, Printf,...).
IsInteractive = false

// outputWriter is the writer used by Printer functions (Print, Printf,...).
outputWriter io.Writer
)

// SetOutputWriter set the output writer that will be used by both Printer functions (Print, Printf,...) and
// readline prompter. This should be called once from the bootstrap function.
func SetOutputWriter(w io.Writer) {
outputWriter = w
}
4 changes: 3 additions & 1 deletion internal/namespaces/container/v1beta1/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ func GetCommands() *core.Commands {
cmds.MustFind("container", "namespace", "update").Override(containerNamespaceUpdateBuilder)
cmds.MustFind("container", "namespace", "delete").Override(containerNamespaceDeleteBuilder)

cmds.Add(containerDeployCommand())
if cmdDeploy := containerDeployCommand(); cmdDeploy != nil {
cmds.Add(cmdDeploy)
}

return cmds
}
2 changes: 2 additions & 0 deletions internal/namespaces/container/v1beta1/custom_deploy.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !wasm

package container

import (
Expand Down
Loading