Skip to content

Commit

Permalink
feat: download cardinal editor to ~/.worldcli as cache
Browse files Browse the repository at this point in the history
  • Loading branch information
rmrt1n committed Mar 31, 2024
1 parent a7d11ec commit 81809a6
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 49 deletions.
6 changes: 6 additions & 0 deletions cmd/world/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/rs/zerolog/log"

"pkg.world.dev/world-cli/cmd/world/root"
"pkg.world.dev/world-cli/common/globalconfig"
"pkg.world.dev/world-cli/telemetry"

_ "pkg.world.dev/world-cli/common/logger"
Expand All @@ -29,6 +30,11 @@ func init() {
}

func main() {
err := globalconfig.SetupConfigDir()
if err != nil {
log.Err(err).Msg("could not setup config folder")
}

// Sentry initialization
telemetry.SentryInit(SentryDsn)
defer telemetry.SentryFlush()
Expand Down
28 changes: 28 additions & 0 deletions common/globalconfig/globalconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package globalconfig

import (
"os"
"path/filepath"
)

const (
configDir = ".worldcli"
)

func GetConfigDir() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}

return filepath.Join(homeDir, configDir), nil
}

func SetupConfigDir() error {
fullConfigDir, err := GetConfigDir()
if err != nil {
return err
}

return os.MkdirAll(fullConfigDir, 0755)
}
155 changes: 121 additions & 34 deletions common/teacmd/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"archive/zip"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
Expand All @@ -12,13 +14,14 @@ import (
"time"

"github.com/google/uuid"

"pkg.world.dev/world-cli/common/globalconfig"
)

const (
latestReleaseURL = "https://api.github.com/repos/Argus-Labs/cardinal-editor/releases/latest"
httpTimeout = 5 * time.Second
tmpZipFileName = "tmp.zip"
editorDir = ".editor"
httpTimeout = 2 * time.Second
targetEditorDir = ".editor"
cardinalProjectIDPlaceholder = "__CARDINAL_PROJECT_ID__"
)

Expand All @@ -27,60 +30,88 @@ type Asset struct {
}

type Release struct {
Name string `json:"name"`
Assets []Asset `json:"assets"`
}

func SetupCardinalEditor() error {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, latestReleaseURL, nil)
configDir, err := globalconfig.GetConfigDir()
if err != nil {
return err

Check warning on line 40 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L37-L40

Added lines #L37 - L40 were not covered by tests
}

editorDir, err := downloadReleaseIfNotCached(configDir)
if err != nil {
return err

Check warning on line 45 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L43-L45

Added lines #L43 - L45 were not covered by tests
}

// rename version tag dir to .editor
err = copyDir(editorDir, targetEditorDir)
if err != nil {
return err

Check warning on line 51 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L49-L51

Added lines #L49 - L51 were not covered by tests
}

// rename project id
// "ce" prefix is added because guids can start with numbers, which is not allowed in js
projectID := "ce" + strippedGUID()
err = replaceProjectIDs(targetEditorDir, projectID)
if err != nil {
return err

Check warning on line 59 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L56-L59

Added lines #L56 - L59 were not covered by tests
}

return nil

Check warning on line 62 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L62

Added line #L62 was not covered by tests
}

func downloadReleaseIfNotCached(configDir string) (string, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, latestReleaseURL, nil)
if err != nil {
return "", err

Check warning on line 68 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L68

Added line #L68 was not covered by tests
}

client := &http.Client{
Timeout: httpTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
return "", err

Check warning on line 76 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L76

Added line #L76 was not covered by tests
}
defer resp.Body.Close()

var release Release
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
return "", err

Check warning on line 83 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L83

Added line #L83 was not covered by tests
}
if err = json.Unmarshal(bodyBytes, &release); err != nil {
return err
return "", err

Check warning on line 86 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L86

Added line #L86 was not covered by tests
}

err = downloadRelease(release.Assets[0].BrowserDownloadURL)
if err != nil {
return err
}
editorDir := filepath.Join(configDir, "editor")

if err = unzipFile(); err != nil {
return err
targetDir := filepath.Join(editorDir, release.Name)
if _, err = os.Stat(targetDir); os.IsNotExist(err) {
return targetDir, downloadAndUnzip(release.Assets[0].BrowserDownloadURL, targetDir)
}

return nil
return targetDir, nil

Check warning on line 96 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L96

Added line #L96 was not covered by tests
}

func downloadRelease(url string) error {
func downloadAndUnzip(url string, targetDir string) error {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if err != nil {
return err

Check warning on line 102 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L102

Added line #L102 was not covered by tests
}

client := &http.Client{
Timeout: httpTimeout,
Timeout: httpTimeout + 10*time.Second,
}
resp, err := client.Do(req)
if err != nil {
return err
return errors.New(url)

Check warning on line 110 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L110

Added line #L110 was not covered by tests
}
defer resp.Body.Close()

tmpZipFileName := "tmp.zip"
file, err := os.Create(tmpZipFileName)
if err != nil {
return err

Check warning on line 117 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L117

Added line #L117 was not covered by tests
Expand All @@ -92,38 +123,46 @@ func downloadRelease(url string) error {
return err

Check warning on line 123 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L123

Added line #L123 was not covered by tests
}

return nil
if err = unzipFile(tmpZipFileName, targetDir); err != nil {
return err

Check warning on line 127 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L127

Added line #L127 was not covered by tests
}

return os.Remove(tmpZipFileName)
}

func unzipFile() error {
reader, err := zip.OpenReader(tmpZipFileName)
func unzipFile(filename string, targetDir string) error {
reader, err := zip.OpenReader(filename)
if err != nil {
return err

Check warning on line 136 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L136

Added line #L136 was not covered by tests
}
defer reader.Close()

// save original folder name
var originalDir string
for _, file := range reader.File {
for i, file := range reader.File {
if i == 0 {
originalDir = file.Name
}

src, err := file.Open()
if err != nil {
return err

Check warning on line 149 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L149

Added line #L149 was not covered by tests
}
defer src.Close()

filePath, err := sanitizeExtractPath(filepath.Dir(targetDir), file.Name)
if err != nil {
return err

Check warning on line 155 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L155

Added line #L155 was not covered by tests
}
if file.FileInfo().IsDir() {
if originalDir == "" {
originalDir = file.Name
}
err = os.MkdirAll(file.Name, 0777)
err = os.MkdirAll(filePath, 0755)
if err != nil {
return err

Check warning on line 160 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L160

Added line #L160 was not covered by tests
}
continue
}

dstPath := file.Name
dst, err := os.Create(dstPath)
dst, err := os.Create(filePath)
if err != nil {
return err

Check warning on line 167 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L167

Added line #L167 was not covered by tests
}
Expand All @@ -135,38 +174,86 @@ func unzipFile() error {
}
}

if err = os.Rename(originalDir, editorDir); err != nil {
if err = os.Rename(filepath.Join(filepath.Dir(targetDir), originalDir), targetDir); err != nil {
return err

Check warning on line 178 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L178

Added line #L178 was not covered by tests
}

if err = os.Remove(tmpZipFileName); err != nil {
return nil
}

func sanitizeExtractPath(dst string, filePath string) (string, error) {
dstPath := filepath.Join(dst, filePath)
if strings.HasPrefix(dstPath, filepath.Clean(dst)) {
return dstPath, nil
}
return "", fmt.Errorf("%s: illegal file path", filePath)

Check warning on line 189 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L189

Added line #L189 was not covered by tests
}

func copyDir(src string, dst string) error {
srcDir, err := os.ReadDir(src)
if err != nil {
return errors.New(src)

Check warning on line 195 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L195

Added line #L195 was not covered by tests
}

if err := os.MkdirAll(dst, 0755); err != nil {
return err

Check warning on line 199 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L199

Added line #L199 was not covered by tests
}

if err = replaceProjectIDs(); err != nil {
for _, entry := range srcDir {
srcPath := filepath.Join(src, entry.Name())
destPath := filepath.Join(dst, entry.Name())

if entry.IsDir() {
// Recursively copy dirs
if err := copyDir(srcPath, destPath); err != nil {
return err

Check warning on line 209 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L209

Added line #L209 was not covered by tests
}
} else {
if err := copyFile(srcPath, destPath); err != nil {
return err

Check warning on line 213 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L213

Added line #L213 was not covered by tests
}
}
}

return nil
}

func copyFile(src, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err

Check warning on line 224 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L224

Added line #L224 was not covered by tests
}
defer srcFile.Close()

destFile, err := os.Create(dest)
if err != nil {
return err

Check warning on line 230 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L230

Added line #L230 was not covered by tests
}
defer destFile.Close()

_, err = io.Copy(destFile, srcFile)
if err != nil {
return err

Check warning on line 236 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L236

Added line #L236 was not covered by tests
}

return nil
}

func replaceProjectIDs() error {
func replaceProjectIDs(editorDir string, newID string) error {
assetsDir := filepath.Join(editorDir, "assets")
files, err := os.ReadDir(assetsDir)
if err != nil {
return err

Check warning on line 246 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L246

Added line #L246 was not covered by tests
}

// "ce" prefix is added because guids can start with numbers, which is not allowed in js
projectID := "ce" + strippedGUID()
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".js") {
content, err := os.ReadFile(filepath.Join(assetsDir, file.Name()))
if err != nil {
return err

Check warning on line 253 in common/teacmd/editor.go

View check run for this annotation

Codecov / codecov/patch

common/teacmd/editor.go#L253

Added line #L253 was not covered by tests
}

newContent := strings.ReplaceAll(string(content), cardinalProjectIDPlaceholder, projectID)
newContent := strings.ReplaceAll(string(content), cardinalProjectIDPlaceholder, newID)

err = os.WriteFile(filepath.Join(assetsDir, file.Name()), []byte(newContent), 0600)
if err != nil {
Expand Down
Loading

0 comments on commit 81809a6

Please sign in to comment.