Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion pkg/action/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package action

import (
"bufio"
"context"
"fmt"
"os"
"syscall"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/werf/3p-helm/pkg/chartutil"
"github.com/werf/3p-helm/pkg/provenance"
"github.com/werf/3p-helm/pkg/werf/helmopts"
tsbundle "github.com/werf/3p-helm/pkg/werf/ts"
)

// Package is the action for packaging a chart.
Expand Down Expand Up @@ -61,6 +63,12 @@ func (p *Package) Run(path string, _ map[string]interface{}, opts helmopts.HelmO
return "", err
}

if tsbundle.BundleEnabled {
if err := tsbundle.BundleTSChartsRecursive(context.Background(), ch, path, true); err != nil {
return "", errors.Wrap(err, "unable to process TypeScript files in chart")
}
}

// If version is set, modify the version.
if p.Version != "" {
ch.Metadata.Version = p.Version
Expand Down Expand Up @@ -137,7 +145,7 @@ func (p *Package) Clearsign(filename string, opts helmopts.HelmOptions) error {
return err
}

return os.WriteFile(filename+".prov", []byte(sig), 0644)
return os.WriteFile(filename+".prov", []byte(sig), 0o644)
}

// promptUser implements provenance.PassphraseFetcher
Expand Down
36 changes: 34 additions & 2 deletions pkg/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"regexp"
"strings"

"github.com/samber/lo"
"github.com/werf/3p-helm/pkg/werf/secrets/runtimedata"
)

Expand Down Expand Up @@ -55,8 +56,6 @@ type Chart struct {
Files []*File `json:"files" copy:"shallow"`
// Files that are used at runtime, but should not be saved to secret/configmap.
RuntimeFiles []*File `json:"-" copy:"shallow"`
// Dependencies for RuntimeFiles that are used at runtime, but should not be saved to secret/configmap and not added to packaged chart.
RuntimeDepsFiles []*File `json:"-" copy:"shallow"`

parent *Chart
dependencies []*Chart
Expand Down Expand Up @@ -180,3 +179,36 @@ func hasManifestExtension(fname string) bool {
ext := filepath.Ext(fname)
return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json")
}

func (ch *Chart) AddRuntimeFile(name string, data []byte) {
ch.Raw = append(ch.Raw, &File{Name: name, Data: data})

ch.RuntimeFiles = append(ch.RuntimeFiles, &File{Name: name, Data: data})
if !ch.IsRoot() {
root := ch.Root()
rawName := getRootRawFileName(ch, name)
root.Raw = append(root.Raw, &File{Name: rawName, Data: data})
}
}

func (ch *Chart) RemoveRuntimeFile(name string) {
ch.Raw = lo.Reject(ch.Raw, func(f *File, _ int) bool {
return f.Name == name
})

ch.RuntimeFiles = lo.Reject(ch.RuntimeFiles, func(f *File, _ int) bool {
return f.Name == name
})

if !ch.IsRoot() {
root := ch.Root()
rawName := getRootRawFileName(ch, name)
root.Raw = lo.Reject(root.Raw, func(f *File, _ int) bool {
return f.Name == rawName
})
}
}

func getRootRawFileName(ch *Chart, name string) string {
return filepath.Join(strings.TrimPrefix(ch.ChartFullPath(), ch.Root().Name()+"/"), name)
}
4 changes: 1 addition & 3 deletions pkg/chart/loader/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,7 @@ func LoadFiles(files []*BufferedFile, opts helmopts.HelmOptions) (*chart.Chart,
fname := strings.TrimPrefix(f.Name, "charts/")
cname := strings.SplitN(fname, "/", 2)[0]
subcharts[cname] = append(subcharts[cname], &BufferedFile{Name: fname, Data: f.Data})
case strings.HasPrefix(f.Name, "ts/node_modules/"):
c.RuntimeDepsFiles = append(c.RuntimeDepsFiles, &chart.File{Name: f.Name, Data: f.Data})
case strings.HasPrefix(f.Name, "ts/"):
case strings.HasPrefix(f.Name, "ts/") && !strings.HasPrefix(f.Name, "ts/node_modules/"):
c.RuntimeFiles = append(c.RuntimeFiles, &chart.File{Name: f.Name, Data: f.Data})
default:
c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data})
Expand Down
137 changes: 137 additions & 0 deletions pkg/werf/ts/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package tsbundle

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/samber/lo"

helmchart "github.com/werf/3p-helm/pkg/chart"
)

const (
// ChartTSSourceDir is the directory containing TypeScript sources in a Helm chart.
ChartTSSourceDir = "ts/"
// ChartTSBundleFile is the path to the bundle in a Helm chart.
ChartTSBundleFile = ChartTSSourceDir + "dist/bundle.js"
// ChartTSEntryPointTS is the TypeScript entry point path.
ChartTSEntryPointTS = "src/index.ts"
// ChartTSEntryPointJS is the JavaScript entry point path.
ChartTSEntryPointJS = "src/index.js"
)

// ChartTSEntryPoints defines supported TypeScript/JavaScript entry points (in priority order).
var ChartTSEntryPoints = [...]string{ChartTSEntryPointTS, ChartTSEntryPointJS}

var BundleEnabled = false

func GetDenoBinary() string {
if denoBin, ok := os.LookupEnv("DENO_BIN"); ok && denoBin != "" {
return denoBin
}

return "deno"
}

func RunDenoBundle(ctx context.Context, chartPath, entryPoint string) ([]uint8, error) {
denoBin := GetDenoBinary()
cmd := exec.CommandContext(ctx, denoBin, "bundle", entryPoint)
cmd.Dir = filepath.Join(chartPath, ChartTSSourceDir)

output, err := cmd.Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
_, _ = os.Stderr.Write(exitErr.Stderr)
}

return nil, fmt.Errorf("get deno build output: %w", err)
}

return output, nil
}

func BundleTSChartsRecursive(ctx context.Context, chart *helmchart.Chart, path string, rebuild bool) error {
entrypoint, bundle := GetEntrypointAndBundle(chart.RuntimeFiles)
if entrypoint == "" {
return nil
}

if bundle == nil || rebuild {
bundleRes, err := RunDenoBundle(ctx, path, entrypoint)
if err != nil {
return fmt.Errorf("build TypeScript bundle: %w", err)
}

if rebuild && bundle != nil {
chart.RemoveRuntimeFile(ChartTSBundleFile)
}

chart.AddRuntimeFile(ChartTSBundleFile, bundleRes)
}

deps := chart.Dependencies()
if len(deps) == 0 {
return nil
}

for _, dep := range deps {
depPath := filepath.Join(path, "charts", dep.Name())

if _, err := os.Stat(depPath); err != nil {
// Subchart loaded from .tgz or missing on disk — skip,
// deno bundle needs a real directory to work with.
continue
}

if err := BundleTSChartsRecursive(ctx, dep, depPath, rebuild); err != nil {
return fmt.Errorf("process dependency %q: %w", dep.Name(), err)
}
}

return nil
}

func GetEntrypointAndBundle(files []*helmchart.File) (string, *helmchart.File) {
entrypoint := findEntrypointInFiles(files)
if entrypoint == "" {
return "", nil
}

bundleFile, foundBundle := lo.Find(files, func(f *helmchart.File) bool {
return f.Name == ChartTSBundleFile
})

if !foundBundle {
return entrypoint, nil
}

return entrypoint, bundleFile
}

func findEntrypointInFiles(files []*helmchart.File) string {
sourceFiles := make(map[string][]byte)

for _, f := range files {
if strings.HasPrefix(f.Name, ChartTSSourceDir+"src/") {
sourceFiles[strings.TrimPrefix(f.Name, ChartTSSourceDir)] = f.Data
}
}

if len(sourceFiles) == 0 {
return ""
}

for _, ep := range ChartTSEntryPoints {
if _, ok := sourceFiles[ep]; ok {
return ep
}
}

return ""
}
Loading