diff --git a/cmd/cloud.go b/cmd/cloud.go index 5e4aa2ed..68aa3a30 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -251,26 +251,35 @@ Examples: deploymentConfig.Runtime = theproject.Bundler.Runtime deploymentConfig.Command = append([]string{theproject.Deployment.Command}, theproject.Deployment.Args...) - if err := deployer.PreflightCheck(ctx, logger, deployer.DeployPreflightCheckData{ - Dir: dir, - APIClient: client, - APIURL: apiUrl, - APIKey: token, - Envfile: envFile, - Project: theproject, - ProjectData: projectData, - Config: deploymentConfig, - OSEnvironment: loadOSEnv(), - PromptHelpers: createPromptHelper(), - }); err != nil { - errsystem.New(errsystem.ErrDeployProject, err).ShowErrorAndExit() - } - - if err := deploymentConfig.Write(logger, dir); err != nil { - errsystem.New(errsystem.ErrWriteConfigurationFile, err, - errsystem.WithContextMessage("Error writing deployment config to disk")).ShowErrorAndExit() + var zipMutator util.ZipDirCallbackMutator + + preflightAction := func() { + zm, err := deployer.PreflightCheck(ctx, logger, deployer.DeployPreflightCheckData{ + Dir: dir, + APIClient: client, + APIURL: apiUrl, + APIKey: token, + Envfile: envFile, + Project: theproject, + ProjectData: projectData, + Config: deploymentConfig, + OSEnvironment: loadOSEnv(), + PromptHelpers: createPromptHelper(), + }) + if err != nil { + errsystem.New(errsystem.ErrDeployProject, err).ShowErrorAndExit() + } + + if err := deploymentConfig.Write(logger, dir); err != nil { + errsystem.New(errsystem.ErrWriteConfigurationFile, err, + errsystem.WithContextMessage("Error writing deployment config to disk")).ShowErrorAndExit() + } + + zipMutator = zm } + tui.ShowSpinner("Bundling ...", preflightAction) + var startResponse startResponse var startRequest startRequest @@ -469,7 +478,7 @@ Examples: // zip up our directory started := time.Now() logger.Debug("creating a zip file of %s into %s", dir, tmpfile.Name()) - if err := util.ZipDir(dir, tmpfile.Name(), func(fn string, fi os.FileInfo) bool { + if err := util.ZipDir(dir, tmpfile.Name(), util.WithMutator(zipMutator), util.WithMatcher(func(fn string, fi os.FileInfo) bool { notok := rules.Ignore(fn, fi) if notok { logger.Trace("❌ %s", fn) @@ -477,7 +486,7 @@ Examples: logger.Trace("❎ %s", fn) } return !notok - }); err != nil { + })); err != nil { errsystem.New(errsystem.ErrCreateZipFile, err, errsystem.WithContextMessage("Error zipping project")).ShowErrorAndExit() } @@ -575,7 +584,7 @@ Examples: } } - tui.ShowSpinner("Deploying ...", action) + tui.ShowSpinner("Uploading ...", action) format, _ := cmd.Flags().GetString("format") if format == "json" { diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index cce6b741..5f8ac869 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -1,6 +1,7 @@ package bundler import ( + "archive/zip" "context" "fmt" "io" @@ -269,6 +270,26 @@ var ( pyProjectVersionRegex = regexp.MustCompile(`version\s+=\s+"(.*?)"`) ) +/* NOTE: leaving this here for now but we don't need it for now but this will allow you to run uv python commands with virtual env +func runUVPython(ctx BundleContext, dir string, args ...string) ([]byte, error) { + venvPath := ".venv" + // python3 -m pip install --upgrade pip + pythonPath := filepath.Join(dir, venvPath, "bin", "python3") + fmt.Println(pythonPath, strings.Join(args, " ")) + cmd := exec.CommandContext(ctx.Context, pythonPath, args...) + env := os.Environ() + env = append(env, "VIRTUAL_ENV="+venvPath) + env = append(env, "PATH="+filepath.Join(venvPath, "bin")+":"+os.Getenv("PATH")) + cmd.Env = env + cmd.Dir = dir + cmd.Stdin = os.Stdin + out, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("failed to run uv: %w. %s", err, string(out)) + } + return out, nil +}*/ + func bundlePython(ctx BundleContext, dir string, outdir string, theproject *project.Project) error { if ctx.Install || !util.Exists(filepath.Join(dir, ".venv", "lib")) { @@ -347,6 +368,15 @@ func getAgents(theproject *project.Project, filename string) []AgentConfig { return agents } +func CreateDeploymentMutator(ctx BundleContext) util.ZipDirCallbackMutator { + return func(writer *zip.Writer) error { + // NOTE: for now we don't need to do anything here + // but this is a hook for future use where we can add files to the zip + // before it is uploaded to the cloud + return nil + } +} + func Bundle(ctx BundleContext) error { theproject := project.NewProject() if err := theproject.Load(ctx.ProjectDir); err != nil { @@ -367,7 +397,6 @@ func Bundle(ctx BundleContext) error { if err := os.MkdirAll(outdir, 0755); err != nil { return fmt.Errorf("failed to create .agentuity directory: %w", err) } - ctx.Logger.Debug("bundling project %s to %s", dir, outdir) switch theproject.Bundler.Language { case "javascript": return bundleJavascript(ctx, dir, outdir, theproject) diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 60c69909..99304216 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -67,17 +67,18 @@ type DeployPreflightCheckData struct { OSEnvironment map[string]string } -func PreflightCheck(ctx context.Context, logger logger.Logger, data DeployPreflightCheckData) error { +func PreflightCheck(ctx context.Context, logger logger.Logger, data DeployPreflightCheckData) (util.ZipDirCallbackMutator, error) { started := time.Now() - if err := bundler.Bundle(bundler.BundleContext{ + bundleCtx := bundler.BundleContext{ Context: context.Background(), Logger: logger, ProjectDir: data.Dir, Production: true, Project: data.Project, - }); err != nil { - return err + } + if err := bundler.Bundle(bundleCtx); err != nil { + return nil, err } logger.Debug("bundled in %s", time.Since(started)) - return nil + return bundler.CreateDeploymentMutator(bundleCtx), nil } diff --git a/internal/util/io.go b/internal/util/io.go index b42481d7..5dffb33a 100644 --- a/internal/util/io.go +++ b/internal/util/io.go @@ -105,8 +105,31 @@ func ListDir(dir string) ([]string, error) { // ZipDirCallbackMatcher is a function that returns true if the file should be included in the zip type ZipDirCallbackMatcher func(fn string, fi os.FileInfo) bool +type ZipDirCallbackMutator func(writer *zip.Writer) error + +type options struct { + matcher ZipDirCallbackMatcher + mutator ZipDirCallbackMutator +} + +type Option func(*options) + +// WithMatcher will filter the files that are added to the zip +func WithMatcher(matcher ZipDirCallbackMatcher) Option { + return func(o *options) { + o.matcher = matcher + } +} + +// WithMutator will mutate the zip file after it has been created allowing you to add files to the zip +func WithMutator(mutator ZipDirCallbackMutator) Option { + return func(o *options) { + o.mutator = mutator + } +} + // ZipDir will zip up a directory into the outfilename and return an error if it fails -func ZipDir(dir string, outfilename string, opts ...ZipDirCallbackMatcher) error { +func ZipDir(dir string, outfilename string, opts ...Option) error { zf, err := os.Create(outfilename) if err != nil { return fmt.Errorf("error opening: %s. %w", outfilename, err) @@ -118,6 +141,12 @@ func ZipDir(dir string, outfilename string, opts ...ZipDirCallbackMatcher) error if err != nil { return fmt.Errorf("error listing files: %w", err) } + var options options + if len(opts) > 0 { + for _, opt := range opts { + opt(&options) + } + } for _, file := range files { fn, err := filepath.Rel(dir, file) if err != nil { @@ -131,16 +160,11 @@ func ZipDir(dir string, outfilename string, opts ...ZipDirCallbackMatcher) error if err != nil { return fmt.Errorf("error getting file info: %s. %w", file, err) } - var notok bool - for _, opt := range opts { - if !opt(fn, fi) { - notok = true - break + if options.matcher != nil { + if !options.matcher(fn, fi) { + continue } } - if notok { - continue - } } rf, err := os.Open(file) if err != nil { @@ -157,6 +181,11 @@ func ZipDir(dir string, outfilename string, opts ...ZipDirCallbackMatcher) error } rf.Close() } + if options.mutator != nil { + if err := options.mutator(zw); err != nil { + return fmt.Errorf("error mutating zip: %w", err) + } + } zw.Flush() zw.Close() return zf.Close()