diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86dae8ce58..884fe6fb26 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,25 +32,25 @@ jobs: # === OS Specific Job (runs on each OS) === os_specific: - name: ${{ matrix.platform }} + name: ${{ matrix.sys.os }} timeout-minutes: 90 strategy: matrix: go-version: - 1.20.x - platform: - - ubuntu-20.04 - - macos-11 - - windows-2019 + sys: + - {os: ubuntu-20.04} + - {os: macos-11, shell: zsh} + - {os: windows-2019} fail-fast: false - runs-on: ${{ matrix.platform }} + runs-on: ${{ matrix.sys.os }} env: ACTIVESTATE_CI: true ACTIVESTATE_CLI_DISABLE_RUNTIME: true SHELL: bash GITHUB_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} concurrency: - group: ${{ github.ref }}-${{ github.event_name }}-${{ matrix.platform }} + group: ${{ github.ref }}-${{ github.event_name }}-${{ matrix.sys.os }} cancel-in-progress: true # === OS Specific Steps === @@ -326,7 +326,7 @@ jobs: if [[ "$TEST_SUITE_TAGS" == "all" ]]; then TIMEOUT=60m fi - SHELL='' go test -timeout $TIMEOUT -v `go list ./... | grep "integration"` -json 2>&1 | gotestfmt -hide empty-packages + SHELL='${{ matrix.sys.shell }}' go test -timeout $TIMEOUT -v `go list ./... | grep "integration"` -json 2>&1 | gotestfmt -hide empty-packages continue-on-error: ${{ github.event_name == 'schedule' }} env: INTEGRATION_TEST_USERNAME: ${{ secrets.INTEGRATION_TEST_USERNAME }} @@ -367,7 +367,7 @@ jobs: "type": "section", "text": { "type": "plain_text", - "text": "Select the '${{ matrix.platform }}' job and expand 'Integration Tests' to inspect the failures." + "text": "Select the '${{ matrix.sys.os }}' job and expand 'Integration Tests' to inspect the failures." } } ] @@ -386,7 +386,7 @@ jobs: name: Upload Session Artifacts uses: actions/upload-artifact@v2 with: - name: session-build-${{ matrix.platform }} + name: session-build-${{ matrix.sys.os }} path: build/ # === Deploy job (runs once with combined artifacts from OS specific job) === diff --git a/activestate.offlineinstall.yaml b/activestate.offlineinstall.yaml deleted file mode 100644 index 8e3b1d7867..0000000000 --- a/activestate.offlineinstall.yaml +++ /dev/null @@ -1,37 +0,0 @@ -constants: - - name: BUILD_OFFINSTALL_TARGET - if: ne .OS.Name "Windows" - value: offline-installer - - name: BUILD_OFFINSTALL_TARGET - if: eq .OS.Name "Windows" - value: offline-installer.exe - - name: OFFINSTALL_PKGS - value: ./cmd/state-offline-installer - - name: BUILD_OFFUNINSTALL_TARGET - if: ne .OS.Name "Windows" - value: uninstall - - name: BUILD_OFFUNINSTALL_TARGET - if: eq .OS.Name "Windows" - value: uninstall.exe - - name: OFFUNINSTALL_PKGS - value: ./cmd/state-offline-uninstaller -scripts: - - name: build-offline-installer - language: bash - description: Builds the project with the host OS as the target OS. - value: | - set -e - $constants.SET_ENV - - rm $BUILD_TARGET_DIR/offline/${constants.BUILD_OFFINSTALL_TARGET} || : - go build -tags "$GO_BUILD_TAGS" -o $BUILD_TARGET_DIR/offline/$constants.BUILD_OFFINSTALL_TARGET $constants.CLI_BUILDFLAGS $constants.OFFINSTALL_PKGS - go build -tags "$GO_BUILD_TAGS" -o $BUILD_TARGET_DIR/offline/$constants.BUILD_OFFUNINSTALL_TARGET $constants.CLI_BUILDFLAGS $constants.OFFUNINSTALL_PKGS - - name: pkg-offline-installer - language: bash - description: Packages the installer / uninstaller with the assets it requires - value: | - set -e - $constants.SET_ENV - - cd $BUILD_TARGET_DIR/offline/ - gozip -c ${constants.BUILD_OFFINSTALL_TARGET} artifacts.tar.gz LICENSE.txt installer_config.json $constants.BUILD_OFFUNINSTALL_TARGET diff --git a/architecture.md b/architecture.md index 43d0c374de..c3bbdd2153 100644 --- a/architecture.md +++ b/architecture.md @@ -64,8 +64,7 @@ from use by external code. #### internal/runbits/ Packages that are made available for use by "runner" packages. In essence, -`internal/runners/internal/runbits`. A synonymous and deprecated directory -exists at `pkg/cmdlets/`. +`internal/runners/internal/runbits`. #### internal/runners/ diff --git a/cmd/state-installer/cmd.go b/cmd/state-installer/cmd.go index 5dfc5bfc6f..7f12ec4337 100644 --- a/cmd/state-installer/cmd.go +++ b/cmd/state-installer/cmd.go @@ -29,10 +29,10 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/rollbar" + "github.com/ActiveState/cli/internal/runbits/errors" "github.com/ActiveState/cli/internal/runbits/panics" "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/cli/internal/subshell/bash" - "github.com/ActiveState/cli/pkg/cmdlets/errors" "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/sysinfo" "golang.org/x/crypto/ssh/terminal" @@ -381,7 +381,7 @@ func postInstallEvents(out output.Outputer, cfg *config.Instance, an analytics.D case params.command != "": an.Event(anaConst.CatInstallerFunnel, "forward-command") - out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]%s[/RESET]`\n", params.command)) + out.Print(fmt.Sprintf("\nRunning '[ACTIONABLE]%s[/RESET]'\n", params.command)) cmd, args := exeutils.DecodeCmd(params.command) if _, _, err := exeutils.ExecuteAndPipeStd(cmd, args, envSlice(binPath)); err != nil { an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-command-err", err.Error()) @@ -391,7 +391,7 @@ func postInstallEvents(out output.Outputer, cfg *config.Instance, an analytics.D case params.activate.IsValid(): an.Event(anaConst.CatInstallerFunnel, "forward-activate") - out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]state activate %s[/RESET]`\n", params.activate.String())) + out.Print(fmt.Sprintf("\nRunning '[ACTIONABLE]state activate %s[/RESET]'\n", params.activate.String())) if _, _, err := exeutils.ExecuteAndPipeStd(stateExe, []string{"activate", params.activate.String()}, envSlice(binPath)); err != nil { an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-activate-err", err.Error()) return errs.Silence(errs.Wrap(err, "Could not activate %s, error returned: %s", params.activate.String(), errs.JoinMessage(err))) @@ -400,7 +400,7 @@ func postInstallEvents(out output.Outputer, cfg *config.Instance, an analytics.D case params.activateDefault.IsValid(): an.Event(anaConst.CatInstallerFunnel, "forward-activate-default") - out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]state activate --default %s[/RESET]`\n", params.activateDefault.String())) + out.Print(fmt.Sprintf("\nRunning '[ACTIONABLE]state activate --default %s[/RESET]'\n", params.activateDefault.String())) if _, _, err := exeutils.ExecuteAndPipeStd(stateExe, []string{"activate", params.activateDefault.String(), "--default"}, envSlice(binPath)); err != nil { an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-activate-default-err", err.Error()) return errs.Silence(errs.Wrap(err, "Could not activate %s, error returned: %s", params.activateDefault.String(), errs.JoinMessage(err))) diff --git a/cmd/state-installer/test/integration/installer_int_test.go b/cmd/state-installer/test/integration/installer_int_test.go index 5b4547e49b..d08dea5d9d 100644 --- a/cmd/state-installer/test/integration/installer_int_test.go +++ b/cmd/state-installer/test/integration/installer_int_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/suite" - "github.com/ActiveState/cli/internal/condition" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/environment" @@ -52,9 +51,6 @@ func (suite *InstallerIntegrationTestSuite) TestInstallFromLocalSource() { cp.Expect("Installing State Tool") cp.Expect("Done") cp.Expect("successfully installed") - if runtime.GOOS == "darwin" && condition.OnCI() { - cp.Expect("You are running bash on macOS") - } suite.NotContains(cp.Output(), "Downloading State Tool") cp.ExpectInput() cp.SendLine("exit") @@ -145,6 +141,7 @@ func (suite *InstallerIntegrationTestSuite) TestInstallIncompatible() { // Assert output cp.Expect("not compatible") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *InstallerIntegrationTestSuite) TestInstallNoErrorTips() { @@ -164,6 +161,7 @@ func (suite *InstallerIntegrationTestSuite) TestInstallNoErrorTips() { cp.ExpectExitCode(1) suite.Assert().NotContains(cp.Output(), "Need More Help?", "error tips should not be displayed when invoking installer") + ts.IgnoreLogErrors() } func (suite *InstallerIntegrationTestSuite) TestInstallErrorTips() { @@ -210,7 +208,7 @@ func (suite *InstallerIntegrationTestSuite) TestInstallerOverwriteServiceApp() { ) cp.Expect("Done") cp.SendLine("exit") - cp.ExpectExitCode(0) + cp.ExpectExit() // the return code can vary depending on shell (e.g. zsh vs. bash); just assert the installer shell exited // State Service.app should be overwritten cleanly without error. cp = ts.SpawnCmdWithOpts( @@ -220,7 +218,7 @@ func (suite *InstallerIntegrationTestSuite) TestInstallerOverwriteServiceApp() { ) cp.Expect("Done") cp.SendLine("exit") - cp.ExpectExitCode(0) + cp.ExpectExit() // the return code can vary depending on shell (e.g. zsh vs. bash); just assert the installer shell exited } func (suite *InstallerIntegrationTestSuite) AssertConfig(ts *e2e.Session) { diff --git a/cmd/state-offline-installer/install.go b/cmd/state-offline-installer/install.go deleted file mode 100644 index 25dc4db130..0000000000 --- a/cmd/state-offline-installer/install.go +++ /dev/null @@ -1,492 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - rt "runtime" - - "github.com/ActiveState/cli/internal/analytics" - ac "github.com/ActiveState/cli/internal/analytics/constants" - "github.com/ActiveState/cli/internal/analytics/dimensions" - "github.com/ActiveState/cli/internal/assets" - "github.com/ActiveState/cli/internal/config" - "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/exeutils" - "github.com/ActiveState/cli/internal/fileutils" - "github.com/ActiveState/cli/internal/locale" - "github.com/ActiveState/cli/internal/offinstall" - "github.com/ActiveState/cli/internal/osutils" - "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/internal/primer" - "github.com/ActiveState/cli/internal/prompt" - "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/internal/subshell/sscommon" - "github.com/ActiveState/cli/internal/testhelpers/outputhelper" - "github.com/ActiveState/cli/internal/unarchiver" - "github.com/ActiveState/cli/pkg/platform/runtime" - "github.com/ActiveState/cli/pkg/platform/runtime/setup/events" - "github.com/ActiveState/cli/pkg/platform/runtime/target" - "github.com/ActiveState/cli/pkg/project" - "github.com/vbauerster/mpb/v7" - "github.com/vbauerster/mpb/v7/decor" -) - -const artifactsTarGZName = "artifacts.tar.gz" -const assetsPathName = "assets" -const artifactsPathName = "artifacts" -const licenseFileName = "LICENSE.txt" -const installerConfigFileName = "installer_config.json" -const uninstallerFileNameRoot = "uninstall" + exeutils.Extension - -type runner struct { - out output.Outputer - prompt prompt.Prompter - analytics analytics.Dispatcher - cfg *config.Instance - shell subshell.SubShell - icfg InstallerConfig -} - -type primeable interface { - primer.Outputer - primer.Prompter - primer.Analyticer - primer.Configurer - primer.Subsheller -} - -func NewRunner(prime primeable) *runner { - return &runner{ - prime.Output(), - prime.Prompt(), - prime.Analytics(), - prime.Config(), - prime.Subshell(), - InstallerConfig{}, - } -} - -type InstallerConfig struct { - OrgName string `json:"org_name"` - ProjectID string `json:"project_id"` - ProjectName string `json:"project_name"` - CommitID string `json:"commit_id"` -} - -type Params struct { - path string -} - -func newParams() *Params { - return &Params{} -} - -func (r *runner) Run(params *Params) (rerr error) { - var installerDimensions *dimensions.Values - defer func() { - if rerr == nil { - return - } - if locale.IsInputError(rerr) { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerAbort, errs.JoinMessage(rerr), installerDimensions) - } else { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerFailure, errs.JoinMessage(rerr), installerDimensions) - } - }() - - tempDir, err := ioutil.TempDir("", "artifacts-") - if err != nil { - return errs.Wrap(err, "Unable to create temporary directory") - } - defer os.RemoveAll(tempDir) - - /* Extract Assets */ - backpackZipFile := os.Args[0] - assetsPath := filepath.Join(tempDir, assetsPathName) - if err := r.extractAssets(assetsPath, backpackZipFile); err != nil { - return errs.Wrap(err, "Could not extract assets") - } - - err = r.prepareInstallerConfig(assetsPath) - if err != nil { - return errs.Wrap(err, "Could not read installer config, this installer appears to be corrupted.") - } - - namespace := project.NewNamespace(r.icfg.OrgName, r.icfg.ProjectName, "") - installerDimensions = &dimensions.Values{ - ProjectNameSpace: ptr.To(namespace.String()), - CommitID: &r.icfg.CommitID, - Trigger: ptr.To(target.TriggerOfflineInstaller.String()), - } - r.analytics.Event(ac.CatOfflineInstaller, "start", installerDimensions) - - // Detect target path - targetPath, err := r.getTargetPath(params.path) - if err != nil { - return errs.Wrap(err, "Could not determine target path") - } - - /* Validate Target Path */ - if err := r.validateTargetPath(targetPath); err != nil { - return errs.Wrap(err, "Could not validate target path") - } - - /* Prompt for License */ - accepted, err := r.promptLicense(assetsPath) - if err != nil { - return errs.Wrap(err, "Could not prompt for license") - } - if !accepted { - return locale.NewInputError("License not accepted") - } - - /* Extract Artifacts */ - artifactsPath := filepath.Join(tempDir, artifactsPathName) - if err := r.extractArtifacts(artifactsPath, assetsPath); err != nil { - return errs.Wrap(err, "Could not extract artifacts") - } - - /* Install Artifacts */ - asrt, err := r.setupRuntime(artifactsPath, targetPath) - if err != nil { - return errs.Wrap(err, "Could not setup runtime") - } - - /* Manually Install License File */ - { - err = fileutils.CopyFile(filepath.Join(assetsPath, licenseFileName), filepath.Join(targetPath, licenseFileName)) - if err != nil { - return errs.Wrap(err, "Error copying license file") - } - } - - /* Manually Install config File */ - { - err = fileutils.CopyFile( - filepath.Join(assetsPath, installerConfigFileName), - filepath.Join(targetPath, installerConfigFileName), - ) - if err != nil { - return errs.Wrap(err, "Error copying config file") - } - } - - var uninstallerSrc string - var uninstallerDest string - - /* Manually Install uninstaller */ - if rt.GOOS == "windows" { - /* shenanigans because windows won't let you delete an executable that's running */ - installDir, err := filepath.Abs(targetPath) - if err != nil { - return errs.Wrap(err, "Error determining absolute install directory") - } - uninstallDir := filepath.Join(installDir, "uninstall-data") - if fileutils.DirExists(uninstallDir) { - if err := os.RemoveAll(uninstallDir); err != nil { - return errs.Wrap(err, "Error removing uninstall directory") - } - } - if err := os.Mkdir(uninstallDir, os.ModeDir); err != nil { - return errs.Wrap(err, "Error creating uninstall directory") - } - - uninstallerSrc = filepath.Join(assetsPath, uninstallerFileNameRoot) - uninstallerDest = filepath.Join(uninstallDir, uninstallerFileNameRoot) - - // create batch script which copies the uninstaller to a temp dir and runs it from there this is necessary - // because windows won't let you delete an executable that's running - // The last message about ignoring the error is because the uninstaller will delete the directory the batch file - // is in, which unlike with the exe is fine because batch files are "special", but it does result in a benign - // "File not Found" error - batch := fmt.Sprintf( - ` - @echo off - copy %[1]s\%[2]s %%TEMP%%\%[2]s >nul 2>&1 - %%TEMP%%\%[2]s %[3]s - del %%TEMP%%\%[2]s >nul 2>&1 - echo You can safely ignore any File not Found errors following this message. - `, - uninstallDir, - uninstallerFileNameRoot, - installDir, - ) - err = os.WriteFile(filepath.Join(installDir, "uninstall.bat"), []byte(batch), 0755) - if err != nil { - return errs.Wrap(err, "Error creating uninstall script") - } - } else { - uninstallerSrc = filepath.Join(assetsPath, uninstallerFileNameRoot) - uninstallerDest = filepath.Join(targetPath, uninstallerFileNameRoot) - } - { - if fileutils.TargetExists(uninstallerDest) { - err := os.Remove(uninstallerDest) - if err != nil { - return errs.Wrap(err, "Error removing existing uninstaller") - } - } - err = fileutils.CopyFile( - uninstallerSrc, - uninstallerDest, - ) - if err != nil { - return errs.Wrap(err, "Error copying uninstaller") - } - err = os.Chmod(uninstallerDest, 0555) - if err != nil { - return errs.Wrap(err, "Error making uninstaller executable") - } - } - - /* Configure Environment */ - if err := r.configureEnvironment(targetPath, namespace, asrt); err != nil { - return errs.Wrap(err, "Could not configure environment") - } - - r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerSuccess, installerDimensions) - - r.out.Print(fmt.Sprintf(`Installation complete. -Your language runtime has been installed in [ACTIONABLE]%s[/RESET].`, targetPath)) - - return nil -} - -func (r *runner) prepareInstallerConfig(assetsPath string) error { - icfg := InstallerConfig{} - installerConfigPath := filepath.Join(assetsPath, installerConfigFileName) - configData, err := os.ReadFile(installerConfigPath) - if err != nil { - return errs.Wrap(err, "Failed to read config_file") - } - if err := json.Unmarshal(configData, &icfg); err != nil { - return errs.Wrap(err, "Failed to decode config_file") - } - - if icfg.ProjectName == "" { - return errs.New("ProjectName is empty") - } - - if icfg.OrgName == "" { - return errs.New("OrgName is empty") - } - - if icfg.CommitID == "" { - return errs.New("CommitID is empty") - } - - r.icfg = icfg - - return nil -} - -func (r *runner) setupRuntime(artifactsPath string, targetPath string) (*runtime.Runtime, error) { - logfile, err := buildlogfile.New(outputhelper.NewCatcher()) - if err != nil { - return nil, errs.Wrap(err, "Unable to create new logfile object") - } - - ns := project.NewNamespace(r.icfg.OrgName, r.icfg.ProjectName, r.icfg.CommitID) - offlineTarget := target.NewOfflineTarget(ns, targetPath, artifactsPath) - offlineTarget.SetTrigger(target.TriggerOfflineInstaller) - - offlineProgress := newOfflineProgressOutput(r.out) - eventHandler := events.NewRuntimeEventHandler(offlineProgress, nil, logfile) - - rti, err := runtime.New(offlineTarget, r.analytics, nil, nil) - if err != nil { - if !runtime.IsNeedsUpdateError(err) { - return nil, errs.Wrap(err, "Could not create runtime") - } - if err = rti.Update(eventHandler); err != nil { - return nil, errs.Wrap(err, "Had an installation error") - } - } - return rti, nil -} - -func (r *runner) extractArtifacts(artifactsPath, assetsPath string) error { - if err := os.Mkdir(artifactsPath, os.ModePerm); err != nil { - return errs.Wrap(err, "Unable to create artifactsPath directory") - } - - archivePath := filepath.Join(assetsPath, artifactsTarGZName) - ua := unarchiver.NewTarGz() - f, siz, err := ua.PrepareUnpacking(archivePath, artifactsPath) - if err != nil { - return errs.Wrap(err, "Unable to prepare unpacking of artifact tarball") - } - - pb := mpb.New( - mpb.WithWidth(40), - ) - barName := "Extracting" - bar := pb.AddBar( - siz, - mpb.PrependDecorators(decor.Name(barName, decor.WC{W: len(barName) + 1, C: decor.DidentRight})), - ) - - ua.SetNotifier(func(filename string, _ int64, isDir bool) { - if !isDir { - bar.Increment() - } - }) - - err = ua.Unarchive(f, siz, artifactsPath) - if err != nil { - return errs.Wrap(err, "Unable to unarchive artifacts to artifactsPath") - } - - bar.SetTotal(0, true) - bar.Abort(true) - pb.Wait() - - return nil -} - -func (r *runner) extractAssets(assetsPath string, backpackZipFile string) error { - if err := os.Mkdir(assetsPath, os.ModePerm); err != nil { - return errs.Wrap(err, "Unable to create assetsPath") - } - - ua := unarchiver.NewZip() - f, siz, err := ua.PrepareUnpacking(backpackZipFile, assetsPath) - if err != nil { - return errs.Wrap(err, "Unable to prepare unpacking of backpack") - } - - err = ua.Unarchive(f, siz, assetsPath) - if err != nil { - return errs.Wrap(err, "Unable to unarchive Assets to assetsPath") - } - - return nil -} - -func (r *runner) configureEnvironment(path string, namespace *project.Namespaced, asrt *runtime.Runtime) error { - env, err := asrt.Env(false, false) - if err != nil { - return errs.Wrap(err, "Error setting environment") - } - - if rt.GOOS == "windows" { - contents, err := assets.ReadFileBytes("scripts/setenv.bat") - if err != nil { - return errs.Wrap(err, "Error reading file bytes") - } - err = fileutils.WriteFile(filepath.Join(path, "setenv.bat"), contents) - if err != nil { - return locale.WrapError(err, - "err_deploy_write_setenv", - "Could not create setenv batch scriptfile at path: {{.V0}}", - path) - } - } - - // Configure available shells - isAdmin, err := osutils.IsAdmin() - if err != nil { - return errs.Wrap(err, "Could not determine if running as Windows administrator") - } - - id := sscommon.ProjectRCIdentifier(sscommon.OfflineInstallID, namespace) - err = subshell.ConfigureAvailableShells(r.shell, r.cfg, env, id, !isAdmin) - if err != nil { - return locale.WrapError(err, - "err_deploy_subshell_write", - "Could not write environment information to your shell configuration.") - } - - binPath := filepath.Join(path, "bin") - if err := fileutils.MkdirUnlessExists(binPath); err != nil { - return locale.WrapError(err, "err_deploy_binpath", "Could not create bin directory.") - } - - // Write global env file - err = r.shell.SetupShellRcFile(binPath, env, nil) - if err != nil { - return locale.WrapError(err, "err_deploy_subshell_rc_file", "Could not create environment script.") - } - - return nil -} - -func (r *runner) getTargetPath(inputPath string) (string, error) { - var targetPath string - if inputPath != "" { - targetPath = inputPath - } else { - parentDir, err := offinstall.DefaultInstallParentDir() - if err != nil { - return "", errs.Wrap(err, "Could not determine default install path") - } - targetPath = filepath.Join(parentDir, r.icfg.ProjectName) - - targetPath, err = r.prompt.Input("", "Enter an installation directory", &targetPath) - if err != nil { - return "", errs.Wrap(err, "Could not retrieve installation directory") - } - } - return targetPath, nil -} - -func (r *runner) validateTargetPath(path string) error { - if !fileutils.IsWritable(path) { - return errs.New( - "Cannot write to [ACTIONABLE]%s[/RESET]. Please ensure that the directory is writeable without "+ - "needing admin privileges or run this installer with Admin.", path) - } - - if fileutils.TargetExists(path) { - if !fileutils.IsDir(path) { - return errs.New("Target path [ACTIONABLE]%s[/RESET] is not a directory", path) - } - - empty, err := fileutils.IsEmptyDir(path) - if err != nil { - return errs.Wrap(err, "Test for directory empty failed") - } - if !empty { - installNonEmpty, err := r.prompt.Confirm( - "Setup", - "Installation directory is not empty, install anyway?", - ptr.To(true)) - if err != nil { - return errs.Wrap(err, "Unable to get confirmation to install into non-empty directory") - } - - if !installNonEmpty { - return locale.NewInputError( - "offline_installer_err_installdir_notempty", - "Installation directory ({{.V0}}) not empty, installation aborted", - path) - } - } - } - - return nil -} - -func (r *runner) promptLicense(assetsPath string) (bool, error) { - licenseFileAssetPath := filepath.Join(assetsPath, licenseFileName) - licenseContents, err := fileutils.ReadFile(licenseFileAssetPath) - if err != nil { - return false, errs.Wrap(err, "Unable to open License file") - } - r.out.Print(licenseContents) - - choice, err := r.prompt.Confirm("", "Do you accept the ActiveState Runtime Installer License Agreement?", ptr.To(false)) - if err != nil { - return false, err - } - - if err != nil { - return false, errs.Wrap(err, "Unable to confirm license") - } - - return choice, nil -} diff --git a/cmd/state-offline-installer/main.go b/cmd/state-offline-installer/main.go deleted file mode 100644 index 82b8cd86e7..0000000000 --- a/cmd/state-offline-installer/main.go +++ /dev/null @@ -1,137 +0,0 @@ -package main - -import ( - "fmt" - "os" - "runtime/debug" - "time" - - "github.com/ActiveState/cli/internal/analytics" - "github.com/ActiveState/cli/internal/analytics/client/sync" - anaConst "github.com/ActiveState/cli/internal/analytics/constants" - "github.com/ActiveState/cli/internal/captain" - "github.com/ActiveState/cli/internal/config" - "github.com/ActiveState/cli/internal/constants" - "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/events" - "github.com/ActiveState/cli/internal/locale" - "github.com/ActiveState/cli/internal/logging" - "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/internal/primer" - "github.com/ActiveState/cli/internal/prompt" - "github.com/ActiveState/cli/internal/rollbar" - "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/internal/runbits/panics" - "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/pkg/cmdlets/errors" -) - -func main() { - var exitCode int - - var an analytics.Dispatcher - var cfg *config.Instance - rollbar.SetupRollbar(constants.OfflineInstallerRollbarToken) - - // Allow starting the installer via a double click - captain.DisableMousetrap() - - // Handle things like panics, exit codes and the closing of globals - defer func() { - if panics.HandlePanics(recover(), debug.Stack()) { - exitCode = 1 - } - - if err := cfg.Close(); err != nil { - logging.Error("Failed to close config: %w", err) - } - - if err := events.WaitForEvents(5*time.Second, rollbar.Wait, an.Wait, logging.Close); err != nil { - logging.Warning("state-remote-installer failed to wait for events: %v", err) - } - os.Exit(exitCode) - }() - - if os.Getenv("VERBOSE") == "true" { - logging.CurrentHandler().SetVerbose(true) - } - - // Set up configuration handler - cfg, err := config.New() - if err != nil { - logging.Critical("Could not set up configuration handler: " + errs.JoinMessage(err)) - fmt.Fprintln(os.Stderr, errs.JoinMessage(err)) - exitCode = 1 - return - } - - rollbar.SetConfig(cfg) - - out, err := output.New("", &output.Config{ - OutWriter: os.Stdout, - ErrWriter: os.Stderr, - }) - if err != nil { - logging.Critical("Could not set up outputter: " + errs.JoinMessage(err)) - fmt.Fprintln(os.Stderr, errs.JoinMessage(err)) - exitCode = 1 - return - } - - an = sync.New(anaConst.SrcOfflineInstaller, cfg, nil, out) - - prime := primer.New( - nil, out, nil, - prompt.New(true, an), - subshell.New(cfg), nil, cfg, - nil, nil, an) - - if err := run(prime); err != nil { - if locale.IsInputError(err) { - logging.Debug("state-offline-installer errored out due to input: %s", errs.JoinMessage(err)) - } else { - multilog.Critical("state-offline-installer errored out: %s", errs.JoinMessage(err)) - } - - exitCode, _ = errors.ParseUserFacing(err) - if err != nil { - fmt.Fprintln(os.Stderr, errs.JoinMessage(err)) - } - } - out.Print("Press enter to exit.") - fmt.Scanln(ptr.To("")) // Wait for input from user -} - -func run(prime *primer.Values) error { - params := newParams() - - cmd := captain.NewCommand( - "install", - "Doing offline installation", - "Do an offline installation", - prime, nil, - []*captain.Argument{ - { - Name: "path", - Description: "Install into target directory ", - Value: ¶ms.path, - Required: false, - }, - }, - func(ccmd *captain.Command, args []string) error { - logging.Debug("Running CmdInstall") - runner := NewRunner(prime) - return runner.Run(params) - }, - ) - - err := cmd.Execute(os.Args[1:]) - if err != nil { - errors.PanicOnMissingLocale = false - errors.ReportError(err, cmd, prime.Analytics()) - return err - } - - return nil -} diff --git a/cmd/state-offline-installer/progress.go b/cmd/state-offline-installer/progress.go deleted file mode 100644 index 81515452dd..0000000000 --- a/cmd/state-offline-installer/progress.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/platform/model" - "github.com/ActiveState/cli/pkg/platform/runtime/artifact" - "github.com/vbauerster/mpb/v7" - "github.com/vbauerster/mpb/v7/decor" -) - -// New returns an error with the supplied message. -// New also records the stack trace at the point it was called. -// func New(out output.Outputer) *offlineProgressOutput { -// -// return &offlineProgressOutput{ -// out: out, -// } -// } - -type offlineProgressOutput struct { - out output.Outputer - pb *mpb.Progress - bar *mpb.Bar -} - -func newOfflineProgressOutput(out output.Outputer) *offlineProgressOutput { - return &offlineProgressOutput{out: out} -} - -func (mpo *offlineProgressOutput) BuildStarted(total int64) error { - return nil -} -func (mpo *offlineProgressOutput) BuildCompleted(bool) error { - return nil -} - -func (mpo *offlineProgressOutput) BuildArtifactStarted(artifactID artifact.ArtifactID, artifactName string) error { - return nil -} -func (mpo *offlineProgressOutput) BuildArtifactCompleted(artifactID artifact.ArtifactID, artifactName, logURI string, cachedBuild bool) error { - return nil -} -func (mpo *offlineProgressOutput) BuildArtifactFailure(artifactID artifact.ArtifactID, artifactName, logURI string, errorMessage string, cachedBuild bool) error { - return nil -} -func (mpo *offlineProgressOutput) BuildArtifactProgress(artifactID artifact.ArtifactID, artifactName, timeStamp, message, facility, pipeName, source string) error { - return nil -} - -func (mpo *offlineProgressOutput) InstallationCompleted(withFailures bool) error { - mpo.bar.SetTotal(0, true) - mpo.bar.Abort(true) - mpo.pb.Wait() - return nil -} -func (mpo *offlineProgressOutput) InstallationStarted(total int64) error { - mpo.pb = mpb.New(mpb.WithWidth(40)) - barName := "Installing" - mpo.bar = mpo.pb.AddBar(total, mpb.PrependDecorators(decor.Name(barName, decor.WC{W: len(barName) + 1, C: decor.DidentRight}))) - return nil -} -func (mpo *offlineProgressOutput) InstallationStatusUpdate(current, total int64) error { - mpo.bar.SetTotal(total, false) - mpo.bar.SetCurrent(current) - return nil -} -func (mpo *offlineProgressOutput) ArtifactStepStarted(artifactID artifact.ArtifactID, artifactName string, title string, total int64, counterCountsBytes bool) error { - return nil -} -func (mpo *offlineProgressOutput) ArtifactStepIncrement(artifactID artifact.ArtifactID, artifactName string, title string, total int64) error { - return nil -} -func (mpo *offlineProgressOutput) ArtifactStepCompleted(artifactID artifact.ArtifactID, artifactName string, title string) error { - return nil -} -func (mpo *offlineProgressOutput) ArtifactStepFailure(artifact.ArtifactID, string, string, string) error { - return nil -} -func (mpo *offlineProgressOutput) StillBuilding(numCompleted, numTotal int) error { - return nil -} -func (mpo *offlineProgressOutput) SolverStart() error { - return nil -} - -func (mpo *offlineProgressOutput) SolverSuccess() error { - return nil -} -func (mpo *offlineProgressOutput) SolverError(serr *model.SolverError) error { - return nil -} -func (mpo *offlineProgressOutput) Close() error { return nil } diff --git a/cmd/state-offline-uninstaller/main.go b/cmd/state-offline-uninstaller/main.go deleted file mode 100644 index 0ba17527c5..0000000000 --- a/cmd/state-offline-uninstaller/main.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "fmt" - "os" - "runtime/debug" - "time" - - "github.com/ActiveState/cli/internal/analytics" - "github.com/ActiveState/cli/internal/analytics/client/sync" - anaConst "github.com/ActiveState/cli/internal/analytics/constants" - "github.com/ActiveState/cli/internal/captain" - "github.com/ActiveState/cli/internal/config" - "github.com/ActiveState/cli/internal/constants" - "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/events" - "github.com/ActiveState/cli/internal/locale" - "github.com/ActiveState/cli/internal/logging" - "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/internal/primer" - "github.com/ActiveState/cli/internal/prompt" - "github.com/ActiveState/cli/internal/rollbar" - "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/internal/runbits/panics" - "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/pkg/cmdlets/errors" -) - -func main() { - var exitCode int - - var an analytics.Dispatcher - var cfg *config.Instance - rollbar.SetupRollbar(constants.OfflineInstallerRollbarToken) - - // Allow starting the installer via a double click - captain.DisableMousetrap() - - // Handle things like panics, exit codes and the closing of globals - defer func() { - if panics.HandlePanics(recover(), debug.Stack()) { - exitCode = 1 - } - - if err := cfg.Close(); err != nil { - logging.Error("Failed to close config: %w", err) - } - - if err := events.WaitForEvents(5*time.Second, rollbar.Wait, an.Wait, logging.Close); err != nil { - logging.Warning("state-remote-installer failed to wait for events: %v", err) - } - os.Exit(exitCode) - }() - - if os.Getenv("VERBOSE") == "true" { - logging.CurrentHandler().SetVerbose(true) - } - - // Set up configuration handler - cfg, err := config.New() - if err != nil { - logging.Critical("Could not set up configuration handler: " + errs.JoinMessage(err)) - fmt.Fprintln(os.Stderr, errs.JoinMessage(err)) - exitCode = 1 - return - } - - rollbar.SetConfig(cfg) - - out, err := output.New("", &output.Config{ - OutWriter: os.Stdout, - ErrWriter: os.Stderr, - }) - if err != nil { - logging.Critical("Could not set up outputter: " + errs.JoinMessage(err)) - fmt.Fprintln(os.Stderr, errs.JoinMessage(err)) - exitCode = 1 - return - } - - an = sync.New(anaConst.SrcOfflineInstaller, cfg, nil, out) - - prime := primer.New( - nil, out, nil, - prompt.New(true, an), - subshell.New(cfg), nil, cfg, - nil, nil, an) - - if err := run(prime); err != nil { - if locale.IsInputError(err) { - logging.Debug("state-offline-uninstaller errored out due to input: %s", errs.JoinMessage(err)) - } else { - multilog.Critical("state-offline-uninstaller errored out: %s", errs.JoinMessage(err)) - } - - exitCode, _ = errors.ParseUserFacing(err) - if err != nil { - fmt.Fprintln(os.Stderr, errs.JoinMessage(err)) - } - } - out.Print("Press enter to exit.") - fmt.Scanln(ptr.To("")) // Wait for input from user -} - -func run(prime *primer.Values) error { - params := newParams() - cmd := captain.NewCommand( - "uninstall", - "Doing offline un-installation", - "Do an offline un-installation", - prime, nil, - []*captain.Argument{ - { - Name: "path", - Description: "Directory to uninstall ", - Value: ¶ms.path, - Required: false, - }, - }, - func(ccmd *captain.Command, args []string) error { - logging.Debug("Running CmdUnInstall") - runner := NewRunner(prime) - return runner.Run(params) - }, - ) - - err := cmd.Execute(os.Args[1:]) - if err != nil { - errors.PanicOnMissingLocale = false - errors.ReportError(err, cmd, prime.Analytics()) - return err - } - - return nil -} diff --git a/cmd/state-offline-uninstaller/uninstall.go b/cmd/state-offline-uninstaller/uninstall.go deleted file mode 100644 index 08a86065fc..0000000000 --- a/cmd/state-offline-uninstaller/uninstall.go +++ /dev/null @@ -1,233 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/ActiveState/cli/internal/analytics" - ac "github.com/ActiveState/cli/internal/analytics/constants" - "github.com/ActiveState/cli/internal/analytics/dimensions" - "github.com/ActiveState/cli/internal/config" - "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/fileutils" - "github.com/ActiveState/cli/internal/locale" - "github.com/ActiveState/cli/internal/primer" - "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/pkg/project" - - "github.com/ActiveState/cli/internal/osutils" - "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/internal/prompt" - "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/internal/subshell/sscommon" - "github.com/ActiveState/cli/pkg/platform/runtime/target" -) - -const licenseFileName = "LICENSE.txt" - -type runner struct { - out output.Outputer - prompt prompt.Prompter - analytics analytics.Dispatcher - cfg *config.Instance - shell subshell.SubShell - icfg InstallerConfig -} - -type primeable interface { - primer.Outputer - primer.Prompter - primer.Analyticer - primer.Configurer - primer.Subsheller -} - -func NewRunner(prime primeable) *runner { - return &runner{ - prime.Output(), - prime.Prompt(), - prime.Analytics(), - prime.Config(), - prime.Subshell(), - InstallerConfig{}, - } -} - -const installerConfigFileName = "installer_config.json" - -type InstallerConfig struct { - OrgName string `json:"org_name"` - ProjectID string `json:"project_id"` - ProjectName string `json:"project_name"` - CommitID string `json:"commit_id"` -} - -type Params struct { - path string -} - -func newParams() *Params { - return &Params{} -} - -func (r *runner) Run(params *Params) (rerr error) { - var installerDimensions *dimensions.Values - defer func() { - if rerr == nil { - return - } - if locale.IsInputError(rerr) { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerAbort, errs.JoinMessage(rerr), installerDimensions) - } else { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerFailure, errs.JoinMessage(rerr), installerDimensions) - } - }() - - // Detect target path - targetPath, err := r.getTargetPath(params.path) - if err != nil { - return errs.Wrap(err, "Could not determine target path") - } - - /* Validate Target Path */ - if err := r.validateTargetPath(targetPath); err != nil { - return errs.Wrap(err, "Could not validate target path") - } - - if err := r.prepareInstallerConfig(targetPath); err != nil { - return errs.Wrap(err, "Could not read installer config, this installer appears to be corrupted.") - } - - cont, err := r.prompt.Confirm("", - fmt.Sprintf("You are about to uninstall the runtime installed at [[ACTIONABLE]%s[/RESET], continue?", targetPath), - ptr.To(false)) - if err != nil { - return errs.Wrap(err, "Could not confirm uninstall") - } - if !cont { - return locale.NewInputError("err_uninstall_abort", "Uninstall aborted") - } - - namespace := project.NewNamespace(r.icfg.OrgName, r.icfg.ProjectName, "") - installerDimensions = &dimensions.Values{ - ProjectNameSpace: ptr.To(namespace.String()), - CommitID: &r.icfg.CommitID, - Trigger: ptr.To(target.TriggerOfflineUninstaller.String()), - } - r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerStart, installerDimensions) - - r.out.Print("Removing environment configuration") - err = r.removeEnvPaths(namespace) - if err != nil { - return errs.Wrap(err, "Error removing environment path") - } - - r.out.Print("Removing installation directory") - err = os.RemoveAll(targetPath) - if err != nil { - return errs.Wrap(err, "Error removing installation directory") - } - - r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerSuccess, installerDimensions) - r.analytics.Event(ac.CatRuntimeUsage, ac.ActRuntimeDelete, installerDimensions) - - r.out.Print("Uninstall Complete") - - return nil -} - -func (r *runner) prepareInstallerConfig(assetsPath string) error { - icfg := InstallerConfig{} - installerConfigPath := filepath.Join(assetsPath, installerConfigFileName) - - configData, err := os.ReadFile(installerConfigPath) - if err != nil { - return errs.Wrap(err, "Failed to read config_file") - } - if err := json.Unmarshal(configData, &icfg); err != nil { - return errs.Wrap(err, "Failed to decode config_file") - } - - if icfg.ProjectName == "" { - return errs.New("ProjectName is empty") - } - - if icfg.OrgName == "" { - return errs.New("OrgName is empty") - } - - if icfg.CommitID == "" { - return errs.New("CommitID is empty") - } - - r.icfg = icfg - - return nil -} - -func (r *runner) getTargetPath(inputPath string) (string, error) { - if inputPath != "" { - return inputPath, nil - } - - cwd, err := os.Getwd() - if err != nil { - return "", errs.Wrap(err, "Could not determine current working directory") - } - - var targetPath string - if fileutils.TargetExists(filepath.Join(cwd, installerConfigFileName)) { - targetPath = cwd - } - - if targetPath != "" { - targetPath, err = r.prompt.Input("", "Enter an installation directory to uninstall", &targetPath) - } else { - targetPath, err = r.prompt.Input("", "Enter an installation directory to uninstall", nil, prompt.InputRequired) - } - if err != nil { - return "", errs.Wrap(err, "Could not retrieve installation directory") - } - return targetPath, nil -} - -func (r *runner) validateTargetPath(path string) error { - if !fileutils.IsWritable(path) { - return errs.New( - "Cannot write to [ACTIONABLE]%s[/RESET]. Please ensure that the directory is writeable without "+ - "needing admin privileges or run this installer with Admin.", path) - } - - if !fileutils.IsDir(path) { - return errs.New("Target path [ACTIONABLE]%s[/RESET] is not a directory", path) - } - - installerConfigPath := filepath.Join(path, installerConfigFileName) - if !fileutils.FileExists(installerConfigPath) { - return errs.New( - "The target directory does not appear to contain an ActiveState Runtime installation. Expected to find: %s.", - installerConfigPath) - } - - return nil -} - -func (r *runner) removeEnvPaths(namespace *project.Namespaced) error { - isAdmin, err := osutils.IsAdmin() - if err != nil { - return errs.Wrap(err, "Could not determine if running as Windows administrator") - } - - // remove shell file additions - id := sscommon.ProjectRCIdentifier(sscommon.OfflineInstallID, namespace) - if err := r.shell.CleanUserEnv(r.cfg, id, !isAdmin); err != nil { - return errs.Wrap(err, "Failed to remove runtime PATH") - } - if err := r.shell.CleanUserEnv(r.cfg, sscommon.AutostartID, !isAdmin); err != nil { - return errs.Wrap(err, "Failed to remove runtime PATH") - } - - return nil -} diff --git a/cmd/state-remote-installer/main.go b/cmd/state-remote-installer/main.go index de30e8c678..3b0713db2e 100644 --- a/cmd/state-remote-installer/main.go +++ b/cmd/state-remote-installer/main.go @@ -25,9 +25,9 @@ import ( "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/rollbar" "github.com/ActiveState/cli/internal/rtutils/ptr" + "github.com/ActiveState/cli/internal/runbits/errors" "github.com/ActiveState/cli/internal/runbits/panics" "github.com/ActiveState/cli/internal/updater" - "github.com/ActiveState/cli/pkg/cmdlets/errors" ) type Params struct { diff --git a/cmd/state-svc/test/integration/svc_int_test.go b/cmd/state-svc/test/integration/svc_int_test.go index 7895b0a05c..a5b51eadb5 100644 --- a/cmd/state-svc/test/integration/svc_int_test.go +++ b/cmd/state-svc/test/integration/svc_int_test.go @@ -43,6 +43,7 @@ func (suite *SvcIntegrationTestSuite) TestStartStop() { cp = ts.SpawnCmdWithOpts(ts.SvcExe, e2e.OptArgs("status")) cp.Expect("Service cannot be reached") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.SpawnCmdWithOpts(ts.SvcExe, e2e.OptArgs("start")) cp.Expect("Starting") @@ -91,6 +92,7 @@ func (suite *SvcIntegrationTestSuite) TestSignals() { suite.OnlyRunForTags(tagsuite.Service) ts := e2e.New(suite.T(), false) + ts.IgnoreLogErrors() defer ts.Close() // SIGINT (^C) @@ -145,6 +147,7 @@ func (suite *SvcIntegrationTestSuite) TestStartDuplicateErrorOutput() { cp = ts.SpawnCmdWithOpts(ts.SvcExe, e2e.OptArgs("foreground")) cp.Expect("An existing server instance appears to be in use") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.SpawnCmdWithOpts(ts.SvcExe, e2e.OptArgs("stop")) cp.ExpectExitCode(0) diff --git a/cmd/state/internal/cmdtree/activate.go b/cmd/state/internal/cmdtree/activate.go index d675e300b4..b87c43f6f3 100644 --- a/cmd/state/internal/cmdtree/activate.go +++ b/cmd/state/internal/cmdtree/activate.go @@ -87,6 +87,5 @@ func newActivateCommand(prime *primer.Values) *captain.Command { }, ) cmd.SetGroup(EnvironmentUsageGroup) - cmd.SetDoesNotSupportStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/auth.go b/cmd/state/internal/cmdtree/auth.go index 8bb549c1c4..8ae7a81ca8 100644 --- a/cmd/state/internal/cmdtree/auth.go +++ b/cmd/state/internal/cmdtree/auth.go @@ -54,7 +54,7 @@ func newAuthCommand(prime *primer.Values, globals *globalOptions) *captain.Comma params.NonInteractive = globals.NonInteractive return authRunner.Run(¶ms) }, - ).SetGroup(PlatformGroup) + ).SetGroup(PlatformGroup).SetSupportsStructuredOutput() } func newSignupCommand(prime *primer.Values) *captain.Command { @@ -70,7 +70,7 @@ func newSignupCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { return signupRunner.Run(¶ms) }, - ).SetDoesNotSupportStructuredOutput() + ) } func newLogoutCommand(prime *primer.Values) *captain.Command { @@ -85,5 +85,5 @@ func newLogoutCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { return logoutRunner.Run() }, - ).SetDoesNotSupportStructuredOutput() + ) } diff --git a/cmd/state/internal/cmdtree/branch.go b/cmd/state/internal/cmdtree/branch.go index e361c1b1ea..247494ccd9 100644 --- a/cmd/state/internal/cmdtree/branch.go +++ b/cmd/state/internal/cmdtree/branch.go @@ -20,7 +20,7 @@ func newBranchCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(_ *captain.Command, _ []string) error { return runner.Run() - }).SetGroup(PlatformGroup).SetUnstable(true) + }).SetGroup(PlatformGroup).SetSupportsStructuredOutput().SetUnstable(true) } func newBranchAddCommand(prime *primer.Values) *captain.Command { @@ -44,7 +44,7 @@ func newBranchAddCommand(prime *primer.Values) *captain.Command { }, func(_ *captain.Command, _ []string) error { return runner.Run(params) - }) + }).SetSupportsStructuredOutput() } func newBranchSwitchCommand(prime *primer.Values) *captain.Command { @@ -69,6 +69,7 @@ func newBranchSwitchCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params) }) + cmd.SetSupportsStructuredOutput() // We set this command to hidden for backwards compatibility as we cannot // alias `state switch` to `state branch switch` cmd.SetHidden(true) diff --git a/cmd/state/internal/cmdtree/bundles.go b/cmd/state/internal/cmdtree/bundles.go index d0932e7b37..c12e8f4e79 100644 --- a/cmd/state/internal/cmdtree/bundles.go +++ b/cmd/state/internal/cmdtree/bundles.go @@ -39,7 +39,7 @@ func newBundlesCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespaceBundle) }, - ).SetGroup(PackagesGroup).SetUnstable(true) + ).SetGroup(PackagesGroup).SetSupportsStructuredOutput().SetUnstable(true) } func newBundleInstallCommand(prime *primer.Values) *captain.Command { @@ -64,7 +64,7 @@ func newBundleInstallCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespaceBundle) }, - ) + ).SetSupportsStructuredOutput() } func newBundleUninstallCommand(prime *primer.Values) *captain.Command { @@ -89,7 +89,7 @@ func newBundleUninstallCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespaceBundle) }, - ) + ).SetSupportsStructuredOutput() } func newBundlesSearchCommand(prime *primer.Values) *captain.Command { @@ -125,5 +125,5 @@ func newBundlesSearchCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespaceBundle) }, - ) + ).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/checkout.go b/cmd/state/internal/cmdtree/checkout.go index f8f03613a5..bf7e7eb25b 100644 --- a/cmd/state/internal/cmdtree/checkout.go +++ b/cmd/state/internal/cmdtree/checkout.go @@ -53,5 +53,6 @@ func newCheckoutCommand(prime *primer.Values) *captain.Command { }, ) cmd.SetGroup(EnvironmentSetupGroup) + cmd.SetSupportsStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/clean.go b/cmd/state/internal/cmdtree/clean.go index 1a0e9bbe72..758f6bb4a1 100644 --- a/cmd/state/internal/cmdtree/clean.go +++ b/cmd/state/internal/cmdtree/clean.go @@ -19,7 +19,7 @@ func newCleanCommand(prime *primer.Values) *captain.Command { prime.Output().Print(ccmd.Help()) return nil }, - ).SetGroup(UtilsGroup) + ).SetGroup(UtilsGroup).SetSupportsStructuredOutput() } func newCleanUninstallCommand(prime *primer.Values, globals *globalOptions) *captain.Command { @@ -62,7 +62,7 @@ func newCleanUninstallCommand(prime *primer.Values, globals *globalOptions) *cap params.NonInteractive = globals.NonInteractive // distinct from --force return runner.Run(¶ms) }, - ).SetDoesNotSupportStructuredOutput() + ) } func newCleanCacheCommand(prime *primer.Values, globals *globalOptions) *captain.Command { @@ -86,7 +86,7 @@ func newCleanCacheCommand(prime *primer.Values, globals *globalOptions) *captain params.Force = globals.NonInteractive return runner.Run(¶ms) }, - ).SetDoesNotSupportStructuredOutput() + ) } func newCleanConfigCommand(prime *primer.Values) *captain.Command { @@ -109,5 +109,5 @@ func newCleanConfigCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, _ []string) error { return runner.Run(¶ms) }, - ).SetDoesNotSupportStructuredOutput() + ) } diff --git a/cmd/state/internal/cmdtree/cmdtree.go b/cmd/state/internal/cmdtree/cmdtree.go index 2abab6d5df..f4f9167db9 100644 --- a/cmd/state/internal/cmdtree/cmdtree.go +++ b/cmd/state/internal/cmdtree/cmdtree.go @@ -47,6 +47,7 @@ func New(prime *primer.Values, args ...string) *CmdTree { newExportGithubActionCommand(prime), newExportDocsCommand(prime), newExportEnvCommand(prime), + newLogCommand(prime), ) platformsCmd := newPlatformsCommand(prime) @@ -278,14 +279,6 @@ func newStateCommand(globals *globalOptions, prime *primer.Values) *captain.Comm Persist: true, Value: &globals.Output, }, - { - /* This option is only used for the vscode extension: It prevents the integrated terminal to close immediately after an error occurs, such that the user can read the message */ - Name: "confirm-exit-on-error", // Name and Shorthand should be kept in sync with cmd/state/output.go - Description: "prompts the user to press enter before exiting, when an error occurs", - Persist: true, - Hidden: true, // No need to add this to help messages - Value: &opts.ConfirmExit, - }, { Name: "non-interactive", // Name and Shorthand should be kept in sync with cmd/state/output.go Description: locale.T("flag_state_non_interactive_description"), @@ -321,6 +314,7 @@ func newStateCommand(globals *globalOptions, prime *primer.Values) *captain.Comm cmd.SetHasVariableArguments() cmd.OnExecStart(cmdCall.OnExecStart) cmd.OnExecStop(cmdCall.OnExecStop) + cmd.SetSupportsStructuredOutput() return cmd } @@ -360,7 +354,7 @@ func (a *addCmdAs) deprecatedAlias(aliased *captain.Command, name string) { func(c *captain.Command, args []string) error { msg := locale.Tl( "cmd_deprecated_notice", - "This command is deprecated. Please use `state {{.V0}}` instead.", + "This command is deprecated. Please use '[ACTIONABLE]state {{.V0}}[/RESET]' instead.", aliased.Name(), ) diff --git a/cmd/state/internal/cmdtree/commit.go b/cmd/state/internal/cmdtree/commit.go index a971ec4dcb..958fd0d6c2 100644 --- a/cmd/state/internal/cmdtree/commit.go +++ b/cmd/state/internal/cmdtree/commit.go @@ -22,7 +22,9 @@ func newCommitCommand(prime *primer.Values) *captain.Command { }, ) - cmd.SetGroup(EnvironmentSetupGroup).SetUnstable(true) + cmd.SetGroup(EnvironmentSetupGroup) + cmd.SetSupportsStructuredOutput() + cmd.SetUnstable(true) return cmd } diff --git a/cmd/state/internal/cmdtree/config.go b/cmd/state/internal/cmdtree/config.go index 8627adb07f..76d9daa495 100644 --- a/cmd/state/internal/cmdtree/config.go +++ b/cmd/state/internal/cmdtree/config.go @@ -21,7 +21,7 @@ func newConfigCommand(prime *primer.Values) *captain.Command { return err } return runner.Run(ccmd.Usage) - }).SetGroup(UtilsGroup) + }).SetGroup(UtilsGroup).SetSupportsStructuredOutput() } func newConfigGetCommand(prime *primer.Values) *captain.Command { @@ -43,7 +43,7 @@ func newConfigGetCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { runner := config.NewGet(prime) return runner.Run(params) - }) + }).SetSupportsStructuredOutput() } func newConfigSetCommand(prime *primer.Values) *captain.Command { @@ -71,5 +71,5 @@ func newConfigSetCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { runner := config.NewSet(prime) return runner.Run(params) - }) + }).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/cve.go b/cmd/state/internal/cmdtree/cve.go index c327f41667..0aa91b5a6e 100644 --- a/cmd/state/internal/cmdtree/cve.go +++ b/cmd/state/internal/cmdtree/cve.go @@ -24,6 +24,7 @@ func newCveCommand(prime *primer.Values) *captain.Command { ) cmd.SetGroup(PlatformGroup) cmd.SetAliases("cve") + cmd.SetSupportsStructuredOutput() cmd.SetUnstable(true) return cmd } @@ -50,7 +51,7 @@ func newReportCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return report.Run(¶ms) }, - ) + ).SetSupportsStructuredOutput() } func newOpenCommand(prime *primer.Values) *captain.Command { @@ -73,5 +74,5 @@ func newOpenCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return open.Run(params) }, - ).SetDoesNotSupportStructuredOutput() + ) } diff --git a/cmd/state/internal/cmdtree/deploy.go b/cmd/state/internal/cmdtree/deploy.go index 8a424b39ac..43efa22d67 100644 --- a/cmd/state/internal/cmdtree/deploy.go +++ b/cmd/state/internal/cmdtree/deploy.go @@ -54,7 +54,6 @@ func newDeployCommand(prime *primer.Values) *captain.Command { }) cmd.SetGroup(EnvironmentSetupGroup) cmd.SetHidden(true) - cmd.SetDoesNotSupportStructuredOutput() return cmd } @@ -85,7 +84,7 @@ func newDeployInstallCommand(prime *primer.Values) *captain.Command { }, func(cmd *captain.Command, args []string) error { return runner.Run(params) - }).SetDoesNotSupportStructuredOutput() + }) } func newDeployConfigureCommand(prime *primer.Values) *captain.Command { @@ -124,7 +123,7 @@ func newDeployConfigureCommand(prime *primer.Values) *captain.Command { }, func(cmd *captain.Command, args []string) error { return runner.Run(params) - }).SetDoesNotSupportStructuredOutput() + }) } func newDeploySymlinkCommand(prime *primer.Values) *captain.Command { @@ -159,7 +158,7 @@ func newDeploySymlinkCommand(prime *primer.Values) *captain.Command { }, func(cmd *captain.Command, args []string) error { return runner.Run(params) - }).SetDoesNotSupportStructuredOutput() + }) } func newDeployReportCommand(prime *primer.Values) *captain.Command { @@ -189,7 +188,7 @@ func newDeployReportCommand(prime *primer.Values) *captain.Command { }, func(cmd *captain.Command, args []string) error { return runner.Run(params) - }).SetDoesNotSupportStructuredOutput() + }) } func newDeployUninstallCommand(prime *primer.Values) *captain.Command { @@ -221,5 +220,5 @@ func newDeployUninstallCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(cmd *captain.Command, args []string) error { return runner.Run(params) - }).SetDoesNotSupportStructuredOutput() + }) } diff --git a/cmd/state/internal/cmdtree/events.go b/cmd/state/internal/cmdtree/events.go index 88634808a4..7ffae42333 100644 --- a/cmd/state/internal/cmdtree/events.go +++ b/cmd/state/internal/cmdtree/events.go @@ -19,7 +19,7 @@ func newEventsCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(cmd *captain.Command, args []string) error { return runner.Run() - }).SetGroup(AutomationGroup).SetUnstable(true) + }).SetGroup(AutomationGroup).SetSupportsStructuredOutput().SetUnstable(true) } func newEventsLogCommand(prime *primer.Values) *captain.Command { @@ -42,5 +42,5 @@ func newEventsLogCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(cmd *captain.Command, args []string) error { return runner.Run(¶ms) - }).SetDoesNotSupportStructuredOutput() + }) } diff --git a/cmd/state/internal/cmdtree/exec.go b/cmd/state/internal/cmdtree/exec.go index 5d237901c3..60a59b08c8 100644 --- a/cmd/state/internal/cmdtree/exec.go +++ b/cmd/state/internal/cmdtree/exec.go @@ -45,7 +45,6 @@ func newExecCommand(prime *primer.Values, args ...string) *captain.Command { cmd.SetGroup(EnvironmentUsageGroup) cmd.SetHasVariableArguments() - cmd.SetDoesNotSupportStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/export.go b/cmd/state/internal/cmdtree/export.go index bb66f88259..5bb12a23b2 100644 --- a/cmd/state/internal/cmdtree/export.go +++ b/cmd/state/internal/cmdtree/export.go @@ -24,7 +24,7 @@ func newExportCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(ccmd *captain.Command, args []string) error { return runner.Run(ccmd) - }).SetGroup(UtilsGroup) + }).SetGroup(UtilsGroup).SetSupportsStructuredOutput() } func newRecipeCommand(prime *primer.Values) *captain.Command { @@ -59,7 +59,7 @@ func newRecipeCommand(prime *primer.Values) *captain.Command { }, func(_ *captain.Command, _ []string) error { return recipe.Run(¶ms) - }).SetUnstable(true) + }).SetSupportsStructuredOutput().SetUnstable(true) } func newJWTCommand(prime *primer.Values) *captain.Command { @@ -76,7 +76,7 @@ func newJWTCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(ccmd *captain.Command, args []string) error { return jwt.Run(¶ms) - }) + }).SetSupportsStructuredOutput() } func newPrivateKeyCommand(prime *primer.Values) *captain.Command { @@ -93,7 +93,7 @@ func newPrivateKeyCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(ccmd *captain.Command, args []string) error { return privateKey.Run(¶ms) - }) + }).SetSupportsStructuredOutput() } func newAPIKeyCommand(prime *primer.Values) *captain.Command { @@ -117,7 +117,7 @@ func newAPIKeyCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { params.IsAuthed = prime.Auth().Authenticated return apikey.Run(params) - }) + }).SetSupportsStructuredOutput() } func newExportConfigCommand(prime *primer.Values) *captain.Command { @@ -142,7 +142,7 @@ func newExportConfigCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(ccmd *captain.Command, _ []string) error { return runner.Run(ccmd, ¶ms) - }).SetUnstable(true) + }).SetSupportsStructuredOutput().SetUnstable(true) } func newExportGithubActionCommand(prime *primer.Values) *captain.Command { @@ -158,7 +158,7 @@ func newExportGithubActionCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(ccmd *captain.Command, _ []string) error { return runner.Run(¶ms) - }).SetUnstable(true).SetDoesNotSupportStructuredOutput() + }).SetUnstable(true) } func newExportDocsCommand(prime *primer.Values) *captain.Command { @@ -177,7 +177,6 @@ func newExportDocsCommand(prime *primer.Values) *captain.Command { }) cmd.SetHidden(true) - cmd.SetDoesNotSupportStructuredOutput() return cmd } @@ -196,6 +195,42 @@ func newExportEnvCommand(prime *primer.Values) *captain.Command { return runner.Run() }) + cmd.SetSupportsStructuredOutput() + cmd.SetUnstable(true) + + return cmd +} + +func newLogCommand(prime *primer.Values) *captain.Command { + runner := export.NewLog(prime) + params := &export.LogParams{} + + cmd := captain.NewCommand( + "log", + locale.Tl("export_log_title", "Show Log File"), + locale.Tl("export_log_description", "Show the path to a State Tool log file"), + prime, + []*captain.Flag{ + { + Name: "index", + Shorthand: "i", + Description: locale.Tl("flag_export_log_index", "The 0-based index of the log file to show, starting with the newest"), + Value: ¶ms.Index, + }, + }, + []*captain.Argument{ + { + Name: "prefix", + Description: locale.Tl("arg_export_log_prefix", "The prefix of the log file to show (e.g. state or state-svc). The default is 'state'"), + Required: false, + Value: ¶ms.Prefix, + }, + }, + func(ccmd *captain.Command, _ []string) error { + return runner.Run(params) + }) + + cmd.SetSupportsStructuredOutput() cmd.SetUnstable(true) return cmd diff --git a/cmd/state/internal/cmdtree/fork.go b/cmd/state/internal/cmdtree/fork.go index 0bf35fea61..258d393a7f 100644 --- a/cmd/state/internal/cmdtree/fork.go +++ b/cmd/state/internal/cmdtree/fork.go @@ -43,5 +43,5 @@ func newForkCommand(prime *primer.Values) *captain.Command { }, func(cmd *captain.Command, args []string) error { return runner.Run(params) - }).SetGroup(VCSGroup) + }).SetGroup(VCSGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/hello_example.go b/cmd/state/internal/cmdtree/hello_example.go index 9edd798d72..d15d1a6f6e 100644 --- a/cmd/state/internal/cmdtree/hello_example.go +++ b/cmd/state/internal/cmdtree/hello_example.go @@ -56,6 +56,8 @@ func newHelloCommand(prime *primer.Values) *captain.Command { // The group is used to group together commands in the --help output cmd.SetGroup(UtilsGroup) + // Commands should support structured (JSON) output whenever possible. + cmd.SetSupportsStructuredOutput() // Any new command should be marked unstable for the first release it goes out in. cmd.SetUnstable(true) // Certain commands like `state deploy` are there for backwards compatibility, but we don't want to show them in the --help output as they are not part of the happy path or our long term goals. diff --git a/cmd/state/internal/cmdtree/history.go b/cmd/state/internal/cmdtree/history.go index 6f19a70f44..b954b99e7a 100644 --- a/cmd/state/internal/cmdtree/history.go +++ b/cmd/state/internal/cmdtree/history.go @@ -21,5 +21,5 @@ func newHistoryCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, _ []string) error { return initRunner.Run(¶ms) }, - ).SetGroup(VCSGroup) + ).SetGroup(VCSGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/init.go b/cmd/state/internal/cmdtree/init.go index 08d2f33a80..ac6e6df3d2 100644 --- a/cmd/state/internal/cmdtree/init.go +++ b/cmd/state/internal/cmdtree/init.go @@ -48,5 +48,5 @@ func newInitCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, _ []string) error { return initRunner.Run(¶ms) }, - ).SetGroup(EnvironmentSetupGroup) + ).SetGroup(EnvironmentSetupGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/invite.go b/cmd/state/internal/cmdtree/invite.go index 93fbfe8b34..85235c6501 100644 --- a/cmd/state/internal/cmdtree/invite.go +++ b/cmd/state/internal/cmdtree/invite.go @@ -44,7 +44,6 @@ func newInviteCommand(prime *primer.Values) *captain.Command { cmd.SetGroup(PlatformGroup) cmd.SetUnstable(true) cmd.SetHasVariableArguments() - cmd.SetDoesNotSupportStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/languages.go b/cmd/state/internal/cmdtree/languages.go index 1c746c4c9a..93480fa2e2 100644 --- a/cmd/state/internal/cmdtree/languages.go +++ b/cmd/state/internal/cmdtree/languages.go @@ -20,7 +20,7 @@ func newLanguagesCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, _ []string) error { return runner.Run() }, - ).SetGroup(PlatformGroup).SetUnstable(true) + ).SetGroup(PlatformGroup).SetSupportsStructuredOutput().SetUnstable(true) } func newLanguageInstallCommand(prime *primer.Values) *captain.Command { @@ -45,5 +45,5 @@ func newLanguageInstallCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, _ []string) error { return runner.Run(¶ms) }, - ) + ).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/learn.go b/cmd/state/internal/cmdtree/learn.go index 1e09441d50..541413f81c 100644 --- a/cmd/state/internal/cmdtree/learn.go +++ b/cmd/state/internal/cmdtree/learn.go @@ -19,5 +19,5 @@ func newLearnCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(cmd *captain.Command, args []string) error { return learnRunner.Run() - }).SetGroup(UtilsGroup).SetDoesNotSupportStructuredOutput() + }).SetGroup(UtilsGroup) } diff --git a/cmd/state/internal/cmdtree/organizations.go b/cmd/state/internal/cmdtree/organizations.go index 5c52df8b37..4ef76984a0 100644 --- a/cmd/state/internal/cmdtree/organizations.go +++ b/cmd/state/internal/cmdtree/organizations.go @@ -26,6 +26,7 @@ func newOrganizationsCommand(prime *primer.Values) *captain.Command { cmd.SetGroup(PlatformGroup) cmd.SetAliases("orgs") + cmd.SetSupportsStructuredOutput() cmd.SetUnstable(true) return cmd diff --git a/cmd/state/internal/cmdtree/packages.go b/cmd/state/internal/cmdtree/packages.go index e3449a3e1f..f3c172a019 100644 --- a/cmd/state/internal/cmdtree/packages.go +++ b/cmd/state/internal/cmdtree/packages.go @@ -43,6 +43,7 @@ func newPackagesCommand(prime *primer.Values) *captain.Command { cmd.SetGroup(PackagesGroup) cmd.SetAliases("pkg", "package") + cmd.SetSupportsStructuredOutput() return cmd } @@ -69,7 +70,7 @@ func newInstallCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespacePackage) }, - ).SetGroup(PackagesGroup) + ).SetGroup(PackagesGroup).SetSupportsStructuredOutput() } func newUninstallCommand(prime *primer.Values) *captain.Command { @@ -94,7 +95,7 @@ func newUninstallCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespacePackage) }, - ).SetGroup(PackagesGroup) + ).SetGroup(PackagesGroup).SetSupportsStructuredOutput() } func newImportCommand(prime *primer.Values, globals *globalOptions) *captain.Command { @@ -120,7 +121,7 @@ func newImportCommand(prime *primer.Values, globals *globalOptions) *captain.Com params.NonInteractive = globals.NonInteractive return runner.Run(params) }, - ).SetGroup(PackagesGroup) + ).SetGroup(PackagesGroup).SetSupportsStructuredOutput() } func newSearchCommand(prime *primer.Values) *captain.Command { @@ -156,7 +157,7 @@ func newSearchCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespacePackage) }, - ).SetGroup(PackagesGroup).SetUnstable(true) + ).SetGroup(PackagesGroup).SetSupportsStructuredOutput().SetUnstable(true) } func newInfoCommand(prime *primer.Values) *captain.Command { @@ -187,5 +188,5 @@ func newInfoCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params, model.NamespacePackage) }, - ).SetGroup(PackagesGroup) + ).SetGroup(PackagesGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/platforms.go b/cmd/state/internal/cmdtree/platforms.go index 640a1de6f9..0b149f4aad 100644 --- a/cmd/state/internal/cmdtree/platforms.go +++ b/cmd/state/internal/cmdtree/platforms.go @@ -20,7 +20,7 @@ func newPlatformsCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run() }, - ).SetGroup(PlatformGroup).SetUnstable(true) + ).SetGroup(PlatformGroup).SetSupportsStructuredOutput().SetUnstable(true) } func newPlatformsSearchCommand(prime *primer.Values) *captain.Command { @@ -36,7 +36,7 @@ func newPlatformsSearchCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run() }, - ) + ).SetSupportsStructuredOutput() } func newPlatformsAddCommand(prime *primer.Values) *captain.Command { @@ -67,7 +67,7 @@ func newPlatformsAddCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params) }, - ) + ).SetSupportsStructuredOutput() } func newPlatformsRemoveCommand(prime *primer.Values) *captain.Command { @@ -98,5 +98,5 @@ func newPlatformsRemoveCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params) }, - ) + ).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/prepare.go b/cmd/state/internal/cmdtree/prepare.go index 3269c4299c..43c2c4b5f0 100644 --- a/cmd/state/internal/cmdtree/prepare.go +++ b/cmd/state/internal/cmdtree/prepare.go @@ -23,7 +23,6 @@ func newPrepareCommand(prime *primer.Values) *captain.Command { ) cmd.SetHidden(true) - cmd.SetDoesNotSupportStructuredOutput() return cmd } @@ -44,7 +43,6 @@ func newPrepareCompletionsCommand(prime *primer.Values) *captain.Command { ) cmd.SetHidden(true) - cmd.SetDoesNotSupportStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/projects.go b/cmd/state/internal/cmdtree/projects.go index 8496e6f03c..1680559907 100644 --- a/cmd/state/internal/cmdtree/projects.go +++ b/cmd/state/internal/cmdtree/projects.go @@ -22,7 +22,7 @@ func newProjectsCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { return runner.Run(params) }, - ).SetGroup(ProjectUsageGroup) + ).SetGroup(ProjectUsageGroup).SetSupportsStructuredOutput() } func newRemoteProjectsCommand(prime *primer.Values) *captain.Command { @@ -39,7 +39,7 @@ func newRemoteProjectsCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { return runner.RunRemote(params) }, - ).SetGroup(ProjectUsageGroup) + ).SetGroup(ProjectUsageGroup).SetSupportsStructuredOutput() } func newProjectsEditCommand(prime *primer.Values) *captain.Command { @@ -84,7 +84,6 @@ func newProjectsEditCommand(prime *primer.Values) *captain.Command { ) cmd.SetGroup(ProjectUsageGroup) - cmd.SetDoesNotSupportStructuredOutput() cmd.SetUnstable(true) return cmd @@ -113,7 +112,6 @@ func newDeleteProjectsCommand(prime *primer.Values) *captain.Command { }, ) cmd.SetGroup(ProjectUsageGroup) - cmd.SetDoesNotSupportStructuredOutput() cmd.SetUnstable(true) return cmd @@ -148,7 +146,6 @@ func newMoveProjectsCommand(prime *primer.Values) *captain.Command { }, ) cmd.SetGroup(ProjectUsageGroup) - cmd.SetDoesNotSupportStructuredOutput() cmd.SetUnstable(true) return cmd diff --git a/cmd/state/internal/cmdtree/protocol.go b/cmd/state/internal/cmdtree/protocol.go index 46022bad4c..f968b1f28f 100644 --- a/cmd/state/internal/cmdtree/protocol.go +++ b/cmd/state/internal/cmdtree/protocol.go @@ -30,7 +30,6 @@ func newProtocolCommand(prime *primer.Values) *captain.Command { }, ) cmd.SetHidden(true) - cmd.SetDoesNotSupportStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/pull.go b/cmd/state/internal/cmdtree/pull.go index 3b687ea5c5..d673c00a2a 100644 --- a/cmd/state/internal/cmdtree/pull.go +++ b/cmd/state/internal/cmdtree/pull.go @@ -29,5 +29,5 @@ func newPullCommand(prime *primer.Values, globals *globalOptions) *captain.Comma func(cmd *captain.Command, args []string) error { params.Force = globals.NonInteractive return runner.Run(params) - }).SetGroup(VCSGroup) + }).SetGroup(VCSGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/push.go b/cmd/state/internal/cmdtree/push.go index 1a1db09032..78a30bd844 100644 --- a/cmd/state/internal/cmdtree/push.go +++ b/cmd/state/internal/cmdtree/push.go @@ -32,5 +32,5 @@ func newPushCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { return pushRunner.Run(params) }, - ).SetGroup(VCSGroup) + ).SetGroup(VCSGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/refresh.go b/cmd/state/internal/cmdtree/refresh.go index 6cbe4cc7d1..025cb66a55 100644 --- a/cmd/state/internal/cmdtree/refresh.go +++ b/cmd/state/internal/cmdtree/refresh.go @@ -33,6 +33,7 @@ func newRefreshCommand(prime *primer.Values) *captain.Command { }, ) cmd.SetGroup(EnvironmentUsageGroup) + cmd.SetSupportsStructuredOutput() cmd.SetUnstable(true) return cmd } diff --git a/cmd/state/internal/cmdtree/reset.go b/cmd/state/internal/cmdtree/reset.go index 688641c99a..44f550cc09 100644 --- a/cmd/state/internal/cmdtree/reset.go +++ b/cmd/state/internal/cmdtree/reset.go @@ -28,5 +28,5 @@ func newResetCommand(prime *primer.Values, globals *globalOptions) *captain.Comm params.Force = globals.NonInteractive return runner.Run(params) }, - ).SetGroup(VCSGroup) + ).SetGroup(VCSGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/revert.go b/cmd/state/internal/cmdtree/revert.go index e22e19446f..896b0f75e8 100644 --- a/cmd/state/internal/cmdtree/revert.go +++ b/cmd/state/internal/cmdtree/revert.go @@ -35,5 +35,5 @@ func newRevertCommand(prime *primer.Values, globals *globalOptions) *captain.Com params.Force = globals.NonInteractive return runner.Run(params) }, - ).SetGroup(VCSGroup) + ).SetGroup(VCSGroup).SetSupportsStructuredOutput() } diff --git a/cmd/state/internal/cmdtree/run.go b/cmd/state/internal/cmdtree/run.go index 62adee00f5..8557296058 100644 --- a/cmd/state/internal/cmdtree/run.go +++ b/cmd/state/internal/cmdtree/run.go @@ -49,7 +49,6 @@ func newRunCommand(prime *primer.Values) *captain.Command { cmd.SetGroup(ProjectUsageGroup) cmd.SetDisableFlagParsing(true) cmd.SetHasVariableArguments() - cmd.SetDoesNotSupportStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/scripts.go b/cmd/state/internal/cmdtree/scripts.go index 98b877b782..fc6e0360e2 100644 --- a/cmd/state/internal/cmdtree/scripts.go +++ b/cmd/state/internal/cmdtree/scripts.go @@ -19,7 +19,7 @@ func newScriptsCommand(prime *primer.Values) *captain.Command { []*captain.Argument{}, func(ccmd *captain.Command, args []string) error { return runner.Run() - }).SetGroup(AutomationGroup) + }).SetGroup(AutomationGroup).SetSupportsStructuredOutput() } func newScriptsEditCommand(prime *primer.Values) *captain.Command { @@ -50,6 +50,6 @@ func newScriptsEditCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { return editRunner.Run(¶ms) }, - ).SetUnstable(true).SetDoesNotSupportStructuredOutput() + ).SetUnstable(true) } diff --git a/cmd/state/internal/cmdtree/secrets.go b/cmd/state/internal/cmdtree/secrets.go index f9004ac861..4446a839eb 100644 --- a/cmd/state/internal/cmdtree/secrets.go +++ b/cmd/state/internal/cmdtree/secrets.go @@ -39,6 +39,7 @@ func newSecretsCommand(secretsClient *secretsapi.Client, prime *primer.Values) * ccmd.SetGroup(PlatformGroup) ccmd.SetAliases("variables", "vars") + ccmd.SetSupportsStructuredOutput() ccmd.SetUnstable(true) return ccmd @@ -66,7 +67,7 @@ func newSecretsGetCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params) }, - ) + ).SetSupportsStructuredOutput() } func newSecretsSetCommand(prime *primer.Values) *captain.Command { @@ -97,7 +98,7 @@ func newSecretsSetCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params) }, - ).SetDoesNotSupportStructuredOutput() + ) } func newSecretsSyncCommand(secretsClient *secretsapi.Client, prime *primer.Values) *captain.Command { @@ -113,5 +114,5 @@ func newSecretsSyncCommand(secretsClient *secretsapi.Client, prime *primer.Value func(_ *captain.Command, _ []string) error { return runner.Run() }, - ).SetDoesNotSupportStructuredOutput() + ) } diff --git a/cmd/state/internal/cmdtree/shell.go b/cmd/state/internal/cmdtree/shell.go index b67c611d08..eebe87ccbd 100644 --- a/cmd/state/internal/cmdtree/shell.go +++ b/cmd/state/internal/cmdtree/shell.go @@ -42,7 +42,6 @@ func newShellCommand(prime *primer.Values) *captain.Command { }, ) cmd.SetGroup(EnvironmentUsageGroup) - cmd.SetDoesNotSupportStructuredOutput() cmd.SetAliases("prompt") return cmd } diff --git a/cmd/state/internal/cmdtree/show.go b/cmd/state/internal/cmdtree/show.go index 2b79b10871..709649096b 100644 --- a/cmd/state/internal/cmdtree/show.go +++ b/cmd/state/internal/cmdtree/show.go @@ -28,5 +28,5 @@ func newShowCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return runner.Run(params) }, - ).SetGroup(ProjectUsageGroup).SetUnstable(true) + ).SetGroup(ProjectUsageGroup).SetSupportsStructuredOutput().SetUnstable(true) } diff --git a/cmd/state/internal/cmdtree/switch.go b/cmd/state/internal/cmdtree/switch.go index be7b478b22..0f36062e38 100644 --- a/cmd/state/internal/cmdtree/switch.go +++ b/cmd/state/internal/cmdtree/switch.go @@ -31,5 +31,6 @@ func newSwitchCommand(prime *primer.Values) *captain.Command { }) cmd.SetGroup(EnvironmentSetupGroup) + cmd.SetSupportsStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/update.go b/cmd/state/internal/cmdtree/update.go index d12d579bf2..1e82a8b7cd 100644 --- a/cmd/state/internal/cmdtree/update.go +++ b/cmd/state/internal/cmdtree/update.go @@ -31,7 +31,6 @@ func newUpdateCommand(prime *primer.Values) *captain.Command { ) cmd.SetGroup(UtilsGroup) cmd.SetSkipChecks(true) - cmd.SetDoesNotSupportStructuredOutput() return cmd } @@ -58,6 +57,7 @@ func newUpdateLockCommand(prime *primer.Values, globals *globalOptions) *captain }, ) cmd.SetSkipChecks(true) + cmd.SetSupportsStructuredOutput() return cmd } @@ -78,6 +78,5 @@ func newUpdateUnlockCommand(prime *primer.Values, globals *globalOptions) *capta }, ) cmd.SetSkipChecks(true) - cmd.SetDoesNotSupportStructuredOutput() return cmd } diff --git a/cmd/state/internal/cmdtree/use.go b/cmd/state/internal/cmdtree/use.go index cf02eaa9e7..2971339b51 100644 --- a/cmd/state/internal/cmdtree/use.go +++ b/cmd/state/internal/cmdtree/use.go @@ -29,7 +29,7 @@ func newUseCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return use.NewUse(prime).Run(params) }, - ).SetGroup(EnvironmentUsageGroup) + ).SetGroup(EnvironmentUsageGroup).SetSupportsStructuredOutput() return cmd } @@ -47,7 +47,7 @@ func newUseResetCommand(prime *primer.Values, globals *globalOptions) *captain.C params.Force = globals.NonInteractive return use.NewReset(prime).Run(params) }, - ).SetDoesNotSupportStructuredOutput() + ) } func newUseShowCommand(prime *primer.Values) *captain.Command { @@ -61,6 +61,6 @@ func newUseShowCommand(prime *primer.Values) *captain.Command { func(_ *captain.Command, _ []string) error { return use.NewShow(prime).Run() }, - ) + ).SetSupportsStructuredOutput() return cmd } diff --git a/cmd/state/main.go b/cmd/state/main.go index 0ee1d8d14e..abf9dfd498 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -1,12 +1,9 @@ package main import ( - "bufio" "context" - "errors" "fmt" "os" - "os/exec" "runtime/debug" "strings" "time" @@ -32,11 +29,11 @@ import ( "github.com/ActiveState/cli/internal/prompt" _ "github.com/ActiveState/cli/internal/prompt" // Sets up survey defaults "github.com/ActiveState/cli/internal/rollbar" + "github.com/ActiveState/cli/internal/runbits/errors" "github.com/ActiveState/cli/internal/runbits/legacy/projectmigration" "github.com/ActiveState/cli/internal/runbits/panics" "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/cli/internal/svcctl" - cmdletErrors "github.com/ActiveState/cli/pkg/cmdlets/errors" secretsapi "github.com/ActiveState/cli/pkg/platform/api/secrets" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" @@ -104,21 +101,10 @@ func main() { // Run our main command logic, which is logic that defers to the error handling logic below err = run(os.Args, isInteractive, cfg, out) if err != nil { - exitCode, err = cmdletErrors.ParseUserFacing(err) + exitCode, err = errors.ParseUserFacing(err) if err != nil { out.Error(err) } - - // If a state tool error occurs in a VSCode integrated terminal, we want - // to pause and give time to the user to read the error message. - // But not, if we exit, because the last command in the activated sub-shell failed. - var eerr *exec.ExitError - isExitError := errors.As(err, &eerr) - if !isExitError && outFlags.ConfirmExit { - out.Print(locale.T("confirm_exit_on_error_prompt")) - br := bufio.NewReader(os.Stdin) - br.ReadLine() - } } } @@ -221,6 +207,13 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out // Set up conditional, which accesses a lot of primer data sshell := subshell.New(cfg) + if isInteractive { + // Disable terminal echo while State Tool is running. + // Other than in prompts and subshells (which temporarily re-enable echo), user typing should + // not interfere with output (e.g. runtime progress bars). + sshell.TurnOffEcho() + defer sshell.TurnOnEcho() + } conditional := constraints.NewPrimeConditional(auth, pj, sshell.Shell()) project.RegisterConditional(conditional) @@ -265,8 +258,10 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out if childCmd != nil { cmdName = childCmd.JoinedSubCommandNames() + " " } - err = errs.AddTips(err, locale.Tl("err_tip_run_help", "Run → [ACTIONABLE]`state {{.V0}}--help`[/RESET] for general help", cmdName)) - cmdletErrors.ReportError(err, cmds.Command(), an) + if !out.Type().IsStructured() { + err = errs.AddTips(err, locale.Tl("err_tip_run_help", "Run → '[ACTIONABLE]state {{.V0}}--help[/RESET]' for general help", cmdName)) + } + errors.ReportError(err, cmds.Command(), an) } return err diff --git a/cmd/state/main_test.go b/cmd/state/main_test.go index d383b3bbae..77f44c8131 100644 --- a/cmd/state/main_test.go +++ b/cmd/state/main_test.go @@ -15,45 +15,44 @@ type MainTestSuite struct { func (suite *MainTestSuite) TestOutputer() { { - outputer, err := initOutput(outputFlags{"", false, false, false}, "", "") + outputer, err := initOutput(outputFlags{"", false, false}, "", "") suite.Require().NoError(err, errs.JoinMessage(err)) suite.Equal(output.PlainFormatName, outputer.Type(), "Returns Plain outputer") } { - outputer, err := initOutput(outputFlags{string(output.PlainFormatName), false, false, false}, "", "") + outputer, err := initOutput(outputFlags{string(output.PlainFormatName), false, false}, "", "") suite.Require().NoError(err) suite.Equal(output.PlainFormatName, outputer.Type(), "Returns Plain outputer") } { - outputer, err := initOutput(outputFlags{string(output.JSONFormatName), false, false, false}, "", "") + outputer, err := initOutput(outputFlags{string(output.JSONFormatName), false, false}, "", "") suite.Require().NoError(err) suite.Equal(output.JSONFormatName, outputer.Type(), "Returns JSON outputer") } { - outputer, err := initOutput(outputFlags{"", false, false, false}, string(output.JSONFormatName), "") + outputer, err := initOutput(outputFlags{"", false, false}, string(output.JSONFormatName), "") suite.Require().NoError(err) suite.Equal(output.JSONFormatName, outputer.Type(), "Returns JSON outputer") } { - outputer, err := initOutput(outputFlags{"", false, false, false}, string(output.EditorFormatName), "") + outputer, err := initOutput(outputFlags{"", false, false}, string(output.EditorFormatName), "") suite.Require().NoError(err) suite.Equal(output.EditorFormatName, outputer.Type(), "Returns JSON outputer") } } func (suite *MainTestSuite) TestParseOutputFlags() { - suite.Equal(outputFlags{"plain", false, false, false}, parseOutputFlags([]string{"state", "foo", "-o", "plain"})) - suite.Equal(outputFlags{"json", false, false, false}, parseOutputFlags([]string{"state", "foo", "--output", "json"})) - suite.Equal(outputFlags{"json", false, false, false}, parseOutputFlags([]string{"state", "foo", "-o", "json"})) - suite.Equal(outputFlags{"editor", false, false, false}, parseOutputFlags([]string{"state", "foo", "--output", "editor"})) - suite.Equal(outputFlags{"", true, false, false}, parseOutputFlags([]string{"state", "foo", "--mono"})) - suite.Equal(outputFlags{"", false, true, false}, parseOutputFlags([]string{"state", "foo", "--confirm-exit-on-error"})) - suite.Equal(outputFlags{"", false, false, true}, parseOutputFlags([]string{"state", "foo", "--non-interactive"})) - suite.Equal(outputFlags{"", false, false, true}, parseOutputFlags([]string{"state", "foo", "-n"})) + suite.Equal(outputFlags{"plain", false, false}, parseOutputFlags([]string{"state", "foo", "-o", "plain"})) + suite.Equal(outputFlags{"json", false, false}, parseOutputFlags([]string{"state", "foo", "--output", "json"})) + suite.Equal(outputFlags{"json", false, false}, parseOutputFlags([]string{"state", "foo", "-o", "json"})) + suite.Equal(outputFlags{"editor", false, false}, parseOutputFlags([]string{"state", "foo", "--output", "editor"})) + suite.Equal(outputFlags{"", true, false}, parseOutputFlags([]string{"state", "foo", "--mono"})) + suite.Equal(outputFlags{"", false, true}, parseOutputFlags([]string{"state", "foo", "--non-interactive"})) + suite.Equal(outputFlags{"", false, true}, parseOutputFlags([]string{"state", "foo", "-n"})) } func (suite *MainTestSuite) TestDisableColors() { diff --git a/cmd/state/output.go b/cmd/state/output.go index edce18f21d..74db4e896c 100644 --- a/cmd/state/output.go +++ b/cmd/state/output.go @@ -19,7 +19,6 @@ type outputFlags struct { // These should be kept in sync with cmd/state/internal/cmdtree (output flag) Output string `short:"o" long:"output"` Mono bool `long:"mono"` - ConfirmExit bool `long:"confirm-exit-on-error"` NonInteractive bool `short:"n" long:"non-interactive"` } diff --git a/internal/assets/contents/shells/bashrc.sh b/internal/assets/contents/shells/bashrc.sh index 20b708a06f..0f3094cbba 100644 --- a/internal/assets/contents/shells/bashrc.sh +++ b/internal/assets/contents/shells/bashrc.sh @@ -1,6 +1,6 @@ if [ -f ~/.bashrc ]; then source ~/.bashrc; fi -{{if ne .Owner ""}} +{{if and (ne .Owner "") (not .PreservePs1) }} export PS1="[{{.Owner}}/{{.Name}}] $PS1" {{end}} diff --git a/internal/assets/contents/shells/bashrc_append.sh b/internal/assets/contents/shells/bashrc_append.sh index c09af1c695..7865f881e8 100644 --- a/internal/assets/contents/shells/bashrc_append.sh +++ b/internal/assets/contents/shells/bashrc_append.sh @@ -6,4 +6,9 @@ export {{$K}}="{{$V}}:$PATH" export {{$K}}="{{$V}}" {{- end}} {{- end}} +{{- if .Default }} +if [[ ! -z "${{.ActivatedEnv}}" && -f "${{.ActivatedEnv}}/{{.ConfigFile}}" ]]; then + echo "State Tool is operating on project ${{.ActivatedNamespaceEnv}}, located at ${{.ActivatedEnv}}" +fi +{{- end}} # {{.Stop}} diff --git a/internal/assets/contents/shells/bashrc_global.sh b/internal/assets/contents/shells/bashrc_global.sh index 9b2f933649..61daa66d21 100644 --- a/internal/assets/contents/shells/bashrc_global.sh +++ b/internal/assets/contents/shells/bashrc_global.sh @@ -1,4 +1,4 @@ -{{if ne .Project ""}} +{{if and (ne .Project "") (not .PreservePs1)}} export PS1="[{{.Project}}] $PS1" {{end}} diff --git a/internal/assets/contents/shells/fishrc_append.fish b/internal/assets/contents/shells/fishrc_append.fish index 0f233db90d..2f6ca35b1e 100644 --- a/internal/assets/contents/shells/fishrc_append.fish +++ b/internal/assets/contents/shells/fishrc_append.fish @@ -6,4 +6,9 @@ set -xg {{$K}} "{{$V}}:$PATH" set -xg {{$K}} "{{$V}}" {{- end}} {{- end}} -# {{.Stop}} \ No newline at end of file +{{- if .Default }} +if test ! -z "${{.ActivatedEnv}}"; test -f "${{.ActivatedEnv}}/{{.ConfigFile}}" + echo "State Tool is operating on project ${{.ActivatedNamespaceEnv}}, located at ${{.ActivatedEnv}}" +end +{{- end}} +# {{.Stop}} diff --git a/internal/assets/contents/shells/tcshrc_append.sh b/internal/assets/contents/shells/tcshrc_append.sh index a8042870fb..5780a3a0bb 100644 --- a/internal/assets/contents/shells/tcshrc_append.sh +++ b/internal/assets/contents/shells/tcshrc_append.sh @@ -5,4 +5,11 @@ setenv {{$K}} "{{$V}}:$PATH" {{- else}} {{- end}} {{- end}} +{{- if .Default }} +if ( $?{{.ActivatedEnv}} ) then + if ( -f "${{.ActivatedEnv}}/{{.ConfigFile}}" ) then + echo "State Tool is operating on project ${{.ActivatedNamespaceEnv}}, located at ${{.ActivatedEnv}}" + endif +endif +{{- end}} # {{.Stop}} diff --git a/internal/assets/contents/shells/zshrc.sh b/internal/assets/contents/shells/zshrc.sh index 1d680a9fa1..7adb9a133c 100644 --- a/internal/assets/contents/shells/zshrc.sh +++ b/internal/assets/contents/shells/zshrc.sh @@ -2,7 +2,7 @@ if [ -f $ZDOTDIR/.zshrc ]; then source $ZDOTDIR/.zshrc; fi cd "{{.WD}}" -{{if ne .Owner ""}} +{{if and (ne .Owner "") (not .PreservePs1)}} export PS1="[{{.Owner}}/{{.Name}}] $PS1" {{end}} diff --git a/internal/assets/contents/shells/zshrc_append.sh b/internal/assets/contents/shells/zshrc_append.sh index c09af1c695..7865f881e8 100644 --- a/internal/assets/contents/shells/zshrc_append.sh +++ b/internal/assets/contents/shells/zshrc_append.sh @@ -6,4 +6,9 @@ export {{$K}}="{{$V}}:$PATH" export {{$K}}="{{$V}}" {{- end}} {{- end}} +{{- if .Default }} +if [[ ! -z "${{.ActivatedEnv}}" && -f "${{.ActivatedEnv}}/{{.ConfigFile}}" ]]; then + echo "State Tool is operating on project ${{.ActivatedNamespaceEnv}}, located at ${{.ActivatedEnv}}" +fi +{{- end}} # {{.Stop}} diff --git a/internal/assets/contents/shells/zshrc_global.sh b/internal/assets/contents/shells/zshrc_global.sh index 1dcbb3c58b..9f19cbfbe8 100644 --- a/internal/assets/contents/shells/zshrc_global.sh +++ b/internal/assets/contents/shells/zshrc_global.sh @@ -1,4 +1,4 @@ -{{if ne .Project ""}} +{{if and (ne .Project "") (not .PreservePs1)}} export PS1="[{{.Project}}] $PS1" {{- end}} diff --git a/internal/captain/command.go b/internal/captain/command.go index 37e1cd4b21..e18d5cff5a 100644 --- a/internal/captain/command.go +++ b/internal/captain/command.go @@ -101,8 +101,8 @@ type Command struct { skipChecks bool - unstable bool - noStructuredOutput bool + unstable bool + structuredOutput bool examples []string @@ -180,7 +180,7 @@ func NewCommand(name, title, description string, prime primer, flags []*Flag, ar return nil } cmd.outputTitleIfAny() - } else if cmd.out.Type().IsStructured() && cmd.noStructuredOutput { + } else if cmd.out.Type().IsStructured() && !cmd.structuredOutput { cmd.out.Error(locale.NewInputError("err_no_structured_output", "", string(cmd.out.Type()))) return nil } @@ -290,7 +290,7 @@ func (c *Command) Execute(args []string) error { c.cobra.SetArgs(args) err := c.cobra.Execute() c.cobra.SetArgs(nil) - return setupSensibleErrors(err) + return setupSensibleErrors(err, args) } func (c *Command) SetExamples(examples ...string) *Command { @@ -448,8 +448,8 @@ func (c *Command) SetHasVariableArguments() *Command { return c } -func (c *Command) SetDoesNotSupportStructuredOutput() *Command { - c.noStructuredOutput = true +func (c *Command) SetSupportsStructuredOutput() *Command { + c.structuredOutput = true return c } @@ -644,7 +644,7 @@ func (c *Command) cobraExecHandler(cobraCmd *cobra.Command, args []string) error if c.shouldWarnUnstable() && !condition.OptInUnstable(c.cfg) { c.out.Notice(locale.Tr("unstable_command_warning")) return nil - } else if c.out.Type().IsStructured() && c.noStructuredOutput { + } else if c.out.Type().IsStructured() && !c.structuredOutput { c.out.Error(locale.NewInputError("err_no_structured_output", "", string(c.out.Type()))) return nil } @@ -768,7 +768,7 @@ func (c *Command) argValidator(cobraCmd *cobra.Command, args []string) error { // setupSensibleErrors inspects an error value for certain errors and returns a // wrapped error that can be checked and that is localized. -func setupSensibleErrors(err error) error { +func setupSensibleErrors(err error, args []string) error { if err, ok := err.(error); ok && err == nil { return nil } @@ -816,10 +816,7 @@ func setupSensibleErrors(err error) error { } if pflagErrCmd := pflagCmdErrMsgCmd(errMsg); pflagErrCmd != "" { - return locale.NewInputError( - "command_cmd_no_such_cmd", - "No such command: [NOTICE]{{.V0}}[/RESET]", pflagErrCmd, - ) + return locale.NewInputError("command_cmd_no_such_cmd", "", pflagErrCmd) } // Cobra error message of the form "accepts at most 0 arg(s), received 1, called at: " @@ -830,6 +827,9 @@ func setupSensibleErrors(err error) error { multilog.Error("Unable to parse cobra error message: %v", err) return locale.NewInputError("err_cmd_unexpected_arguments", "Unexpected argument(s) given") } + if max == 0 && received > 0 { + return locale.NewInputError("command_cmd_no_such_cmd", "", args[len(args)-received]) + } return locale.NewInputError( "err_cmd_too_many_arguments", "Too many arguments given: {{.V0}} expected, {{.V1}} received", diff --git a/internal/constants/constants.go b/internal/constants/constants.go index e2a794a63c..591c9c97c4 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -93,6 +93,9 @@ const ActivatedStateEnvVarName = "ACTIVESTATE_ACTIVATED" // ActivatedStateIDEnvVarName is the name of the environment variable that is set when in an activated state, its value will be a unique id identifying a specific instance of an activated state const ActivatedStateIDEnvVarName = "ACTIVESTATE_ACTIVATED_ID" +// ActivatedStateNamespaceEnvVarName is the name of the environment variable that specifies the activated state's org/project namespace. +const ActivatedStateNamespaceEnvVarName = "ACTIVESTATE_ACTIVATED_NAMESPACE" + // ForwardedStateEnvVarName is the name of the environment variable that is set when in an activated state, its value will be the path of the project const ForwardedStateEnvVarName = "ACTIVESTATE_FORWARDED" @@ -500,6 +503,9 @@ const PipShim = "pip" // AutoUpdateConfigKey is the config key for storing whether or not autoupdates can be performed const AutoUpdateConfigKey = "autoupdate" +// PreservePs1ConfigKey is the config key that specifies whether to modify the shell PS1/prompt to show [org/project] info. +const PreservePs1ConfigKey = "preserve.prompt" + // DefaultAnalyticsPixel is the default url for the analytics pixel const DefaultAnalyticsPixel = "https://state-tool.s3.amazonaws.com/pixel" diff --git a/internal/httpreq/request.go b/internal/httpreq/request.go deleted file mode 100644 index fd41b67f98..0000000000 --- a/internal/httpreq/request.go +++ /dev/null @@ -1,45 +0,0 @@ -package httpreq - -import ( - "context" - "io/ioutil" - "net/http" - - "github.com/ActiveState/cli/internal/errs" -) - -type Client struct { - HttpClient *http.Client -} - -func New() *Client { - return &Client{http.DefaultClient} -} - -func (c *Client) Get(url string) ([]byte, int, error) { - return c.GetWithContext(context.Background(), url) -} - -func (c *Client) GetWithContext(ctx context.Context, url string) ([]byte, int, error) { - resp, err := c.HttpClient.Get(url) - if err != nil { - return []byte{}, 0, errs.Wrap(err, "Couldn't get url=%s", url) - } - defer resp.Body.Close() - - code := 0 - if resp != nil { - code = resp.StatusCode - } - - response, err := ioutil.ReadAll(resp.Body) - if err != nil { - return []byte{}, code, errs.New("Could not read body. Status: %s", resp.Status) - } - - if resp.StatusCode != 200 { - return response, code, errs.New("bad http status from %s: %v, body: %s", url, resp.Status, response) - } - - return response, code, nil -} diff --git a/internal/locale/locales/en-us.yaml b/internal/locale/locales/en-us.yaml index 80cceaee0e..22088dd2b3 100644 --- a/internal/locale/locales/en-us.yaml +++ b/internal/locale/locales/en-us.yaml @@ -286,7 +286,7 @@ err_language_not_found: error_state_activate_copy_save: other: Error saving project file error_state_activate_new_no_commit_aborted: - other: "Platform project created but cannot activate. Try [ACTIONABLE]`state activate {{.Owner}}/{{.ProjectName}}`[/RESET]." + other: "Platform project created but cannot activate. Try '[ACTIONABLE]state activate {{.Owner}}/{{.ProjectName}}[/RESET]'." state_activate_new_prompt_owner: other: "Please provide an owner for the new project:" error_state_activate_new_fetch_organizations: @@ -294,7 +294,7 @@ error_state_activate_new_fetch_organizations: state_activate_new_created: other: Created new project in [NOTICE]{{.Dir}}[/RESET] state_activate_new_platform_project: - other: New platform project successfully created as [NOTICE]{{.Owner}}/{{.Project}}[/RESET]. To activate your new project run [ACTIONABLE]`state activate {{.Owner}}/{{.Project}}`[/RESET] + other: New platform project successfully created as [NOTICE]{{.Owner}}/{{.Project}}[/RESET]. To activate your new project run '[ACTIONABLE]state activate {{.Owner}}/{{.Project}}[/RESET]' state_activate_new_prompt_language: other: Which language would you like to use to manage your State Tool scripts? state_activate_new_language_none: @@ -326,7 +326,7 @@ projects_description: projects_list_description: other: List your platform projects err_api_not_authenticated: - other: You are not authenticated, authenticate with [ACTIONABLE]`state auth`[/RESET]. + other: You are not authenticated, authenticate with '[ACTIONABLE]state auth[/RESET]'. err_api_forbidden: other: You are not allowed to access or modify the requested resource err_api_org_not_found: @@ -351,6 +351,11 @@ scripts_description: other: Show project scripts flag_json_desc: other: Changes output to machine-readable JSON +runtime_setup_in_use_err: + other: | + Could not update your runtime as it is currently in use. Please stop any active runtime processes and try again. State Tool found the following processes to be using the runtime: + + {{.V0}} runtime_alternative_failed_artifact_order: other: "Could not write runtime.json file: internal error" runtime_alternative_file_transforms_err: @@ -467,8 +472,6 @@ prompt_env_choose_remove: other: Which variable would you like to remove? err_env_cannot_parse: other: Could not parse variables used in your request -prompt_env_option: - other: "{{.Variable}}: `{{.Value}}` ({{.Constraints}}{{.Hash}})" env_removed: other: "Variable removed: [NOTICE]{{.Variable}} ({{.Hash}}[/RESET])" env_inherit_description: @@ -661,7 +664,7 @@ keypair_err_publickey_not_found: keypair_err_passphrase_prompt: other: The provided passphrase is invalid keypair_err_require_auth: - other: You need to be authenticated to run this command, please run [ACTIONABLE]`state auth`[/RESET] first + other: You need to be authenticated to run this command, please run '[ACTIONABLE]state auth[/RESET]' first keypairs_err_bitlength_too_short: other: bit-length too short keypairs_err_pem_encoding: @@ -681,7 +684,7 @@ keypairs_err_override_with_save: keypairs_err_override_with_delete: other: "cannot delete key file while key override is set" err_command_requires_auth: - other: You need to be authenticated to run this command. Authenticate by running [ACTIONABLE]`state auth`[/RESET]. + other: You need to be authenticated to run this command. Authenticate by running '[ACTIONABLE]state auth[/RESET]'. warn_script_name_in_use: other: "[ERROR]WARNING:[/RESET] The following scripts collide with existing commands and should be renamed: [NOTICE]{{.V0}}[/RESET]." warn_move_incompatible_modes: @@ -714,7 +717,7 @@ update_available: other: | Your version: [NOTICE]{{.V0}}[/RESET] Available Version: [NOTICE]{{.V1}}[/RESET] - You can update by running [ACTIONABLE]`state update`[/RESET] + You can update by running '[ACTIONABLE]state update[/RESET]' update_none_found: other: Update not found for {{.V0}}. updating_version: @@ -724,7 +727,7 @@ runtime_update_notice_known_count: runtime_update_notice_unknown_count: other: "Your activestate.yaml is behind." runtime_update_help: - other: "Run [ACTIONABLE]`state pull`[/RESET] to pull in the latest runtime environment as defined in your project on [NOTICE]https://platform.activestate.com/{{.V0}}/{{.V1}}[/RESET]" + other: "Run '[ACTIONABLE]state pull[/RESET]' to pull in the latest runtime environment as defined in your project on [NOTICE]https://platform.activestate.com/{{.V0}}/{{.V1}}[/RESET]" err_no_credentials: other: Cannot authenticate without credentials err_token_list: @@ -859,7 +862,7 @@ warn_deprecation: Reason for deprecation: [NOTICE]{{.V1}}[/RESET] - Please update by running [ACTIONABLE]`state update`[/RESET]. + Please update by running '[ACTIONABLE]state update[/RESET]'. err_deprecation: other: | You are running a version of the State Tool that is no longer supported! You will be encountering issues. @@ -867,7 +870,7 @@ err_deprecation: Reason for deprecation: [NOTICE]{{.V1}}[/RESET] - Please update by running [ACTIONABLE]`state update`[/RESET]. + Please update by running '[ACTIONABLE]state update[/RESET]'. err_auth_required: other: Authentication is required, please authenticate by running 'state auth' err_auth_needinput: @@ -1001,9 +1004,9 @@ err_replaceall_check_log: err_bad_project_url: other: "Invalid value for 'project' field in your activestate.yaml, please ensure it is in the format of: 'https://platform.activestate.com/org/project'." err_set_commit_id: - other: "Could not update your activestate.yaml with the latest commit ID. Please ensure you have your project defined in the format of [ACTIONABLE]`project: https://platform.activestate.com/org/project`[/RESET]" + other: "Could not update your activestate.yaml with the latest commit ID. Please ensure you have your project defined in the format of '[ACTIONABLE]project: https://platform.activestate.com/org/project[/RESET]'" err_set_project: - other: "Could not update the project field in your activestate.yaml. Please ensure you have your project defined in the format of [ACTIONABLE]`project: https://platform.activestate.com/org/project`[/RESET]" + other: "Could not update the project field in your activestate.yaml. Please ensure you have your project defined in the format of '[ACTIONABLE]project: https://platform.activestate.com/org/project[/RESET]'" err_unauthorized: other: You are not authorized, did you provide valid login credentials? err_projectfile_exists: @@ -1084,14 +1087,10 @@ commit_message_updated_package: other: "Updated package: {{.V0}} to {{.V1}}" commit_message_add_initial: other: Initialize project via the State Tool -confirm_remove_existing_prompt: - other: "Your project already has packages configured for it, continuing will overwrite your existing package selection. Are you sure you want to do this?" commit_reqstext_message: other: "Import from requirements file" commit_reqstext_remove_existing_message: other: "Remove current packages prior to import from requirements file" -err_cannot_remove_existing: - other: "The already configured packages were not able to be overwritten" commit_failed_push_tip: other: Ensure any previous changes have been saved by running [ACTIONABLE]state push[/RESET]. Then try running your command again. commit_failed_pull_tip: @@ -1154,6 +1153,8 @@ bundle_version_updated: other: "Bundle updated: [NOTICE]{{.V0}}@{{.V1}}[/RESET]" err_package_removed: other: Failed to remove package +err_remove_requirement_not_found: + other: Could not remove requirement '[ACTIONABLE]{{.V0}}[/RESET]', because it does not exist. err_bundle_removed: other: Failed to remove bundle err_packages_removed: @@ -1163,19 +1164,21 @@ package_removed: bundle_removed: other: "Bundle uninstalled: [NOTICE]{{.V0}}[/RESET]" update_config: - other: To ensure your local project is synchronized with the ActiveState platform please use [ACTIONABLE]`state pull`[/RESET] + other: To ensure your local project is synchronized with the ActiveState platform please use '[ACTIONABLE]state pull[/RESET]' package_update_config_file: - other: To ensure your local project is synchronized with the ActiveState platform please use [ACTIONABLE]`state pull`[/RESET] + other: To ensure your local project is synchronized with the ActiveState platform please use '[ACTIONABLE]state pull[/RESET]' inventory_ingredient_not_available: other: The package '[NOTICE]{{.V0}}[/RESET]' is not available on the ActiveState Platform, or does not have a matching version err_update_build_script: other: Could not update runtime build script notice_commit_build_script: other: | - Your local build script has changes that should be committed. Please run [ACTIONABLE]`state commit`[/RESET] to do so. + Your local build script has changes that should be committed. Please run '[ACTIONABLE]state commit[/RESET]' to do so. command_flag_invalid_value: other: "Invalid value for {{.V0}} flag: [NOTICE]{{.V1}}[/RESET]" +command_cmd_no_such_cmd: + other: "No such command: [NOTICE]{{.V0}}[/RESET]" err_cmdtree: other: Could not run the requested command err_fetch_languages: @@ -1199,7 +1202,7 @@ err_init_lang: init_success: other: | Project '[NOTICE]{{.V0}}[/RESET]' has been successfully initialized at '[NOTICE]{{.V1}}[/RESET]' and pushed to the Platform. - You can start using your project with [ACTIONABLE]`state shell`[/RESET] and [ACTIONABLE]`state use`[/RESET]. + You can start using your project with '[ACTIONABLE]state shell[/RESET]' and '[ACTIONABLE]state use[/RESET]'. For editors and other tooling use the executables at: [ACTIONABLE]{{.V2}}[/RESET]. arg_state_init_namespace: other: org/project @@ -1440,7 +1443,7 @@ deploy_restart_shell: deploy_restart_cmd: other: | Please log out and back in again in order to start using the newly configured Runtime Environment. - Alternatively, you can run `setenv` from the installation directory to update your environment + Alternatively, you can run '[ACTIONABLE]setenv[/RESET]' from the installation directory to update your environment flag_state_deploy_path_description: other: The path to deploy the runtime installation files to. This directory will not be affected by 'state clean' flag_state_deploy_force_description: @@ -1513,23 +1516,23 @@ ppm_print_main: See [ACTIONABLE]https://docs.activestate.com/platform/state/[/RESET] for details. ppm_print_forward: other: | - Your command is being forwarded to [ACTIONABLE]`{{.V0}}`[/RESET]. - In the future you can run [ACTIONABLE]`{{.V0}}`[/RESET] directly instead of running [ACTIONABLE]`{{.V1}}`[/RESET], which will soon be deprecated. + Your command is being forwarded to '[ACTIONABLE]{{.V0}}[/RESET]'. + In the future you can run '[ACTIONABLE]{{.V0}}[/RESET]' directly instead of running '[ACTIONABLE]{{.V1}}[/RESET]', which will soon be deprecated. ppm_print_forward_after_convert: other: | - Please navigate to your newly created project directory and run [ACTIONABLE]`{{.V1}}`[/RESET] again. - You can also run [ACTIONABLE]`{{.V0}}`[/RESET] directly instead of running [ACTIONABLE]`{{.V1}}`[/RESET], which will soon be deprecated. + Please navigate to your newly created project directory and run '[ACTIONABLE]{{.V1}}[/RESET]' again. + You can also run '[ACTIONABLE]{{.V0}}[/RESET]' directly instead of running '[ACTIONABLE]{{.V1}}[/RESET]', which will soon be deprecated. ppm_print_forward_failure: other: | - Your command is being forwarded to [ACTIONABLE]`{{.V0}}`[/RESET]. - In the future you can run [ACTIONABLE]`{{.V0}}`[/RESET] directly instead of running [ACTIONABLE]`{{.V1}}`[/RESET], which will soon be deprecated. + Your command is being forwarded to '[ACTIONABLE]{{.V0}}[/RESET]'. + In the future you can run '[ACTIONABLE]{{.V0}}[/RESET]' directly instead of running '[ACTIONABLE]{{.V1}}[/RESET]', which will soon be deprecated. - Note that [ACTIONABLE]`{{.V0}}`[/RESET] is not a direct analog for [ACTIONABLE]`{{.V1}}`[/RESET] and your command may have failed as a result. - Please run [ACTIONABLE]`{{.V0}} --help`[/RESET] for further information on how to use it for your use-case. + Note that '[ACTIONABLE]{{.V0}}[/RESET]' is not a direct analog for '[ACTIONABLE]{{.V1}}[/RESET]' and your command may have failed as a result. + Please run '[ACTIONABLE]{{.V0}} --help[/RESET]' for further information on how to use it for your use-case. ppm_print_area_redundant: other: | `ppm area` and its subcommands have been made redundant thanks to State Tool's use of virtual environments. - You can simply start managing your packages, run [ACTIONABLE]`{{.V0}} --help`[/RESET] for more information. + You can simply start managing your packages, run '[ACTIONABLE]{{.V0}} --help[/RESET]' for more information. ppm_install_intent: other: add new packages to your project ppm_upgrade_intent: @@ -1561,8 +1564,6 @@ deploy_usable_path: Please ensure '[NOTICE]{{.V0}}[/RESET]' exists and is on your PATH. script_watcher_watch_file: other: "Watching file changes at: [NOTICE]{{.V0}}[/RESET]" -confirm_exit_on_error_prompt: - other: Press enter to continue... tutorial_newproject_intro: other: | The State Tool lets you create and maintain a set of virtual environments (eg., one per project), allowing you to @@ -1573,17 +1574,17 @@ tutorial_newproject_intro: set up shared scripts, and much more. [[BR]][[BR]] We'll help you create your first virtual environment, after that you can create as many as you want by running - [ACTIONABLE]`state init`[/RESET]. + '[ACTIONABLE]state init[/RESET]'. [[BR]] tutorial_newproject_outro: other: | Your Virtual Runtime Environment has been created. [[BR]][[BR]] - To start using your project simply run [ACTIONABLE]`state activate`[/RESET] from your project directory at [ACTIONABLE]{{.Dir}}[/RESET]. + To start using your project simply run '[ACTIONABLE]state activate[/RESET]' from your project directory at [ACTIONABLE]{{.Dir}}[/RESET]. You can also manage your project in your browser at [ACTIONABLE]{{.URL}}[/RESET]. [[BR]][[BR]] - To create another project run [ACTIONABLE]`state init`[/RESET], or if you want to find out more about the State Tool and what it can do - run [ACTIONABLE]`state --help`[/RESET] or check out [ACTIONABLE]{{.Docs}}[/RESET] + To create another project run '[ACTIONABLE]state init[/RESET]', or if you want to find out more about the State Tool and what it can do + run '[ACTIONABLE]state --help[/RESET]' or check out [ACTIONABLE]{{.Docs}}[/RESET] ppm_convert_after_tutorial: other: | For your convenience you can continue to use ppm commands once you've activated your virtual runtime environment. @@ -1604,8 +1605,8 @@ ppm_convert_ask_feedback: We understand your need to do things traditionally, but we currently do not support it. We'd love to hear more about your use case to see if we can better meet your needs. Please consider posting to our forum at {{.ForumURL}}. [[BR]] - To create another project run [ACTIONABLE]`state init`[/RESET], or if you want to find out more about the State Tool and what it can do - run [ACTIONABLE]`state --help`[/RESET] or check out [ACTIONABLE]{{.Docs}}[/RESET] + To create another project run '[ACTIONABLE]state init[/RESET]', or if you want to find out more about the State Tool and what it can do + run '[ACTIONABLE]state --help[/RESET]' or check out [ACTIONABLE]{{.Docs}}[/RESET] windows_compatibility_warning: other: | Unable to detect your Windows version. You may encounter issues; please ensure you are running at least Windows 10 build 17134.1. @@ -1630,7 +1631,7 @@ artifact_succeeded: artifact_failed: other: "Failed: [NOTICE]{{.V0}}[/RESET]. Error returned: [ERROR]{{.V1}}[/RESET]" err_package_update_commit_id: - other: "Could not update your project runtime's commit ID. Please run [ACTIONABLE]`state pull`[/RESET] manually." + other: "Could not update your project runtime's commit ID. Please run '[ACTIONABLE]state pull[/RESET]' manually." prepare_protocol_warning: other: Could not add state protocol err_preparestart_shortcut: @@ -1640,7 +1641,7 @@ err_preparestart_icon: confirm: other: Please Confirm err_no_project: - other: To run this command you need to be in an existing project. You can create a new project with [ACTIONABLE]state init[/RESET] or run this command from a directory that is in an existing project. You can also use an existing project by running [ACTIONABLE]`state activate --default`[/RESET] inside your project directory. + other: To run this command you need to be in an existing project. You can create a new project with [ACTIONABLE]state init[/RESET] or run this command from a directory that is in an existing project. You can also use an existing project by running '[ACTIONABLE]state activate --default[/RESET]' inside your project directory. activate_default_prompt: other: | Would you like always use [NOTICE]{{.V0}}[/RESET]? @@ -1664,16 +1665,16 @@ config_get_error: cve_needs_authentication: other: You need to be authenticated in order to access vulnerability information about your project. auth_tip: - other: Run `state auth` to authenticate. + other: Run '[ACTIONABLE]state auth[/RESET]' to authenticate. err_non_interactive_mode: - other: You are running the State Tool in non-interactive mode, but interactive input was needed. Please re-run the State Tool in interactive mode. If you are using a non-interactive terminal like Git Bash on Windows, prefix your command with [ACTIONABLE]`winpty`[/RESET]. (See https://github.com/git-for-windows/git/wiki/FAQ#some-native-console-programs-dont-work-when-run-from-git-bash-how-to-fix-it for more information.) + other: You are running the State Tool in non-interactive mode, but interactive input was needed. Please re-run the State Tool in interactive mode. If you are using a non-interactive terminal like Git Bash on Windows, prefix your command with '[ACTIONABLE]winpty[/RESET]'. (See https://github.com/git-for-windows/git/wiki/FAQ#some-native-console-programs-dont-work-when-run-from-git-bash-how-to-fix-it for more information.) err_alternate_branches: other: | - Your current platform ([ACTIONABLE]{{.V0}}/{{.V1}}[/RESET]) does not appear to be configured in your branch: [ACTIONABLE]'{{.V2}}'[/RESET], perhaps you could try one of the alternate branches on your project: + Your current platform ([ACTIONABLE]{{.V0}}/{{.V1}}[/RESET]) does not appear to be configured in your branch: '[ACTIONABLE]{{.V2}}[/RESET]', perhaps you could try one of the alternate branches on your project: - {{.V3}} - Type [ACTIONABLE]'state branch switch '[/RESET] to switch to a different branch. + Type '[ACTIONABLE]state branch switch [/RESET]' to switch to a different branch. err_fetch_project: other: "Could not fetch details for project: {{.V0}}" err_set_default_branch: @@ -1709,12 +1710,12 @@ install_initial_success: err_package_info_no_packages: other: No packages in our catalog are an exact match for [NOTICE]"{{.V0}}"[/RESET]. package_try_search: - other: "Valid package names can be searched using [ACTIONABLE]`state search {package_name}`[/RESET]" + other: "Valid package names can be searched using '[ACTIONABLE]state search [/RESET]'" package_info_request: other: "Request a package at [ACTIONABLE]https://community.activestate.com/[/RESET]" err_push_outdated: other: | - Your project has new changes available that need to be merged first. Please first run `[ACTIONABLE]state pull[/RESET]` to update your project. + Your project has new changes available that need to be merged first. Please first run '[ACTIONABLE]state pull[/RESET]' to update your project. err_tip_push_outdated: other: Run '[ACTIONABLE]state pull[/RESET]'. err_push_not_authenticated: @@ -1739,12 +1740,12 @@ err_push_target_invalid_history: other: The target's commit history does not match your local commit history. Are you pushing to the right project? err_pull_incompatible: other: | - The remote project has an incompatible commit history. Target a different project with `[ACTIONABLE--set-project[/RESET]` or reset your local checkout with `[ACTIONABLE]state reset[/RESET]`. + The remote project has an incompatible commit history. Target a different project with '[ACTIONABLE]--set-project[/RESET]' or reset your local checkout with '[ACTIONABLE]state reset[/RESET]'. pull_diverged_message: other: | A merge commit will be created for project [NOTICE]{{.V0}}[/RESET] on branch [NOTICE]{{.V1}}[/RESET]. Merging commits [NOTICE]{{.V2}}[/RESET] (local) and [NOTICE]{{.V3}}[/RESET] (remote). - Any conflicts will favour your local changes. To view the merge commit run `[ACTIONABLE]state history[/RESET]` + Any conflicts will favour your local changes. To view the merge commit run '[ACTIONABLE]state history[/RESET]' pull_merge_commit: other: Merged {{.V1}} into {{.V0}} operation_success_local: @@ -1753,6 +1754,8 @@ operation_success_local: Run [ACTIONABLE]state push[/RESET] to save changes to the platform. buildplan_err: other: "Could not plan build, platform responded with:\n{{.V0}}\n{{.V1}}" +err_buildplanner_head_on_branch_moved: + other: The branch you're trying to update has changed remotely, please run '[ACTIONABLE]state pull[/RESET]'. transient_solver_tip: other: You may want to retry this command if the failure was related to a resourcing or networking issue. warning_git_project_mismatch: @@ -1787,7 +1790,7 @@ lock_update_legacy_version: lock_version_mismatch: other: This project is locked at State Tool version [NOTICE]{{.V0}}[/RESET]. Your current State Tool version is [NOTICE]{{.V1}}@{{.V2}}[/RESET]. lock_update_lock: - other: You can lock the project to the running State Tool version with `[ACTIONABLE]state update lock[/RESET]`. + other: You can lock the project to the running State Tool version with '[ACTIONABLE]state update lock[/RESET]'. err_ping_version_mismatch: other: | The installed version of the State Tool Service does not match that of the client. @@ -1873,13 +1876,13 @@ err_update_corrupt_install: arg_state_use_namespace: other: org/project or project arg_state_use_namespace_description: - other: "The fully qualified namespace (org/project) or just the project name (project) of the project to use. Must have been previously checked out using [ACTIONABLE]`state checkout`[/RESET]." + other: "The fully qualified namespace (org/project) or just the project name (project) of the project to use. Must have been previously checked out using '[ACTIONABLE]state checkout[/RESET]'." err_use_commit_id_mismatch: - other: "Cannot switch to the given commit ID. Please use [ACTIONABLE]`state checkout`[/RESET] to check out a specific commit ID and then try again." + other: "Cannot switch to the given commit ID. Please use '[ACTIONABLE]state checkout[/RESET]' to check out a specific commit ID and then try again." err_use_default_project_does_not_exist: other: "Cannot find your project. Please either check it out again with [ACTIONABLE]state checkout[/RESET] or run [ACTIONABLE]state use reset[/RESET] to stop using it." err_shell_commit_id_mismatch: - other: "Cannot start a shell/prompt for the given commit ID. Please use [ACTIONABLE]`state checkout`[/RESET] to check out a specific commit ID and then try again." + other: "Cannot start a shell/prompt for the given commit ID. Please use '[ACTIONABLE]state checkout[/RESET]' to check out a specific commit ID and then try again." err_shell_cannot_load_project: other: Cannot load project to start a shell/prompt in. err_shell_already_active: @@ -1897,7 +1900,7 @@ err_project_namespace_missmatch: err_project_fromenv: other: Could not detect an appropriate project to use, please provide a valid path or namespace. err_local_project_not_checked_out: - other: "Cannot find the [ACTIONABLE]{{.V0}}[/RESET] project. Please either check it out using [ACTIONABLE]`state checkout`[/RESET] or run this command again from the project directory." + other: "Cannot find the [ACTIONABLE]{{.V0}}[/RESET] project. Please either check it out using '[ACTIONABLE]state checkout[/RESET]' or run this command again from the project directory." err_findproject_notfound: other: "Could not load project [ACTIONABLE]{{.V0}}[/RESET] from path: [ACTIONABLE]{{.V1}}[/RESET]" arg_state_shell_namespace_description: @@ -2030,7 +2033,7 @@ pjfile_deprecation_msg: pjfile_deprecation_entry: other: " - '[ACTIONABLE]{{.V0}}[/RESET]' located at byte [ACTIONABLE]{{.V1}}[/RESET]" err_init_authenticated: - other: You need to be authenticated to initialize a project. Authenticate by running [ACTIONABLE]`state auth`[/RESET]. + other: You need to be authenticated to initialize a project. Authenticate by running '[ACTIONABLE]state auth[/RESET]'. err_country_blocked: other: This service is not available in your region. err_commit_id_invalid: @@ -2061,12 +2064,12 @@ notice_runtime_disabled: err_local_commit_file: other: | Could not find or read the commit file for your project at '[ACTIONABLE]{{.V0}}[/RESET]'. - + You can fix this by either running '[ACTIONABLE]state pull[/RESET]' to update to the latest commit, or manually switch to a specific commit by running '[ACTIONABLE]state switch [/RESET]'. - + To view your projects commits run '[ACTIONABLE]state history[/RESET]'. - + Please avoid deleting or editing this file directly. projectmigration_confirm: other: | diff --git a/internal/offinstall/storage_darwin.go b/internal/offinstall/storage_darwin.go deleted file mode 100644 index 893adf8a77..0000000000 --- a/internal/offinstall/storage_darwin.go +++ /dev/null @@ -1,17 +0,0 @@ -package offinstall - -import ( - "path/filepath" - - "github.com/ActiveState/cli/internal/errs" - "github.com/mitchellh/go-homedir" -) - -func DefaultInstallParentDir() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", errs.Wrap(err, "Could not get home directory") - } - - return filepath.Join(home, "Applications"), nil -} diff --git a/internal/offinstall/storage_linux.go b/internal/offinstall/storage_linux.go deleted file mode 100644 index 20d0f87d93..0000000000 --- a/internal/offinstall/storage_linux.go +++ /dev/null @@ -1,17 +0,0 @@ -package offinstall - -import ( - "path/filepath" - - "github.com/ActiveState/cli/internal/errs" - "github.com/mitchellh/go-homedir" -) - -func DefaultInstallParentDir() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", errs.Wrap(err, "Could not get home directory") - } - - return filepath.Join(home, ".local", "share", "applications"), nil -} diff --git a/internal/offinstall/storage_windows.go b/internal/offinstall/storage_windows.go deleted file mode 100644 index 54c4036337..0000000000 --- a/internal/offinstall/storage_windows.go +++ /dev/null @@ -1,11 +0,0 @@ -package offinstall - -import ( - "os" - "path/filepath" -) - -func DefaultInstallParentDir() (string, error) { - // There is no system install path for Windows - return filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local", "Programs"), nil -} diff --git a/internal/osutils/osutils.go b/internal/osutils/osutils.go index 3618d31389..5c3e65782a 100644 --- a/internal/osutils/osutils.go +++ b/internal/osutils/osutils.go @@ -6,8 +6,11 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strings" + "github.com/shirou/gopsutil/v3/process" + "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/multilog" @@ -123,3 +126,33 @@ func ExecutableName() string { name = strings.TrimSuffix(name, path.Ext(name)) return name } + +// GetProcessesInUse returns a map of currently running executables to their process IDs where +// those executables are in the given directory or a subdirectory of that given directory. +func GetProcessesInUse(dir string) map[string]int32 { + inUse := map[string]int32{} + + procs, err := process.Processes() + if err != nil { + multilog.Error("Unable to get running processes: %v", err) + return inUse + } + + if runtime.GOOS != "linux" { + dir = strings.ToLower(dir) // Windows and macOS filesystems are case-insensitive + } + for _, p := range procs { + exe, err := p.Exe() + if err != nil { + continue // probably a permission error; ignore + } + exeToCompare := exe + if runtime.GOOS != "linux" { + exeToCompare = strings.ToLower(exeToCompare) // Windows and macOS filesystems are case-insensitive + } + if strings.Contains(exeToCompare, dir) { + inUse[exe] = p.Pid + } + } + return inUse +} diff --git a/internal/output/json.go b/internal/output/json.go index 5411d4a530..38f28c9b8d 100644 --- a/internal/output/json.go +++ b/internal/output/json.go @@ -65,7 +65,8 @@ func (f *JSON) Fprint(writer io.Writer, value interface{}) { } type StructuredError struct { - Error string `json:"error"` + Error string `json:"error"` + Tips []string `json:"tips,omitempty"` } // Error will marshal and print the given value to the error writer @@ -120,11 +121,11 @@ func toStructuredError(v interface{}) StructuredError { case StructuredError: return vv case error: - return StructuredError{locale.JoinedErrorMessage(vv)} + return StructuredError{Error: locale.JoinedErrorMessage(vv)} case string: - return StructuredError{vv} + return StructuredError{Error: vv} } message := fmt.Sprintf("Not a recognized error format: %v", v) multilog.Error(message) - return StructuredError{message} + return StructuredError{Error: message} } diff --git a/internal/output/mediator.go b/internal/output/mediator.go index 6b3cc2a680..c71acb111e 100644 --- a/internal/output/mediator.go +++ b/internal/output/mediator.go @@ -56,7 +56,7 @@ func mediatorValue(v interface{}, format Format) interface{} { if vt, ok := v.(StructuredMarshaller); ok { return vt.MarshalStructured(format) } - return StructuredError{locale.Tr("err_no_structured_output", string(format))} + return StructuredError{Error: locale.Tr("err_no_structured_output", string(format))} } if vt, ok := v.(Marshaller); ok { return vt.MarshalOutput(format) diff --git a/internal/output/mediator_test.go b/internal/output/mediator_test.go index 1b4adbe5f7..2648eb0117 100644 --- a/internal/output/mediator_test.go +++ b/internal/output/mediator_test.go @@ -67,7 +67,7 @@ func Test_mediatorValue(t *testing.T) { "unstructured", JSONFormatName, }, - StructuredError{locale.Tr("err_no_structured_output", string(JSONFormatName))}, + StructuredError{Error: locale.Tr("err_no_structured_output", string(JSONFormatName))}, }, } for _, tt := range tests { diff --git a/internal/retryhttp/client.go b/internal/retryhttp/client.go index 5924591357..a97f438b06 100644 --- a/internal/retryhttp/client.go +++ b/internal/retryhttp/client.go @@ -124,7 +124,7 @@ func normalizeResponse(res *http.Response, err error) (*http.Response, error) { var dnsError *net.DNSError if errors.As(err, &dnsError) { - return res, locale.WrapError(&UserNetworkError{}, "err_user_network_dns", "Request failed due to DNS error: {{.V0}}. {{.V1}}", err.Error(), locale.Tr("err_user_network_solution", constants.ForumsURL)) + return res, locale.WrapInputError(&UserNetworkError{}, "err_user_network_dns", "Request failed due to DNS error: {{.V0}}. {{.V1}}", err.Error(), locale.Tr("err_user_network_solution", constants.ForumsURL)) } // Due to Go's handling of these types of errors and due to Windows localizing the errors in question we have to rely on the `wsarecv:` keyword to capture a series diff --git a/internal/runbits/activation/activation.go b/internal/runbits/activation/activation.go index 865c7a9211..4cb7585648 100644 --- a/internal/runbits/activation/activation.go +++ b/internal/runbits/activation/activation.go @@ -39,7 +39,7 @@ func ActivateAndWait( } } - ve, err := venv.GetEnv(false, true, projectDir) + ve, err := venv.GetEnv(false, true, projectDir, proj.Namespace().String()) if err != nil { return locale.WrapError(err, "error_could_not_activate_venv", "Could not retrieve environment information.") } @@ -67,6 +67,8 @@ func ActivateAndWait( if err := ss.Activate(proj, cfg, out); err != nil { return locale.WrapError(err, "error_could_not_activate_subshell", "Could not activate a new subshell.") } + ss.TurnOnEcho() // temporarily re-enable echo while the subshell is active + defer ss.TurnOffEcho() a, err := process.NewActivation(cfg, os.Getpid()) if err != nil { diff --git a/pkg/cmdlets/auth/keypair.go b/internal/runbits/auth/keypair.go similarity index 100% rename from pkg/cmdlets/auth/keypair.go rename to internal/runbits/auth/keypair.go diff --git a/pkg/cmdlets/auth/login.go b/internal/runbits/auth/login.go similarity index 100% rename from pkg/cmdlets/auth/login.go rename to internal/runbits/auth/login.go diff --git a/pkg/cmdlets/auth/signup.go b/internal/runbits/auth/signup.go similarity index 100% rename from pkg/cmdlets/auth/signup.go rename to internal/runbits/auth/signup.go diff --git a/pkg/cmdlets/checker/checker.go b/internal/runbits/checker/checker.go similarity index 100% rename from pkg/cmdlets/checker/checker.go rename to internal/runbits/checker/checker.go diff --git a/pkg/cmdlets/checkout/checkout.go b/internal/runbits/checkout/checkout.go similarity index 99% rename from pkg/cmdlets/checkout/checkout.go rename to internal/runbits/checkout/checkout.go index 26bd63b2ab..5692e31ef3 100644 --- a/pkg/cmdlets/checkout/checkout.go +++ b/internal/runbits/checkout/checkout.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/cmdlets/git" + "github.com/ActiveState/cli/internal/runbits/git" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" diff --git a/pkg/cmdlets/checkout/path.go b/internal/runbits/checkout/path.go similarity index 95% rename from pkg/cmdlets/checkout/path.go rename to internal/runbits/checkout/path.go index 864693d499..1e6f33b3aa 100644 --- a/pkg/cmdlets/checkout/path.go +++ b/internal/runbits/checkout/path.go @@ -1,7 +1,6 @@ package checkout import ( - "os" "path/filepath" "github.com/ActiveState/cli/internal/constants" @@ -9,6 +8,7 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/pkg/project" ) @@ -22,7 +22,7 @@ func (r *Checkout) pathToUse(namespace *project.Namespaced, preferredPath string logging.Debug("No path provided, using default") // Get path from working directory - wd, err := os.Getwd() + wd, err := osutils.Getwd() if err != nil { return "", errs.Wrap(err, "Could not get working directory") } diff --git a/pkg/cmdlets/checkout/rationalize.go b/internal/runbits/checkout/rationalize.go similarity index 100% rename from pkg/cmdlets/checkout/rationalize.go rename to internal/runbits/checkout/rationalize.go diff --git a/pkg/cmdlets/commit/commit.go b/internal/runbits/commit/commit.go similarity index 100% rename from pkg/cmdlets/commit/commit.go rename to internal/runbits/commit/commit.go diff --git a/pkg/cmdlets/errors/errors.go b/internal/runbits/errors/errors.go similarity index 94% rename from pkg/cmdlets/errors/errors.go rename to internal/runbits/errors/errors.go index 3a3946e8cb..108eab2424 100644 --- a/pkg/cmdlets/errors/errors.go +++ b/internal/runbits/errors/errors.go @@ -67,17 +67,7 @@ func (o *OutputError) MarshalOutput(f output.Format) interface{} { } // Concatenate error tips - errorTips := []string{} - err := o.error - for _, err := range errs.Unpack(err) { - if v, ok := err.(ErrorTips); ok { - for _, tip := range v.ErrorTips() { - if !funk.Contains(errorTips, tip) { - errorTips = append(errorTips, tip) - } - } - } - } + errorTips := getErrorTips(o.error) errorTips = append(errorTips, locale.Tl("err_help_forum", "Ask For Help → [ACTIONABLE]{{.V0}}[/RESET]", constants.ForumsURL)) // Print tips @@ -92,6 +82,23 @@ func (o *OutputError) MarshalOutput(f output.Format) interface{} { return strings.Join(outLines, "\n") } +func getErrorTips(err error) []string { + errorTips := []string{} + for _, err := range errs.Unpack(err) { + v, ok := err.(ErrorTips) + if !ok { + continue + } + for _, tip := range v.ErrorTips() { + if funk.Contains(errorTips, tip) { + continue + } + errorTips = append(errorTips, tip) + } + } + return errorTips +} + func (o *OutputError) MarshalStructured(f output.Format) interface{} { var userFacingError errs.UserFacingError var message string @@ -100,7 +107,7 @@ func (o *OutputError) MarshalStructured(f output.Format) interface{} { } else { message = locale.JoinedErrorMessage(o.error) } - return output.StructuredError{message} + return output.StructuredError{message, getErrorTips(o.error)} } func trimError(msg string) string { diff --git a/pkg/cmdlets/git/git.go b/internal/runbits/git/git.go similarity index 98% rename from pkg/cmdlets/git/git.go rename to internal/runbits/git/git.go index 4ff0c248b7..044325a94d 100644 --- a/pkg/cmdlets/git/git.go +++ b/internal/runbits/git/git.go @@ -65,7 +65,7 @@ func (r *Repo) CloneProject(owner, name, path string, out output.Outputer, an an err = locale.WrapError(err, "err_clone_repo", "Could not clone repository with URL: {{.V0}}, error received: {{.V1}}.", *project.RepoURL, err.Error()) tipMsg := locale.Tl( "err_tip_git_ssh-add", - "If you are using an SSH key please ensure it's configured by running `[ACTIONABLE]ssh-add [/RESET]`.", + "If you are using an SSH key please ensure it's configured by running '[ACTIONABLE]ssh-add [/RESET]'.", ) return errs.AddTips(err, tipMsg) } diff --git a/pkg/cmdlets/git/test/integration/git_test.go b/internal/runbits/git/test/integration/git_test.go similarity index 89% rename from pkg/cmdlets/git/test/integration/git_test.go rename to internal/runbits/git/test/integration/git_test.go index ca691edaeb..07412744bc 100644 --- a/pkg/cmdlets/git/test/integration/git_test.go +++ b/internal/runbits/git/test/integration/git_test.go @@ -16,8 +16,8 @@ import ( "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/locale" + runbitsGit "github.com/ActiveState/cli/internal/runbits/git" "github.com/ActiveState/cli/internal/testhelpers/outputhelper" - gitlet "github.com/ActiveState/cli/pkg/cmdlets/git" "github.com/ActiveState/cli/pkg/project" ) @@ -79,7 +79,7 @@ func (suite *GitTestSuite) AfterTest(suiteName, testName string) { } func (suite *GitTestSuite) TestEnsureCorrectProject() { - err := gitlet.EnsureCorrectProject("test-owner", "test-project", filepath.Join(suite.dir, constants.ConfigFileName), "test-repo", outputhelper.NewCatcher(), blackhole.New()) + err := runbitsGit.EnsureCorrectProject("test-owner", "test-project", filepath.Join(suite.dir, constants.ConfigFileName), "test-repo", outputhelper.NewCatcher(), blackhole.New()) suite.NoError(err, "projectfile URL should contain owner and name") } @@ -88,7 +88,7 @@ func (suite *GitTestSuite) TestEnsureCorrectProject_Missmatch() { name := "bad-project" projectPath := filepath.Join(suite.dir, constants.ConfigFileName) actualCatcher := outputhelper.NewCatcher() - err := gitlet.EnsureCorrectProject(owner, name, projectPath, "test-repo", actualCatcher, blackhole.New()) + err := runbitsGit.EnsureCorrectProject(owner, name, projectPath, "test-repo", actualCatcher, blackhole.New()) suite.NoError(err) proj, err := project.Parse(projectPath) @@ -104,7 +104,7 @@ func (suite *GitTestSuite) TestEnsureCorrectProject_Missmatch() { func (suite *GitTestSuite) TestMoveFiles() { anotherDir := filepath.Join(suite.anotherDir, "anotherDir") - err := gitlet.MoveFiles(suite.dir, anotherDir) + err := runbitsGit.MoveFiles(suite.dir, anotherDir) suite.NoError(err, "should be able to move files wihout error") _, err = os.Stat(filepath.Join(anotherDir, constants.ConfigFileName)) @@ -122,7 +122,7 @@ func (suite *GitTestSuite) TestMoveFilesDirNoEmpty() { err = fileutils.Touch(filepath.Join(anotherDir, "file.txt")) suite.Require().NoError(err) - err = gitlet.MoveFiles(suite.dir, anotherDir) + err = runbitsGit.MoveFiles(suite.dir, anotherDir) expected := locale.WrapError(err, "err_git_verify_dir", "Could not verify destination directory") suite.EqualError(err, expected.Error()) } diff --git a/internal/runbits/runtime/progress/progress.go b/internal/runbits/runtime/progress/progress.go index 2a08b5ca74..786336b807 100644 --- a/internal/runbits/runtime/progress/progress.go +++ b/internal/runbits/runtime/progress/progress.go @@ -7,7 +7,6 @@ import ( "sync" "time" - "github.com/ActiveState/cli/internal/multilog" "github.com/go-openapi/strfmt" "github.com/vbauerster/mpb/v7" "golang.org/x/net/context" @@ -15,6 +14,7 @@ import ( "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/pkg/platform/runtime/artifact" "github.com/ActiveState/cli/pkg/platform/runtime/setup/events" diff --git a/internal/runbits/runtime/rationalize.go b/internal/runbits/runtime/rationalize.go index e48ad8cb4d..0039f3b159 100644 --- a/internal/runbits/runtime/rationalize.go +++ b/internal/runbits/runtime/rationalize.go @@ -4,16 +4,19 @@ import ( "errors" "strings" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" + "github.com/ActiveState/cli/pkg/platform/runtime/setup" "github.com/ActiveState/cli/pkg/project" ) func rationalizeError(auth *authentication.Auth, proj *project.Project, rerr *error) { var errNoMatchingPlatform *model.ErrNoMatchingPlatform + var errArtifactSetup *setup.ArtifactSetupErrors isUpdateErr := errs.Matches(*rerr, &ErrUpdate{}) switch { @@ -49,6 +52,21 @@ func rationalizeError(auth *authentication.Auth, proj *project.Project, rerr *er errNoMatchingPlatform.HostPlatform, errNoMatchingPlatform.HostArch)) } + // If there was an artifact download error, say so, rather than reporting a generic "could not + // update runtime" error. + case isUpdateErr && errors.As(*rerr, &errArtifactSetup): + for _, err := range errArtifactSetup.Errors() { + if !errs.Matches(err, &setup.ArtifactDownloadError{}) { + continue + } + *rerr = errs.WrapUserFacing(*rerr, + locale.Tl("err_runtime_setup_download", "Your runtime could not be installed or updated because one or more artifacts failed to download."), + errs.SetInput(), + errs.SetTips(locale.Tr("err_user_network_solution", constants.ForumsURL)), + ) + break // it only takes one download failure to report the runtime failure as due to download error + } + // If updating failed due to unidentified errors, and the user is not authenticated, add a tip suggesting that they authenticate as // this may be a private project. // Note since we cannot assert the actual error type we do not wrap this as user-facing, as we do not know what we're diff --git a/internal/runbits/runtime/runtime.go b/internal/runbits/runtime/runtime.go index 35c712d43e..aee2239fc9 100644 --- a/internal/runbits/runtime/runtime.go +++ b/internal/runbits/runtime/runtime.go @@ -6,6 +6,7 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/rtutils" "github.com/ActiveState/cli/internal/runbits" + "github.com/ActiveState/cli/internal/runbits/rationalize" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" rt "github.com/ActiveState/cli/pkg/platform/runtime" @@ -28,6 +29,10 @@ func NewFromProject( auth *authentication.Auth) (_ *rt.Runtime, rerr error) { defer rationalizeError(auth, proj, &rerr) + if proj.IsHeadless() { + return nil, rationalize.ErrHeadless + } + rti, err := rt.New(target.NewProjectTarget(proj, nil, trigger), an, svcModel, auth) if err == nil { return rti, nil diff --git a/internal/runners/activate/activate.go b/internal/runners/activate/activate.go index 04e2ac0a5c..45b012e10a 100644 --- a/internal/runners/activate/activate.go +++ b/internal/runners/activate/activate.go @@ -22,14 +22,14 @@ import ( "github.com/ActiveState/cli/internal/process" "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/runbits/activation" + "github.com/ActiveState/cli/internal/runbits/checker" + "github.com/ActiveState/cli/internal/runbits/checkout" "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/runbits/findproject" + "github.com/ActiveState/cli/internal/runbits/git" "github.com/ActiveState/cli/internal/runbits/runtime" "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/cli/internal/virtualenvironment" - "github.com/ActiveState/cli/pkg/cmdlets/checker" - "github.com/ActiveState/cli/pkg/cmdlets/checkout" - "github.com/ActiveState/cli/pkg/cmdlets/git" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/target" @@ -160,7 +160,7 @@ func (r *Activate) Run(params *ActivateParams) (rerr error) { } else { r.out.Notice(locale.Tl( "activate_default_optin_msg", - "To make this project always available for use without activating it in the future, run your activate command with the `[ACTIONABLE]--default[/RESET]` flag.", + "To make this project always available for use without activating it in the future, run your activate command with the '[ACTIONABLE]--default[/RESET]' flag.", )) } } @@ -190,7 +190,7 @@ func (r *Activate) Run(params *ActivateParams) (rerr error) { return errs.Wrap(err, "Unable to get local commit") } if commitID == "" { - err := locale.NewInputError("err_project_no_commit", "Your project does not have a commit ID, please run `state push` first.", model.ProjectURL(proj.Owner(), proj.Name(), "")) + err := locale.NewInputError("err_project_no_commit", "Your project does not have a commit ID, please run [ACTIONIABLE]'state push'[/RESET] first.", model.ProjectURL(proj.Owner(), proj.Name(), "")) return errs.AddTips(err, "Run → [ACTIONABLE]state push[/RESET] to create your project") } diff --git a/internal/runners/auth/auth.go b/internal/runners/auth/auth.go index 5a16385a8a..57efc2e415 100644 --- a/internal/runners/auth/auth.go +++ b/internal/runners/auth/auth.go @@ -6,7 +6,7 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/prompt" - authlet "github.com/ActiveState/cli/pkg/cmdlets/auth" + "github.com/ActiveState/cli/internal/runbits/auth" "github.com/ActiveState/cli/pkg/platform/authentication" ) @@ -84,18 +84,18 @@ func (a *Auth) Run(params *AuthParams) error { func (a *Auth) authenticate(params *AuthParams) error { if params.Prompt || params.Username != "" { - return authlet.AuthenticateWithInput(params.Username, params.Password, params.Totp, params.NonInteractive, a.Cfg, a.Outputer, a.Prompter, a.Auth) + return auth.AuthenticateWithInput(params.Username, params.Password, params.Totp, params.NonInteractive, a.Cfg, a.Outputer, a.Prompter, a.Auth) } if params.Token != "" { - return authlet.AuthenticateWithToken(params.Token, a.Auth) + return auth.AuthenticateWithToken(params.Token, a.Auth) } if params.NonInteractive { return locale.NewInputError("err_auth_needinput") } - return authlet.AuthenticateWithBrowser(a.Outputer, a.Auth, a.Prompter) + return auth.AuthenticateWithBrowser(a.Outputer, a.Auth, a.Prompter) } func (a *Auth) verifyAuthentication() error { diff --git a/internal/runners/auth/signup.go b/internal/runners/auth/signup.go index 66183b4f8b..f4f92aa340 100644 --- a/internal/runners/auth/signup.go +++ b/internal/runners/auth/signup.go @@ -5,7 +5,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/prompt" - authlet "github.com/ActiveState/cli/pkg/cmdlets/auth" + "github.com/ActiveState/cli/internal/runbits/auth" "github.com/ActiveState/cli/pkg/platform/authentication" ) @@ -26,8 +26,8 @@ func NewSignup(prime primeable) *Signup { func (s *Signup) Run(params *SignupParams) error { if s.Auth.Authenticated() { - return locale.NewInputError("err_auth_authenticated", "You are already authenticated as: {{.V0}}. You can log out by running `state auth logout`.", s.Auth.WhoAmI()) + return locale.NewInputError("err_auth_authenticated", "You are already authenticated as: {{.V0}}. You can log out by running '[ACTIONABLE]state auth logout[/RESET]'.", s.Auth.WhoAmI()) } - return authlet.SignupWithBrowser(s.Outputer, s.Auth, s.Prompter) + return auth.SignupWithBrowser(s.Outputer, s.Auth, s.Prompter) } diff --git a/internal/runners/checkout/checkout.go b/internal/runners/checkout/checkout.go index 2f5b18fec8..d5ad5319f5 100644 --- a/internal/runners/checkout/checkout.go +++ b/internal/runners/checkout/checkout.go @@ -8,11 +8,11 @@ import ( "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" + "github.com/ActiveState/cli/internal/runbits/checker" + "github.com/ActiveState/cli/internal/runbits/checkout" + "github.com/ActiveState/cli/internal/runbits/git" "github.com/ActiveState/cli/internal/runbits/runtime" "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/pkg/cmdlets/checker" - "github.com/ActiveState/cli/pkg/cmdlets/checkout" - "github.com/ActiveState/cli/pkg/cmdlets/git" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/setup" diff --git a/internal/runners/deploy/deploy.go b/internal/runners/deploy/deploy.go index 4de02d08fe..6dbf1cae6f 100644 --- a/internal/runners/deploy/deploy.go +++ b/internal/runners/deploy/deploy.go @@ -218,7 +218,7 @@ func (d *Deploy) configure(namespace project.Namespaced, rtTarget setup.Targeter // Write global env file d.output.Notice(fmt.Sprintf("Writing shell env file to %s\n", filepath.Join(rtTarget.Dir(), "bin"))) - err = d.subshell.SetupShellRcFile(binPath, env, &namespace) + err = d.subshell.SetupShellRcFile(binPath, env, &namespace, d.cfg) if err != nil { return locale.WrapError(err, "err_deploy_subshell_rc_file", "Could not create environment script.") } diff --git a/internal/runners/deploy/uninstall/uninstall.go b/internal/runners/deploy/uninstall/uninstall.go index 2ffde0e656..dae78fb129 100644 --- a/internal/runners/deploy/uninstall/uninstall.go +++ b/internal/runners/deploy/uninstall/uninstall.go @@ -63,12 +63,12 @@ func (u *Uninstall) Run(params *Params) error { var cwd string if path == "" { var err error - cwd, err = os.Getwd() + cwd, err = osutils.Getwd() if err != nil { return locale.WrapInputError( err, "err_deploy_uninstall_cannot_get_cwd", - "Cannot determine current working directory. Please supply `--path` argument") + "Cannot determine current working directory. Please supply '[ACTIONABLE]--path[/RESET]' argument") } path = cwd } diff --git a/internal/runners/events/log.go b/internal/runners/events/log.go index 2a54469bd2..60d852fab5 100644 --- a/internal/runners/events/log.go +++ b/internal/runners/events/log.go @@ -30,7 +30,7 @@ func NewLog(prime primeable) *EventLog { func (e *EventLog) Run(params *EventLogParams) error { pid := process.ActivationPID(e.cfg) if pid == -1 { - return locale.NewInputError("err_eventlog_pid", "Could not find parent process ID, make sure you're running this command from inside an activated state (run `state activate` first).") + return locale.NewInputError("err_eventlog_pid", "Could not find parent process ID, make sure you're running this command from inside an activated state (run '[ACTIONABLE]state activate[/RESET]' first).") } filepath := logging.FilePathFor(logging.FileNameFor(int(pid))) diff --git a/internal/runners/exec/exec.go b/internal/runners/exec/exec.go index 601828940e..14dd3eb2fb 100644 --- a/internal/runners/exec/exec.go +++ b/internal/runners/exec/exec.go @@ -135,7 +135,7 @@ func (s *Exec) Run(params *Params, args ...string) (rerr error) { } venv := virtualenvironment.New(rt) - env, err := venv.GetEnv(true, false, projectDir) + env, err := venv.GetEnv(true, false, projectDir, projectNamespace) if err != nil { return locale.WrapError(err, "err_exec_env", "Could not retrieve environment information for your runtime") } diff --git a/internal/runners/export/log.go b/internal/runners/export/log.go new file mode 100644 index 0000000000..b2ea102794 --- /dev/null +++ b/internal/runners/export/log.go @@ -0,0 +1,84 @@ +package export + +import ( + "path/filepath" + "regexp" + "sort" + "strconv" + + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/fileutils" + "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/output" +) + +type Log struct { + output.Outputer +} + +func NewLog(prime primeable) *Log { + return &Log{prime.Output()} +} + +type LogParams struct { + Prefix string + Index int +} + +type logFile struct { + Name string + Timestamp int +} + +var ErrInvalidLogIndex = errs.New("invalid index") +var ErrInvalidLogPrefix = errs.New("invalid log prefix") +var ErrLogNotFound = errs.New("log not found") + +func (l *Log) Run(params *LogParams) (rerr error) { + defer rationalizeError(&rerr) + + if params.Index < 0 { + return ErrInvalidLogIndex + } + if params.Prefix == "" { + params.Prefix = "state" + } + + // Fetch list of log files. + logDir := filepath.Dir(logging.FilePath()) + logFiles := fileutils.ListDirSimple(logDir, false) + + // Filter down the list based on the given prefix. + filteredLogFiles := []*logFile{} + regex, err := regexp.Compile(params.Prefix + `-\d+-(\d+)\.log`) + if err != nil { + return ErrInvalidLogPrefix + } + for _, file := range logFiles { + if regex.MatchString(file) { + timestamp, err := strconv.Atoi(regex.FindStringSubmatch(file)[1]) + if err != nil { + continue + } + filteredLogFiles = append(filteredLogFiles, &logFile{file, timestamp}) + } + } + + // Sort logs in ascending order by name (which include timestamp), not modification time. + sort.SliceStable(filteredLogFiles, func(i, j int) bool { + return filteredLogFiles[i].Timestamp > filteredLogFiles[j].Timestamp // sort ascending, not descending + }) + + if params.Index >= len(filteredLogFiles) { + return ErrLogNotFound + } + + l.Outputer.Print(output.Prepare( + filteredLogFiles[params.Index].Name, + &struct { + LogFile string `json:"logFile"` + }{filteredLogFiles[params.Index].Name}, + )) + + return nil +} diff --git a/internal/runners/export/privkey.go b/internal/runners/export/privkey.go index 06cc900964..c182b935f4 100644 --- a/internal/runners/export/privkey.go +++ b/internal/runners/export/privkey.go @@ -29,7 +29,7 @@ func (p *PrivateKey) Run(params *PrivateKeyParams) error { if !p.Auth.Authenticated() { return locale.NewInputError("err_export_privkey_requires_auth", - "You need to be authenticated to run this command. Authenticate by running [ACTIONABLE]`state auth --prompt`[/RESET].") + "You need to be authenticated to run this command. Authenticate by running '[ACTIONABLE]state auth --prompt[/RESET]'.") } filepath := keypairs.LocalKeyFilename(p.cfg.ConfigPath(), constants.KeypairLocalFileName) diff --git a/internal/runners/export/rationalize.go b/internal/runners/export/rationalize.go new file mode 100644 index 0000000000..e1402e1b70 --- /dev/null +++ b/internal/runners/export/rationalize.go @@ -0,0 +1,36 @@ +package export + +import ( + "errors" + + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/locale" +) + +func rationalizeError(err *error) { + switch { + // export log with invalid --index. + case errors.Is(*err, ErrInvalidLogIndex): + *err = errs.WrapUserFacing(*err, + locale.Tl("err_export_log_invalid_index", "Index must be >= 0"), + errs.SetInput(), + ) + + // export log with invalid . + case errors.Is(*err, ErrInvalidLogPrefix): + *err = errs.WrapUserFacing(*err, + locale.Tl("err_export_log_invalid_prefix", "Invalid log prefix"), + errs.SetInput(), + errs.SetTips( + locale.Tl("export_log_prefix_tip", "Try a prefix like 'state' or 'state-svc'"), + ), + ) + + // export log does not turn up a log file. + case errors.Is(*err, ErrLogNotFound): + *err = errs.WrapUserFacing(*err, + locale.Tl("err_export_log_out_of_bounds", "Log file not found"), + errs.SetInput(), + ) + } +} diff --git a/internal/runners/hello/hello_example.go b/internal/runners/hello/hello_example.go index bd78721b4b..63fd924b7e 100644 --- a/internal/runners/hello/hello_example.go +++ b/internal/runners/hello/hello_example.go @@ -78,7 +78,7 @@ func rationalizeError(err *error) { *err, locale.Tl("hello_err_no_project", "Cannot say hello because you are not in a project directory."), errs.SetTips( - locale.Tl("hello_suggest_checkout", "Try using [ACTIONABLE]`state checkout`[/RESET] first."), + locale.Tl("hello_suggest_checkout", "Try using '[ACTIONABLE]state checkout[/RESET]' first."), ), ) } diff --git a/internal/runners/history/history.go b/internal/runners/history/history.go index 55f7d26e49..0ba655b9e0 100644 --- a/internal/runners/history/history.go +++ b/internal/runners/history/history.go @@ -5,8 +5,8 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" + "github.com/ActiveState/cli/internal/runbits/commit" "github.com/ActiveState/cli/internal/runbits/commitmediator" - "github.com/ActiveState/cli/pkg/cmdlets/commit" "github.com/ActiveState/cli/pkg/platform/api/mono/mono_models" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" diff --git a/internal/runners/initialize/rationalize.go b/internal/runners/initialize/rationalize.go index 606fc6f3f7..bf3c9fd011 100644 --- a/internal/runners/initialize/rationalize.go +++ b/internal/runners/initialize/rationalize.go @@ -3,27 +3,47 @@ package initialize import ( "errors" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" + "github.com/ActiveState/cli/pkg/platform/runtime/setup" ) func rationalizeError(err *error) { - if err == nil { - return - } + var pcErr *bpModel.ProjectCreatedError + var errArtifactSetup *setup.ArtifactSetupErrors - pcErr := &bpModel.ProjectCreatedError{} - if !errors.As(*err, &pcErr) { + switch { + case err == nil: return - } - switch pcErr.Type { - case bpModel.AlreadyExistsErrorType: - *err = errs.NewUserFacing(locale.Tl("err_create_project_exists", "That project already exists."), errs.SetInput()) - case bpModel.ForbiddenErrorType: - *err = errs.NewUserFacing( - locale.Tl("err_create_project_forbidden", "You do not have permission to create that project"), - errs.SetInput(), - errs.SetTips(locale.T("err_init_authenticated"))) + + // Error creating project. + case errors.As(*err, &pcErr): + switch pcErr.Type { + case bpModel.AlreadyExistsErrorType: + *err = errs.NewUserFacing(locale.Tl("err_create_project_exists", "That project already exists."), errs.SetInput()) + case bpModel.ForbiddenErrorType: + *err = errs.NewUserFacing( + locale.Tl("err_create_project_forbidden", "You do not have permission to create that project"), + errs.SetInput(), + errs.SetTips(locale.T("err_init_authenticated"))) + } + + // If there was an artifact download error, say so, rather than reporting a generic "could not + // update runtime" error. + case errors.As(*err, &errArtifactSetup): + for _, serr := range errArtifactSetup.Errors() { + if !errs.Matches(serr, &setup.ArtifactDownloadError{}) { + continue + } + *err = errs.WrapUserFacing(*err, + locale.Tl("err_init_download", "Your project could not be created because one or more artifacts failed to download."), + errs.SetInput(), + errs.SetTips(locale.Tr("err_user_network_solution", constants.ForumsURL)), + ) + break // it only takes one download failure to report the runtime failure as due to download error + } + } } diff --git a/internal/runners/invite/invite.go b/internal/runners/invite/invite.go index 1c12743525..908e8a072f 100644 --- a/internal/runners/invite/invite.go +++ b/internal/runners/invite/invite.go @@ -53,7 +53,7 @@ func (i *invite) Run(params *Params, args []string) error { return locale.NewInputError("err_no_projectfile", "Must be in a project directory.") } if !i.auth.Authenticated() { - return locale.NewInputError("err_invite_not_logged_in", "You need to authenticate with [ACTIONABLE]`state auth`[/RESET] before you can invite new members.") + return locale.NewInputError("err_invite_not_logged_in", "You need to authenticate with '[ACTIONABLE]state auth[/RESET]' before you can invite new members.") } if len(args) > 1 { diff --git a/internal/runners/languages/languages.go b/internal/runners/languages/languages.go index 1381ede185..c043a88d50 100644 --- a/internal/runners/languages/languages.go +++ b/internal/runners/languages/languages.go @@ -37,11 +37,11 @@ func (l *Languages) Run() error { locale.WrapError( err, "err_languages_no_commitid", - "Your project runtime does not have a commit defined, you may need to run [ACTIONABLE]`state pull`[/RESET] first.", + "Your project runtime does not have a commit defined, you may need to run '[ACTIONABLE]state pull[/RESET]' first.", ), locale.Tl( "languages_no_commitid_help", - "Run → [ACTIONABLE]`state pull`[/RESET] to update your project", + "Run → '[ACTIONABLE]state pull[/RESET]' to update your project", ), ) } diff --git a/internal/runners/packages/import.go b/internal/runners/packages/import.go index c49036a7b3..f97e15f201 100644 --- a/internal/runners/packages/import.go +++ b/internal/runners/packages/import.go @@ -14,7 +14,6 @@ import ( "github.com/ActiveState/cli/internal/runbits" "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/platform/api" - gqlModel "github.com/ActiveState/cli/pkg/platform/api/graphql/model" "github.com/ActiveState/cli/pkg/platform/api/reqsimport" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" @@ -125,14 +124,6 @@ func (i *Import) Run(params *ImportRunParams) error { return errs.Wrap(err, "Could not import changeset") } - packageReqs := model.FilterCheckpointNamespace(reqs, model.NamespacePackage, model.NamespaceBundle) - if len(packageReqs) > 0 { - err = removeRequirements(i.Prompter, i.proj, params, packageReqs) - if err != nil { - return locale.WrapError(err, "err_cannot_remove_existing") - } - } - msg := locale.T("commit_reqstext_message") commitID, err := commitChangeset(i.proj, msg, changeset) if err != nil { @@ -142,26 +133,6 @@ func (i *Import) Run(params *ImportRunParams) error { return runbits.RefreshRuntime(i.auth, i.out, i.analytics, i.proj, commitID, true, target.TriggerImport, i.svcModel) } -func removeRequirements(conf Confirmer, project *project.Project, params *ImportRunParams, reqs []*gqlModel.Requirement) error { - if !params.NonInteractive { - msg := locale.T("confirm_remove_existing_prompt") - - defaultChoice := params.NonInteractive - confirmed, err := conf.Confirm(locale.T("confirm"), msg, &defaultChoice) - if err != nil { - return err - } - if !confirmed { - return locale.NewInputError("err_action_was_not_confirmed", "Cancelled Import.") - } - } - - removal := model.ChangesetFromRequirements(model.OperationRemoved, reqs) - msg := locale.T("commit_reqstext_remove_existing_message") - _, err := commitChangeset(project, msg, removal) - return err -} - func fetchImportChangeset(cp ChangesetProvider, file string, lang string) (model.Changeset, error) { data, err := ioutil.ReadFile(file) if err != nil { diff --git a/internal/runners/packages/info.go b/internal/runners/packages/info.go index ea0370d5c6..01aaa75ae7 100644 --- a/internal/runners/packages/info.go +++ b/internal/runners/packages/info.go @@ -222,7 +222,7 @@ func whatsNextMessages(name string, versions []string) []string { locale.Tl( "install_latest_version", "To install the latest version, run "+ - "[ACTIONABLE]`state install {{.V0}}`[/RESET]", + "'[ACTIONABLE]state install {{.V0}}[/RESET]'", name, ), ) @@ -236,7 +236,7 @@ func whatsNextMessages(name string, versions []string) []string { locale.Tl( "install_specific_version", "To install a specific version, run "+ - "[ACTIONABLE]`state install {{.V0}}@{{.V1}}[/RESET]`", + "'[ACTIONABLE]state install {{.V0}}@{{.V1}}[/RESET]'", name, version, ), ) @@ -248,7 +248,7 @@ func whatsNextMessages(name string, versions []string) []string { locale.Tl( "show_specific_version", "To view details for a specific version, run "+ - "[ACTIONABLE]`state info {{.V0}}@{{.V1}}`[/RESET]", + "'[ACTIONABLE]state info {{.V0}}@{{.V1}}[/RESET]'", name, version, ), ) diff --git a/internal/runners/packages/install.go b/internal/runners/packages/install.go index 282099daca..47e62b44fe 100644 --- a/internal/runners/packages/install.go +++ b/internal/runners/packages/install.go @@ -37,7 +37,8 @@ func NewInstall(prime primeable) *Install { } // Run executes the install behavior. -func (a *Install) Run(params InstallRunParams, nsType model.NamespaceType) error { +func (a *Install) Run(params InstallRunParams, nsType model.NamespaceType) (rerr error) { + defer rationalizeError(a.prime.Auth(), &rerr) logging.Debug("ExecuteInstall") return requirements.NewRequirementOperation(a.prime).ExecuteRequirementOperation( params.Package.Name(), diff --git a/internal/runners/packages/rationalize.go b/internal/runners/packages/rationalize.go new file mode 100644 index 0000000000..c93d174dc8 --- /dev/null +++ b/internal/runners/packages/rationalize.go @@ -0,0 +1,68 @@ +package packages + +import ( + "errors" + + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/locale" + "github.com/ActiveState/cli/internal/runbits/rationalize" + bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" + "github.com/ActiveState/cli/pkg/platform/authentication" + "github.com/ActiveState/cli/pkg/platform/runtime/buildexpression" +) + +func rationalizeError(auth *authentication.Auth, err *error) { + var commitError *bpModel.CommitError + var requirementNotFoundErr *buildexpression.RequirementNotFoundError + + switch { + case err == nil: + return + + // No activestate.yaml. + case errors.Is(*err, rationalize.ErrNoProject): + *err = errs.WrapUserFacing(*err, + locale.T("err_no_project"), + errs.SetInput(), + ) + + // Error staging a commit during install. + case errors.As(*err, &commitError): + switch commitError.Type { + case bpModel.NotFoundErrorType: + *err = errs.WrapUserFacing(*err, + locale.Tl("err_packages_not_found", "Could not make runtime changes because your project was not found."), + errs.SetInput(), + errs.SetTips(locale.T("tip_private_project_auth")), + ) + case bpModel.ForbiddenErrorType: + *err = errs.WrapUserFacing(*err, + locale.Tl("err_packages_forbidden", "Could not make runtime changes because you do not have permission to do so."), + errs.SetInput(), + errs.SetTips(locale.T("tip_private_project_auth")), + ) + case bpModel.HeadOnBranchMovedErrorType: + *err = errs.WrapUserFacing(*err, + locale.T("err_buildplanner_head_on_branch_moved"), + errs.SetInput(), + ) + case bpModel.NoChangeSinceLastCommitErrorType: + *err = errs.WrapUserFacing(*err, + locale.Tl("err_packages_exists", "That package is already installed."), + errs.SetInput(), + ) + default: + *err = errs.WrapUserFacing(*err, + locale.Tl("err_packages_buildplanner_error", "Could not make runtime changes due to the following error: {{.V0}}", commitError.Message), + errs.SetInput(), + ) + } + + // Requirement not found for uninstall. + case errors.As(*err, &requirementNotFoundErr): + *err = errs.WrapUserFacing(*err, + locale.Tr("err_remove_requirement_not_found", requirementNotFoundErr.Name), + errs.SetInput(), + ) + } +} diff --git a/internal/runners/packages/uninstall.go b/internal/runners/packages/uninstall.go index 4748f0602b..ff2f559ae2 100644 --- a/internal/runners/packages/uninstall.go +++ b/internal/runners/packages/uninstall.go @@ -1,8 +1,8 @@ package packages import ( - "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/runbits/rationalize" "github.com/ActiveState/cli/internal/runbits/requirements" bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" "github.com/ActiveState/cli/pkg/platform/model" @@ -24,10 +24,11 @@ func NewUninstall(prime primeable) *Uninstall { } // Run executes the uninstall behavior. -func (u *Uninstall) Run(params UninstallRunParams, nsType model.NamespaceType) error { +func (u *Uninstall) Run(params UninstallRunParams, nsType model.NamespaceType) (rerr error) { + defer rationalizeError(u.prime.Auth(), &rerr) logging.Debug("ExecuteUninstall") if u.prime.Project() == nil { - return locale.NewInputError("err_no_project") + return rationalize.ErrNoProject } return requirements.NewRequirementOperation(u.prime).ExecuteRequirementOperation( diff --git a/internal/runners/pull/pull.go b/internal/runners/pull/pull.go index bcb15ce183..bbed2c59b2 100644 --- a/internal/runners/pull/pull.go +++ b/internal/runners/pull/pull.go @@ -15,8 +15,8 @@ import ( "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/runbits" buildscriptRunbits "github.com/ActiveState/cli/internal/runbits/buildscript" + "github.com/ActiveState/cli/internal/runbits/commit" "github.com/ActiveState/cli/internal/runbits/commitmediator" - "github.com/ActiveState/cli/pkg/cmdlets/commit" bpModel "github.com/ActiveState/cli/pkg/platform/api/buildplanner/model" "github.com/ActiveState/cli/pkg/platform/api/mono/mono_models" "github.com/ActiveState/cli/pkg/platform/authentication" @@ -91,7 +91,7 @@ func (p *Pull) Run(params *PullParams) (rerr error) { } if p.project.BranchName() == "" { - return locale.NewError("err_pull_branch", "Your [NOTICE]activestate.yaml[/RESET] project field does not contain a branch. Please ensure you are using the latest version of the State Tool by running [ACTIONABLE]`state update`[/RESET] and then trying again.") + return locale.NewError("err_pull_branch", "Your [NOTICE]activestate.yaml[/RESET] project field does not contain a branch. Please ensure you are using the latest version of the State Tool by running '[ACTIONABLE]state update[/RESET]' and then trying again.") } // Determine the project to pull from @@ -258,7 +258,7 @@ func (p *Pull) mergeBuildScript(strategies *mono_models.MergeStrategies, remoteC } return locale.NewInputError( "err_build_script_merge", - "Unable to automatically merge build scripts. Please resolve conflicts manually in '{{.V0}}' and then run [ACTIONABLE]`state commit`[/RESET]", + "Unable to automatically merge build scripts. Please resolve conflicts manually in '{{.V0}}' and then run '[ACTIONABLE]state commit[/RESET]'", filepath.Join(p.project.Dir(), constants.BuildScriptFileName)) } diff --git a/internal/runners/revert/revert.go b/internal/runners/revert/revert.go index b6c72237c3..d07d3ee6b2 100644 --- a/internal/runners/revert/revert.go +++ b/internal/runners/revert/revert.go @@ -8,8 +8,8 @@ import ( "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/runbits" + "github.com/ActiveState/cli/internal/runbits/commit" "github.com/ActiveState/cli/internal/runbits/commitmediator" - "github.com/ActiveState/cli/pkg/cmdlets/commit" gqlmodel "github.com/ActiveState/cli/pkg/platform/api/graphql/model" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" diff --git a/internal/runners/run/run.go b/internal/runners/run/run.go index 6037ee2042..dc62091646 100644 --- a/internal/runners/run/run.go +++ b/internal/runners/run/run.go @@ -10,9 +10,9 @@ import ( "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" + "github.com/ActiveState/cli/internal/runbits/checker" "github.com/ActiveState/cli/internal/scriptrun" "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/pkg/cmdlets/checker" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" diff --git a/internal/runners/scripts/edit_test.go b/internal/runners/scripts/edit_test.go index 172afcd3a1..873fff30c7 100644 --- a/internal/runners/scripts/edit_test.go +++ b/internal/runners/scripts/edit_test.go @@ -13,6 +13,7 @@ import ( "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/locale" + "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/scriptfile" "github.com/ActiveState/cli/internal/testhelpers/outputhelper" "github.com/ActiveState/cli/pkg/project" @@ -115,7 +116,7 @@ func (suite *EditTestSuite) TestGetOpenCmd_EditorSet() { originalPath := os.Getenv("PATH") defer os.Setenv("PATH", originalPath) - wd, err := os.Getwd() + wd, err := osutils.Getwd() suite.NoError(err, "could not get current working directory") err = os.Setenv("PATH", wd) @@ -136,7 +137,7 @@ func (suite *EditTestSuite) TestGetOpenCmd_EditorSet_NotInPath() { } func (suite *EditTestSuite) TestGetOpenCmd_EditorSet_InvalidFilepath() { - wd, err := os.Getwd() + wd, err := osutils.Getwd() suite.NoError(err, "could not get current working directory") executeable := "someExecutable" @@ -154,7 +155,7 @@ func (suite *EditTestSuite) TestGetOpenCmd_EditorSet_NoExtensionWindows() { suite.T().Skip("the test for file extensions is only relevant for Windows") } - wd, err := os.Getwd() + wd, err := osutils.Getwd() suite.NoError(err, "could not get current working director") os.Setenv("EDITOR", filepath.Join(wd, "executable")) diff --git a/internal/runners/shell/shell.go b/internal/runners/shell/shell.go index 15c0d8f540..169b6386ba 100644 --- a/internal/runners/shell/shell.go +++ b/internal/runners/shell/shell.go @@ -1,8 +1,11 @@ package shell import ( + "os" + "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/config" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" @@ -87,7 +90,9 @@ func (u *Shell) Run(params *Params) error { } if process.IsActivated(u.config) { - return locale.NewInputError("err_shell_already_active", "", proj.NamespaceString(), proj.Dir()) + activatedProjectNamespace := os.Getenv(constants.ActivatedStateNamespaceEnvVarName) + activatedProjectDir := os.Getenv(constants.ActivatedStateEnvVarName) + return locale.NewInputError("err_shell_already_active", "", activatedProjectNamespace, activatedProjectDir) } u.out.Notice(locale.Tl("shell_project_statement", "", diff --git a/internal/runners/show/show.go b/internal/runners/show/show.go index faeda19abd..4e227ed472 100644 --- a/internal/runners/show/show.go +++ b/internal/runners/show/show.go @@ -211,7 +211,7 @@ func (s *Show) Run(params Params) error { remoteProject, err := model.LegacyFetchProjectByName(owner, projectName) if err != nil && errs.Matches(err, &model.ErrProjectNotFound{}) { - return locale.WrapError(err, "err_show_project_not_found", "Please run `state push` to synchronize this project with the ActiveState Platform.") + return locale.WrapError(err, "err_show_project_not_found", "Please run '[ACTIONABLE]state push[/RESET]' to synchronize this project with the ActiveState Platform.") } else if err != nil { return locale.WrapError(err, "err_show_get_project", "Could not get remote project details") } diff --git a/internal/runners/state/state.go b/internal/runners/state/state.go index 6e40f7d4d6..1b6ddc3849 100644 --- a/internal/runners/state/state.go +++ b/internal/runners/state/state.go @@ -11,7 +11,7 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/profile" - "github.com/ActiveState/cli/pkg/cmdlets/checker" + "github.com/ActiveState/cli/internal/runbits/checker" "github.com/ActiveState/cli/pkg/platform/model" ) diff --git a/internal/runners/tutorial/tutorial.go b/internal/runners/tutorial/tutorial.go index c1f9fb00dc..88a176a7b1 100644 --- a/internal/runners/tutorial/tutorial.go +++ b/internal/runners/tutorial/tutorial.go @@ -109,7 +109,7 @@ func (t *Tutorial) RunNewProject(params NewProjectParams) error { // Run state push if err := runbits.Invoke(t.outputer, "push"); err != nil { - return locale.WrapInputError(err, "err_tutorial_state_push", "Could not push project to ActiveState Platform, try manually running `state push` from your project directory at {{.V0}}.", dir) + return locale.WrapInputError(err, "err_tutorial_state_push", "Could not push project to ActiveState Platform, try manually running '[ACTIONABLE]state push[/RESET]' from your project directory at {{.V0}}.", dir) } // Print outro @@ -149,12 +149,12 @@ func (t *Tutorial) authFlow() error { case signIn: t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-in") if err := runbits.Invoke(t.outputer, "auth"); err != nil { - return locale.WrapInputError(err, "err_tutorial_signin", "Sign in failed. You could try manually signing in by running `state auth`.") + return locale.WrapInputError(err, "err_tutorial_signin", "Sign in failed. You could try manually signing in by running '[ACTIONABLE]state auth[/RESET]'.") } case signUpCLI: t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-up") if err := runbits.Invoke(t.outputer, "auth", "signup"); err != nil { - return locale.WrapInputError(err, "err_tutorial_signup", "Sign up failed. You could try manually signing up by running `state auth signup`.") + return locale.WrapInputError(err, "err_tutorial_signup", "Sign up failed. You could try manually signing up by running '[ACTIONABLE]state auth signup[/RESET]'.") } case signUpBrowser: t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-up-browser") @@ -164,7 +164,7 @@ func (t *Tutorial) authFlow() error { } t.outputer.Notice(locale.Tl("tutorial_signing_ready", "[NOTICE]Please sign in once you have finished signing up via your browser.[/RESET]")) if err := runbits.Invoke(t.outputer, "auth"); err != nil { - return locale.WrapInputError(err, "err_tutorial_signin", "Sign in failed. You could try manually signing in by running `state auth`.") + return locale.WrapInputError(err, "err_tutorial_signin", "Sign in failed. You could try manually signing in by running '[ACTIONABLE]state auth[/RESET]'.") } } diff --git a/internal/runners/use/use.go b/internal/runners/use/use.go index 7b42f8d641..c752af05dc 100644 --- a/internal/runners/use/use.go +++ b/internal/runners/use/use.go @@ -12,13 +12,13 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/prompt" + "github.com/ActiveState/cli/internal/runbits/checker" + "github.com/ActiveState/cli/internal/runbits/checkout" "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/runbits/findproject" + "github.com/ActiveState/cli/internal/runbits/git" "github.com/ActiveState/cli/internal/runbits/runtime" "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/pkg/cmdlets/checker" - "github.com/ActiveState/cli/pkg/cmdlets/checkout" - "github.com/ActiveState/cli/pkg/cmdlets/git" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/setup" diff --git a/internal/scriptrun/scriptrun.go b/internal/scriptrun/scriptrun.go index 70b90c4e24..9894a51c3e 100644 --- a/internal/scriptrun/scriptrun.go +++ b/internal/scriptrun/scriptrun.go @@ -85,7 +85,7 @@ func (s *ScriptRun) PrepareVirtualEnv() (rerr error) { venv := virtualenvironment.New(rt) projDir := filepath.Dir(s.project.Source().Path()) - env, err := venv.GetEnv(true, true, projDir) + env, err := venv.GetEnv(true, true, projDir, s.project.Namespace().String()) if err != nil { return errs.Wrap(err, "Could not get venv environment") } @@ -95,7 +95,7 @@ func (s *ScriptRun) PrepareVirtualEnv() (rerr error) { } // search the "clean" path first (PATHS that are set by venv) - env, err = venv.GetEnv(false, true, "") + env, err = venv.GetEnv(false, true, "", "") if err != nil { return errs.Wrap(err, "Could not get venv environment") } diff --git a/internal/subshell/bash/bash.go b/internal/subshell/bash/bash.go index 53ca17bdb7..08eb86e4a9 100644 --- a/internal/subshell/bash/bash.go +++ b/internal/subshell/bash/bash.go @@ -15,6 +15,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -134,9 +135,9 @@ func (v *SubShell) EnsureRcFileExists() error { } // SetupShellRcFile - subshell.SubShell -func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced) error { +func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced, cfg sscommon.Configurable) error { env = sscommon.EscapeEnv(env) - return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.sh"), "bashrc_global.sh", env, namespace) + return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.sh"), "bashrc_global.sh", env, namespace, cfg) } // SetEnv - see subshell.SetEnv @@ -219,3 +220,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.On() +} diff --git a/internal/subshell/cmd/cmd.go b/internal/subshell/cmd/cmd.go index 3601eb6f78..761c35e974 100644 --- a/internal/subshell/cmd/cmd.go +++ b/internal/subshell/cmd/cmd.go @@ -12,6 +12,7 @@ import ( "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -132,9 +133,9 @@ func (v *SubShell) EnsureRcFileExists() error { } // SetupShellRcFile - subshell.SubShell -func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced) error { +func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced, cfg sscommon.Configurable) error { env = sscommon.EscapeEnv(env) - return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.bat"), "config_global.bat", env, namespace) + return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.bat"), "config_global.bat", env, namespace, cfg) } // SetEnv - see subshell.SetEnv @@ -203,3 +204,11 @@ func (v *SubShell) IsActive() bool { func (v *SubShell) IsAvailable() bool { return runtime.GOOS == "windows" } + +func (v *SubShell) TurnOffEcho() { + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + termecho.On() +} diff --git a/internal/subshell/fish/fish.go b/internal/subshell/fish/fish.go index da50347327..c39c3ea687 100644 --- a/internal/subshell/fish/fish.go +++ b/internal/subshell/fish/fish.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" @@ -14,6 +15,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -116,9 +118,9 @@ func (v *SubShell) EnsureRcFileExists() error { } // SetupShellRcFile - subshell.SubShell -func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced) error { +func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced, cfg sscommon.Configurable) error { env = sscommon.EscapeEnv(env) - return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.fish"), "fishrc_global.fish", env, namespace) + return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.fish"), "fishrc_global.fish", env, namespace, cfg) } // SetEnv - see subshell.SetEnv @@ -195,3 +197,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.On() +} diff --git a/internal/subshell/sscommon/rcfile.go b/internal/subshell/sscommon/rcfile.go index 0a7f5ac828..3f8ef5a203 100644 --- a/internal/subshell/sscommon/rcfile.go +++ b/internal/subshell/sscommon/rcfile.go @@ -20,6 +20,7 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" + configMediator "github.com/ActiveState/cli/internal/mediators/config" "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/pkg/project" @@ -53,6 +54,10 @@ var ( } ) +func init() { + configMediator.RegisterOption(constants.PreservePs1ConfigKey, configMediator.Bool, configMediator.EmptyEvent, configMediator.EmptyEvent) +} + // Configurable defines an interface to store and get configuration data type Configurable interface { Set(string, interface{}) error @@ -73,9 +78,13 @@ func WriteRcFile(rcTemplateName string, path string, data RcIdentification, env } rcData := map[string]interface{}{ - "Start": data.Start, - "Stop": data.Stop, - "Env": env, + "Start": data.Start, + "Stop": data.Stop, + "Env": env, + "ActivatedEnv": constants.ActivatedStateEnvVarName, + "ConfigFile": constants.ConfigFileName, + "ActivatedNamespaceEnv": constants.ActivatedStateNamespaceEnvVarName, + "Default": data == DefaultID, } if err := CleanRcFile(path, data); err != nil { @@ -191,7 +200,7 @@ func CleanRcFile(path string, data RcIdentification) error { } // SetupShellRcFile create a rc file to activate a runtime (without a project being present) -func SetupShellRcFile(rcFileName, templateName string, env map[string]string, namespace *project.Namespaced) error { +func SetupShellRcFile(rcFileName, templateName string, env map[string]string, namespace *project.Namespaced, cfg Configurable) error { tpl, err := assets.ReadFileBytes(fmt.Sprintf("shells/%s", templateName)) if err != nil { return errs.Wrap(err, "Failed to read asset") @@ -208,8 +217,9 @@ func SetupShellRcFile(rcFileName, templateName string, env map[string]string, na var out bytes.Buffer rcData := map[string]interface{}{ - "Env": env, - "Project": projectValue, + "Env": env, + "Project": projectValue, + "PreservePs1": cfg.GetBool(constants.PreservePs1ConfigKey), } err = t.Execute(&out, rcData) if err != nil { @@ -322,6 +332,7 @@ func SetupProjectRcFile(prj *project.Project, templateName, ext string, env map[ "ExecName": constants.CommandName, "ActivatedMessage": colorize.ColorizedOrStrip(locale.Tl("project_activated", "[SUCCESS]✔ Project \"{{.V0}}\" Has Been Activated[/RESET]", prj.Namespace().String()), isConsole), + "PreservePs1": cfg.GetBool(constants.PreservePs1ConfigKey), } currExec := osutils.Executable() diff --git a/internal/subshell/sscommon/rcfile_test.go b/internal/subshell/sscommon/rcfile_test.go index bd01eb3833..3fe2d5d3f2 100644 --- a/internal/subshell/sscommon/rcfile_test.go +++ b/internal/subshell/sscommon/rcfile_test.go @@ -3,6 +3,7 @@ package sscommon import ( "fmt" "reflect" + "runtime" "strings" "testing" @@ -18,9 +19,9 @@ func fakeContents(before, contents, after string) string { if contents != "" { blocks = append( blocks, - fmt.Sprintf("# %s", constants.RCAppendDeployStartLine), + fmt.Sprintf("# %s", constants.RCAppendDefaultStartLine), contents, - fmt.Sprintf("# %s", constants.RCAppendDeployStopLine), + fmt.Sprintf("# %s", constants.RCAppendDefaultStopLine), ) } if after != "" { @@ -43,10 +44,25 @@ func TestWriteRcFile(t *testing.T) { path string env map[string]string } + + fish := fmt.Sprintf( + `set -xg PATH "foo:$PATH" +if test ! -z "$%s"; test -f "$%s/%s" + echo "State Tool is operating on project $%s, located at $%s" +end`, + constants.ActivatedStateEnvVarName, + constants.ActivatedStateEnvVarName, + constants.ConfigFileName, + constants.ActivatedStateNamespaceEnvVarName, + constants.ActivatedStateEnvVarName) + if runtime.GOOS == "windows" { + fish = strings.ReplaceAll(fish, "\n", "\r\n") + } + tests := []struct { name string args args - want error + want error wantContents string }{ { @@ -59,7 +75,7 @@ func TestWriteRcFile(t *testing.T) { }, }, nil, - fakeContents("", `set -xg PATH "foo:$PATH"`, ""), + fakeContents("", fish, ""), }, { "Write RC update", @@ -71,12 +87,12 @@ func TestWriteRcFile(t *testing.T) { }, }, nil, - fakeContents(strings.Join([]string{"before", "after"}, fileutils.LineEnd), `set -xg PATH "foo:$PATH"`, ""), + fakeContents(strings.Join([]string{"before", "after"}, fileutils.LineEnd), fish, ""), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := WriteRcFile(tt.args.rcTemplateName, tt.args.path, DeployID, tt.args.env); !reflect.DeepEqual(got, tt.want) { + if got := WriteRcFile(tt.args.rcTemplateName, tt.args.path, DefaultID, tt.args.env); !reflect.DeepEqual(got, tt.want) { t.Errorf("WriteRcFile() = %v, want %v", got, tt.want) } if !fileutils.FileExists(tt.args.path) { diff --git a/internal/subshell/subshell.go b/internal/subshell/subshell.go index 589a51f52e..4482fdf87a 100644 --- a/internal/subshell/subshell.go +++ b/internal/subshell/subshell.go @@ -71,7 +71,7 @@ type SubShell interface { EnsureRcFileExists() error // SetupShellRcFile writes a script or source-able file that updates the environment variables and sets the prompt - SetupShellRcFile(string, map[string]string, *project.Namespaced) error + SetupShellRcFile(string, map[string]string, *project.Namespaced, sscommon.Configurable) error // Shell returns an identifiable string representing the shell, eg. bash, zsh Shell() string @@ -84,6 +84,12 @@ type SubShell interface { // IsAvailable returns whether the shell is available on the system IsAvailable() bool + + // TurnOffEcho turns off input echoing. + TurnOffEcho() + + // TurnOnEcho turns on input echoing. + TurnOnEcho() } // New returns the subshell relevant to the current process, but does not activate it diff --git a/internal/subshell/tcsh/tcsh.go b/internal/subshell/tcsh/tcsh.go index 11b8908485..036d909890 100644 --- a/internal/subshell/tcsh/tcsh.go +++ b/internal/subshell/tcsh/tcsh.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/fileutils" @@ -13,6 +14,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -104,9 +106,9 @@ func (v *SubShell) EnsureRcFileExists() error { } // SetupShellRcFile - subshell.SubShell -func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced) error { +func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced, cfg sscommon.Configurable) error { env = sscommon.EscapeEnv(env) - return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.tcsh"), "tcsh_global.sh", env, namespace) + return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.tcsh"), "tcsh_global.sh", env, namespace, cfg) } // SetEnv - see subshell.SetEnv @@ -189,3 +191,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.On() +} diff --git a/internal/subshell/termecho/termecho.go b/internal/subshell/termecho/termecho.go new file mode 100644 index 0000000000..5fc36e17b8 --- /dev/null +++ b/internal/subshell/termecho/termecho.go @@ -0,0 +1,20 @@ +package termecho + +import ( + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/multilog" +) + +func Off() { + err := toggle(false) + if err != nil { + multilog.Error("Unable to turn off terminal echoing: %v", errs.JoinMessage(err)) + } +} + +func On() { + err := toggle(true) + if err != nil { + multilog.Error("Unable to turn off terminal echoing: %v", errs.JoinMessage(err)) + } +} diff --git a/internal/subshell/termecho/termecho_darwin.go b/internal/subshell/termecho/termecho_darwin.go new file mode 100644 index 0000000000..30dedb72d5 --- /dev/null +++ b/internal/subshell/termecho/termecho_darwin.go @@ -0,0 +1,6 @@ +package termecho + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA +const ioctlWriteTermios = unix.TIOCSETA diff --git a/internal/subshell/termecho/termecho_linux.go b/internal/subshell/termecho/termecho_linux.go new file mode 100644 index 0000000000..3bc972354e --- /dev/null +++ b/internal/subshell/termecho/termecho_linux.go @@ -0,0 +1,6 @@ +package termecho + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/internal/subshell/termecho/termecho_unix.go b/internal/subshell/termecho/termecho_unix.go new file mode 100644 index 0000000000..2b04217ec3 --- /dev/null +++ b/internal/subshell/termecho/termecho_unix.go @@ -0,0 +1,32 @@ +//go:build linux || darwin +// +build linux darwin + +package termecho + +import ( + "os" + + "github.com/ActiveState/cli/internal/errs" + "golang.org/x/sys/unix" +) + +func toggle(on bool) error { + fd := int(os.Stdin.Fd()) + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return errs.Wrap(err, "Could not get termios") + } + + newState := *termios // copy + if !on { + newState.Lflag &^= unix.ECHO + } else { + newState.Lflag |= unix.ECHO + } + err = unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState) + if err != nil { + return errs.Wrap(err, "Could not set termios") + } + + return nil +} diff --git a/internal/subshell/termecho/termecho_windows.go b/internal/subshell/termecho/termecho_windows.go new file mode 100644 index 0000000000..29d3c5eb6d --- /dev/null +++ b/internal/subshell/termecho/termecho_windows.go @@ -0,0 +1,30 @@ +package termecho + +import ( + "os" + + "github.com/ActiveState/cli/internal/errs" + "golang.org/x/sys/windows" +) + +func toggle(on bool) error { + fd := windows.Handle(os.Stdin.Fd()) + var mode uint32 + err := windows.GetConsoleMode(fd, &mode) + if err != nil { + return errs.Wrap(err, "Error calling GetConsoleMode") + } + + newMode := mode + if !on { + newMode &^= windows.ENABLE_ECHO_INPUT + } else { + newMode |= windows.ENABLE_ECHO_INPUT + } + err = windows.SetConsoleMode(fd, newMode) + if err != nil { + return errs.Wrap(err, "Error calling SetConsoleMode") + } + + return nil +} diff --git a/internal/subshell/zsh/zsh.go b/internal/subshell/zsh/zsh.go index 68b4599f3b..cbf0ae2f77 100644 --- a/internal/subshell/zsh/zsh.go +++ b/internal/subshell/zsh/zsh.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "github.com/ActiveState/cli/internal/constants" @@ -18,6 +19,7 @@ import ( "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/subshell/sscommon" + "github.com/ActiveState/cli/internal/subshell/termecho" "github.com/ActiveState/cli/pkg/project" ) @@ -135,9 +137,9 @@ func (v *SubShell) EnsureRcFileExists() error { } // SetupShellRcFile - subshell.SubShell -func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced) error { +func (v *SubShell) SetupShellRcFile(targetDir string, env map[string]string, namespace *project.Namespaced, cfg sscommon.Configurable) error { env = sscommon.EscapeEnv(env) - return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.zsh"), "zshrc_global.sh", env, namespace) + return sscommon.SetupShellRcFile(filepath.Join(targetDir, "shell.zsh"), "zshrc_global.sh", env, namespace, cfg) } // SetEnv - see subshell.SetEnv @@ -239,3 +241,17 @@ func (v *SubShell) IsAvailable() bool { } return fileutils.FileExists(rcFile) } + +func (v *SubShell) TurnOffEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.Off() +} + +func (v *SubShell) TurnOnEcho() { + if runtime.GOOS == "windows" { + return // not supported + } + termecho.On() +} diff --git a/internal/testhelpers/e2e/session.go b/internal/testhelpers/e2e/session.go index 93d22c0723..d80effa8eb 100644 --- a/internal/testhelpers/e2e/session.go +++ b/internal/testhelpers/e2e/session.go @@ -57,12 +57,13 @@ type Session struct { retainDirs bool createdProjects []*project.Namespaced // users created during session - users []string - t *testing.T - Exe string - SvcExe string - ExecutorExe string - spawned []*SpawnedCmd + users []string + t *testing.T + Exe string + SvcExe string + ExecutorExe string + spawned []*SpawnedCmd + ignoreLogErrors bool } var ( @@ -638,6 +639,10 @@ func (s *Session) Close() error { } } + if !s.ignoreLogErrors { + s.detectLogErrors() + } + return nil } @@ -742,12 +747,19 @@ func (s *Session) DebugLogsDump() string { return result } +// IgnoreLogErrors disables log error checking after the session closes. +// Normally, logged errors automatically cause test failures, so calling this is needed for tests +// with expected errors. +func (s *Session) IgnoreLogErrors() { + s.ignoreLogErrors = true +} + var errorOrPanicRegex = regexp.MustCompile(`(?:\[ERR:|Panic:)`) -func (s *Session) DetectLogErrors() { +func (s *Session) detectLogErrors() { for _, path := range s.LogFiles() { if contents := string(fileutils.ReadFileUnsafe(path)); errorOrPanicRegex.MatchString(contents) { - s.t.Errorf("Found error and/or panic in log file %s, contents:\n%s", path, contents) + s.t.Errorf("Found error and/or panic in log file %s\nIf this was expected, call session.IgnoreLogErrors() to avoid this check\nLog contents:\n%s", path, contents) } } } diff --git a/internal/updater/checker.go b/internal/updater/checker.go index 481efe9f2d..30d746c11e 100644 --- a/internal/updater/checker.go +++ b/internal/updater/checker.go @@ -2,6 +2,9 @@ package updater import ( "encoding/json" + "io/ioutil" + "net" + "net/http" "net/url" "os" "runtime" @@ -12,15 +15,11 @@ import ( "github.com/ActiveState/cli/internal/analytics/dimensions" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/httpreq" "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/retryhttp" "github.com/ActiveState/cli/internal/rtutils/ptr" ) -type httpGetter interface { - Get(string) ([]byte, int, error) -} - type Configurable interface { GetString(string) string Set(string, interface{}) error @@ -37,7 +36,7 @@ type Checker struct { cfg Configurable an analytics.Dispatcher apiInfoURL string - httpreq httpGetter + retryhttp *retryhttp.Client cache *AvailableUpdate done chan struct{} @@ -49,10 +48,10 @@ func NewDefaultChecker(cfg Configurable, an analytics.Dispatcher) *Checker { if url, ok := os.LookupEnv("_TEST_UPDATE_INFO_URL"); ok { infoURL = url } - return NewChecker(cfg, an, infoURL, httpreq.New()) + return NewChecker(cfg, an, infoURL, retryhttp.DefaultClient) } -func NewChecker(cfg Configurable, an analytics.Dispatcher, infoURL string, httpget httpGetter) *Checker { +func NewChecker(cfg Configurable, an analytics.Dispatcher, infoURL string, httpget *retryhttp.Client) *Checker { return &Checker{ cfg, an, @@ -94,54 +93,65 @@ func (u *Checker) getUpdateInfo(desiredChannel, desiredVersion string) (*Availab tag := u.cfg.GetString(CfgUpdateTag) infoURL := u.infoURL(tag, desiredVersion, desiredChannel, runtime.GOOS) logging.Debug("Getting update info: %s", infoURL) + + var info *AvailableUpdate + var err error var label string var msg string - res, code, err := u.httpreq.Get(infoURL) - if err != nil { - if code == 404 || strings.Contains(string(res), "Could not retrieve update info") { - // The above string match can be removed once https://www.pivotaltracker.com/story/show/179426519 is resolved + dims := &dimensions.Values{Version: ptr.To(desiredVersion)} // will change to info.Version if possible + + var resp *http.Response + if resp, err = u.retryhttp.Get(infoURL); err == nil { + var res []byte + res, err = ioutil.ReadAll(resp.Body) + switch { + // If there was an error reading the response. + case err != nil: + label = anaConst.UpdateLabelFailed + msg = anaConst.UpdateErrorFetch + err = errs.Wrap(err, "Could not read update info") + + // If the response was a 404 not found, or if the response body indicates failure. + // The above string match can be removed once https://www.pivotaltracker.com/story/show/179426519 is resolved + case resp.StatusCode == 404 || strings.Contains(string(res), "Could not retrieve update info"): logging.Debug("Update info 404s: %v", errs.JoinMessage(err)) label = anaConst.UpdateLabelUnavailable msg = anaConst.UpdateErrorNotFound - err = nil - } else if code == 403 || code == 503 { - // The request could not be satisfied or service is unavailable. This happens when Cloudflare - // blocks access, or the service is unavailable in a particular geographic location. + + // The request could not be satisfied or service is unavailable. This happens when Cloudflare + // blocks access, or the service is unavailable in a particular geographic location. + case resp.StatusCode == 403 || resp.StatusCode == 503: logging.Warning("Update info request blocked or service unavailable: %v", err) label = anaConst.UpdateLabelUnavailable msg = anaConst.UpdateErrorBlocked - err = nil - } else { - label = anaConst.UpdateLabelFailed - msg = anaConst.UpdateErrorFetch - err = errs.Wrap(err, "Could not fetch update info from %s", infoURL) + + // If all went well. + default: + if err = json.Unmarshal(res, &info); err == nil { + label = anaConst.UpdateLabelAvailable + dims.Version = ptr.To(info.Version) + } else { + label = anaConst.UpdateLabelFailed + msg = anaConst.UpdateErrorFetch + err = errs.Wrap(err, "Could not unmarshal update info: %s", res) + } } - u.an.EventWithLabel( - anaConst.CatUpdates, - anaConst.ActUpdateCheck, - label, - &dimensions.Values{ - Version: ptr.To(desiredVersion), - Error: ptr.To(msg), - }, - ) - return nil, err + } else { // retryhttp returned err + label = anaConst.UpdateLabelFailed + msg = anaConst.UpdateErrorFetch + err = errs.Wrap(err, "Could not fetch update info from %s", infoURL) + if e, ok := err.(net.Error); ok && e.Timeout() { + logging.Debug("Silencing network timeout error: %v", err) + err = errs.Silence(err) + } } - var info *AvailableUpdate - if err := json.Unmarshal(res, &info); err != nil { - return nil, errs.Wrap(err, "Could not unmarshal update info: %s", res) + if msg != "" { + dims.Error = ptr.To(msg) } - u.an.EventWithLabel( - anaConst.CatUpdates, - anaConst.ActUpdateCheck, - anaConst.UpdateLabelAvailable, - &dimensions.Values{ - Version: ptr.To(info.Version), - }, - ) + u.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateCheck, label, dims) - return info, nil + return info, err } diff --git a/internal/updater/fetcher.go b/internal/updater/fetcher.go index c7fca8e2ce..b748a584c2 100644 --- a/internal/updater/fetcher.go +++ b/internal/updater/fetcher.go @@ -3,37 +3,45 @@ package updater import ( "crypto/sha256" "fmt" + "io/ioutil" "github.com/ActiveState/cli/internal/analytics" anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/analytics/dimensions" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/fileutils" - "github.com/ActiveState/cli/internal/httpreq" "github.com/ActiveState/cli/internal/logging" + "github.com/ActiveState/cli/internal/retryhttp" "github.com/ActiveState/cli/internal/rtutils/ptr" ) const CfgUpdateTag = "update_tag" type Fetcher struct { - httpreq *httpreq.Client - an analytics.Dispatcher + retryhttp *retryhttp.Client + an analytics.Dispatcher } func NewFetcher(an analytics.Dispatcher) *Fetcher { - return &Fetcher{httpreq.New(), an} + return &Fetcher{retryhttp.DefaultClient, an} } func (f *Fetcher) Fetch(update *UpdateInstaller, targetDir string) error { logging.Debug("Fetching update: %s", update.url) - b, _, err := f.httpreq.Get(update.url) + resp, err := f.retryhttp.Get(update.url) if err != nil { msg := fmt.Sprintf("Fetch %s failed", update.url) f.analyticsEvent(update.AvailableUpdate.Version, msg) return errs.Wrap(err, msg) } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + msg := "Could not read response body" + f.analyticsEvent(update.AvailableUpdate.Version, msg) + return errs.Wrap(err, msg) + } + if err := verifySha(b, update.AvailableUpdate.Sha256); err != nil { msg := "Could not verify sha256" f.analyticsEvent(update.AvailableUpdate.Version, msg) diff --git a/internal/virtualenvironment/virtualenvironment.go b/internal/virtualenvironment/virtualenvironment.go index e6aca20fe3..055bc890fc 100644 --- a/internal/virtualenvironment/virtualenvironment.go +++ b/internal/virtualenvironment/virtualenvironment.go @@ -29,7 +29,7 @@ func New(runtime *runtime.Runtime) *VirtualEnvironment { } // GetEnv returns a map of the cumulative environment variables for all active virtual environments -func (v *VirtualEnvironment) GetEnv(inherit bool, useExecutors bool, projectDir string) (map[string]string, error) { +func (v *VirtualEnvironment) GetEnv(inherit bool, useExecutors bool, projectDir, namespace string) (map[string]string, error) { envMap := make(map[string]string) // Source runtime environment information @@ -44,6 +44,7 @@ func (v *VirtualEnvironment) GetEnv(inherit bool, useExecutors bool, projectDir if projectDir != "" { envMap[constants.ActivatedStateEnvVarName] = projectDir envMap[constants.ActivatedStateIDEnvVarName] = v.activationID + envMap[constants.ActivatedStateNamespaceEnvVarName] = namespace // Get project from explicitly defined configuration file configFile := filepath.Join(projectDir, constants.ConfigFileName) diff --git a/pkg/cmdlets/README.md b/pkg/cmdlets/README.md deleted file mode 100644 index 314a6c7637..0000000000 --- a/pkg/cmdlets/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Commandlets (cmdlets) are packages that contain logic used by our commands that do not belong in any other package. In -most cases this logic is contained in the command itself, but in some cases logic needs to be shared between commands, -this is where cmdlets come in. \ No newline at end of file diff --git a/pkg/platform/api/buildplanner/model/buildplan.go b/pkg/platform/api/buildplanner/model/buildplan.go index 1012847e68..f06a6871b8 100644 --- a/pkg/platform/api/buildplanner/model/buildplan.go +++ b/pkg/platform/api/buildplanner/model/buildplan.go @@ -302,6 +302,12 @@ func IsErrorResponse(errorType string) bool { errorType == ComitHasNoParentErrorType } +type CommitError struct { + Type string + Message string + *locale.LocalizedError // for legacy, non-user-facing error usages +} + func ProcessCommitError(commit *Commit, fallbackMessage string) error { if commit.Error == nil { return errs.New(fallbackMessage) @@ -309,15 +315,30 @@ func ProcessCommitError(commit *Commit, fallbackMessage string) error { switch commit.Type { case NotFoundErrorType: - return locale.NewInputError("err_buildplanner_commit_not_found", "Could not find commit, received message: {{.V0}}", commit.Message) + return &CommitError{ + commit.Type, commit.Message, + locale.NewInputError("err_buildplanner_commit_not_found", "Could not find commit, received message: {{.V0}}", commit.Message), + } case ParseErrorType: - return locale.NewInputError("err_buildplanner_parse_error", "The platform failed to parse the build expression, received message: {{.V0}}. Path: {{.V1}}", commit.Message, commit.ParseError.Path) + return &CommitError{ + commit.Type, commit.Message, + locale.NewInputError("err_buildplanner_parse_error", "The platform failed to parse the build expression, received message: {{.V0}}. Path: {{.V1}}", commit.Message, commit.ParseError.Path), + } case ForbiddenErrorType: - return locale.NewInputError("err_buildplanner_forbidden", "Operation forbidden: {{.V0}}, received message: {{.V1}}", commit.Operation, commit.Message) + return &CommitError{ + commit.Type, commit.Message, + locale.NewInputError("err_buildplanner_forbidden", "Operation forbidden: {{.V0}}, received message: {{.V1}}", commit.Operation, commit.Message), + } case HeadOnBranchMovedErrorType: - return errs.Wrap(locale.NewInputError("err_buildplanner_head_on_branch_moved", "The branch you're trying to update has changed remotely, please run '[ACTIONABLE]state pull[/RESET]'."), "received message: "+commit.Error.Message) + return errs.Wrap(&CommitError{ + commit.Type, commit.Error.Message, + locale.NewInputError("err_buildplanner_head_on_branch_moved"), + }, "received message: "+commit.Error.Message) case NoChangeSinceLastCommitErrorType: - return errs.Wrap(locale.NewInputError("err_buildplanner_no_change_since_last_commit", "No new changes to commit."), commit.Error.Message) + return errs.Wrap(&CommitError{ + commit.Type, commit.Error.Message, + locale.NewInputError("err_buildplanner_no_change_since_last_commit", "No new changes to commit."), + }, commit.Error.Message) default: return errs.New(fallbackMessage) } diff --git a/pkg/platform/api/buildplanner/request/commit.go b/pkg/platform/api/buildplanner/request/commit.go index 8cb832430e..a4f64c8d3a 100644 --- a/pkg/platform/api/buildplanner/request/commit.go +++ b/pkg/platform/api/buildplanner/request/commit.go @@ -1,9 +1,6 @@ package request -import "github.com/ActiveState/cli/internal/logging" - func BuildPlanByCommitID(commitID string) *buildPlanByCommitID { - logging.Debug("BuildPlanByCommitID") bp := &buildPlanByCommitID{map[string]interface{}{ "commitID": commitID, }} diff --git a/pkg/platform/api/buildplanner/request/project.go b/pkg/platform/api/buildplanner/request/project.go index 87c6063829..5233f252a9 100644 --- a/pkg/platform/api/buildplanner/request/project.go +++ b/pkg/platform/api/buildplanner/request/project.go @@ -1,9 +1,6 @@ package request -import "github.com/ActiveState/cli/internal/logging" - func BuildPlanByProject(organization, project, commitID string) *buildPlanByProject { - logging.Debug("BuildPlanByProject") bp := &buildPlanByProject{map[string]interface{}{ "organization": organization, "project": project, diff --git a/pkg/platform/authentication/auth.go b/pkg/platform/authentication/auth.go index 2823ac02b5..25357b48b6 100644 --- a/pkg/platform/authentication/auth.go +++ b/pkg/platform/authentication/auth.go @@ -203,8 +203,8 @@ func (s *Auth) AuthenticateWithModel(credentials *mono_models.Credentials) error if err != nil { tips := []string{ locale.Tl("relog_tip", "If you're having trouble authenticating try logging out and logging back in again."), - locale.Tl("logout_tip", "Logout with [ACTIONABLE]`state auth logout`[/RESET]."), - locale.Tl("logout_tip", "Login with [ACTIONABLE]`state auth`[/RESET]."), + locale.Tl("logout_tip", "Logout with '[ACTIONABLE]state auth logout[/RESET]'."), + locale.Tl("logout_tip", "Login with '[ACTIONABLE]state auth[/RESET]'."), } switch err.(type) { diff --git a/pkg/platform/model/cve.go b/pkg/platform/model/cve.go index cf37ae383a..a9b1141afa 100644 --- a/pkg/platform/model/cve.go +++ b/pkg/platform/model/cve.go @@ -23,7 +23,7 @@ func FetchProjectVulnerabilities(auth *authentication.Auth, org, project string) if !auth.Authenticated() { return nil, errs.AddTips( locale.NewError("cve_needs_authentication", "You need to be authenticated in order to access vulnerability information about your project."), - locale.Tl("auth_tip", "Run `state auth` to authenticate."), + locale.T("auth_tip"), ) } req := request.VulnerabilitiesByProject(org, project) @@ -48,7 +48,7 @@ func FetchCommitVulnerabilities(auth *authentication.Auth, commitID string) (*mo if !auth.Authenticated() { return nil, errs.AddTips( locale.NewError("cve_needs_authentication", "You need to be authenticated in order to access vulnerability information about your project."), - locale.Tl("auth_tip", "Run `state auth` to authenticate."), + locale.T("auth_tip"), ) } req := request.VulnerabilitiesByCommit(commitID) diff --git a/pkg/platform/runtime/buildexpression/buildexpression.go b/pkg/platform/runtime/buildexpression/buildexpression.go index 9aebc10e84..64a2e88886 100644 --- a/pkg/platform/runtime/buildexpression/buildexpression.go +++ b/pkg/platform/runtime/buildexpression/buildexpression.go @@ -767,6 +767,11 @@ func (e *BuildExpression) addRequirement(requirement model.Requirement) error { return nil } +type RequirementNotFoundError struct { + Name string + *locale.LocalizedError // for legacy non-user-facing error usages +} + func (e *BuildExpression) removeRequirement(requirement model.Requirement) error { requirementsNode, err := e.getRequirementsNode() if err != nil { @@ -789,7 +794,10 @@ func (e *BuildExpression) removeRequirement(requirement model.Requirement) error } if !found { - return locale.NewInputError("err_remove_requirement_not_found", "Could not remove requirement '[ACTIONABLE]{{.V0}}[/RESET]', because it does not exist.", requirement.Name) + return &RequirementNotFoundError{ + requirement.Name, + locale.NewInputError("err_remove_requirement_not_found", "", requirement.Name), + } } solveNode, err := e.getSolveNode() diff --git a/pkg/platform/runtime/setup/setup.go b/pkg/platform/runtime/setup/setup.go index 6da4b173bc..4f18b129ee 100644 --- a/pkg/platform/runtime/setup/setup.go +++ b/pkg/platform/runtime/setup/setup.go @@ -23,6 +23,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/multilog" + "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/proxyreader" "github.com/ActiveState/cli/internal/rollbar" "github.com/ActiveState/cli/internal/rtutils/ptr" @@ -109,6 +110,11 @@ type ProgressReportError struct { *errs.WrapperError } +type RuntimeInUseError struct { + *locale.LocalizedError + Processes map[string]int32 // exe path to process ID +} + type Targeter interface { CommitUUID() strfmt.UUID Name() string @@ -201,6 +207,15 @@ func (s *Setup) Update() (rerr error) { return locale.NewInputError("err_runtime_setup_root", "Cannot set up a runtime in the root directory. Please specify or run from a user-writable directory.") } + procs := osutils.GetProcessesInUse(ExecDir(s.target.Dir())) + if len(procs) > 0 { + list := []string{} + for exe, pid := range procs { + list = append(list, fmt.Sprintf(" - %s (process: %d)", exe, pid)) + } + return &RuntimeInUseError{locale.NewInputError("runtime_setup_in_use_err", "", strings.Join(list, "\n")), procs} + } + // Update all the runtime artifacts artifacts, err := s.updateArtifacts() if err != nil { diff --git a/pkg/projectfile/projectfile_test.go b/pkg/projectfile/projectfile_test.go index 6e1657145f..03ede03f45 100644 --- a/pkg/projectfile/projectfile_test.go +++ b/pkg/projectfile/projectfile_test.go @@ -14,6 +14,7 @@ import ( "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/locale" + "github.com/ActiveState/cli/internal/osutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" @@ -212,7 +213,7 @@ func TestGetProjectFilePath(t *testing.T) { root, err := environment.GetRootPath() assert.NoError(t, err, "Should detect root path") - cwd, err := os.Getwd() + cwd, err := osutils.Getwd() assert.NoError(t, err, "Should fetch cwd") defer os.Chdir(cwd) // restore os.Chdir(filepath.Join(root, "pkg", "projectfile", "testdata")) @@ -256,7 +257,7 @@ func TestGetProjectFilePath(t *testing.T) { func TestGet(t *testing.T) { root, err := environment.GetRootPath() assert.NoError(t, err, "Should detect root path") - cwd, _ := os.Getwd() + cwd, _ := osutils.Getwd() os.Chdir(filepath.Join(root, "pkg", "projectfile", "testdata")) config := Get() @@ -270,7 +271,7 @@ func TestGet(t *testing.T) { func TestGetActivated(t *testing.T) { root, _ := environment.GetRootPath() - cwd, _ := os.Getwd() + cwd, _ := osutils.Getwd() os.Chdir(filepath.Join(root, "pkg", "projectfile", "testdata")) config1 := Get() @@ -322,7 +323,7 @@ func TestNewProjectfile(t *testing.T) { assert.Error(t, err, "We don't accept blank paths") setCwd(t, "") - dir, err = os.Getwd() + dir, err = osutils.Getwd() assert.NoError(t, err, "Should be no error when getting the CWD") _, err = testOnlyCreateWithProjectURL("https://platform.activestate.com/xowner/xproject", dir) assert.Error(t, err, "Cannot create new project if existing as.yaml ...exists") diff --git a/test/integration/activate_int_test.go b/test/integration/activate_int_test.go index 603ad5d4ff..c37c3a81ed 100644 --- a/test/integration/activate_int_test.go +++ b/test/integration/activate_int_test.go @@ -178,7 +178,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivatePythonByHostOnly() { projectName := "Python-LinuxWorks" cp := ts.SpawnWithOpts( e2e.OptArgs("activate", "cli-integration-tests/"+projectName, "--path="+ts.Dirs.Work), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) if runtime.GOOS == "linux" { @@ -201,6 +201,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivatePythonByHostOnly() { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) } } + ts.IgnoreLogErrors() } func (suite *ActivateIntegrationTestSuite) assertCompletedStatusBarReport(snapshot string) { @@ -226,7 +227,7 @@ func (suite *ActivateIntegrationTestSuite) activatePython(version string, extraE cp := ts.SpawnWithOpts( e2e.OptArgs("activate", namespace), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), e2e.OptAppendEnv(extraEnv...), ) @@ -278,7 +279,7 @@ func (suite *ActivateIntegrationTestSuite) activatePython(version string, extraE cp = ts.SpawnCmdWithOpts( executor, e2e.OptArgs("-c", "import sys; print(sys.copyright);"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("ActiveState Software Inc.", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -296,7 +297,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivate_PythonPath() { cp := ts.SpawnWithOpts( e2e.OptArgs("activate", namespace), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) @@ -376,9 +377,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivatePerl() { cp := ts.SpawnWithOpts( e2e.OptArgs("activate", "ActiveState-CLI/Perl"), - e2e.OptAppendEnv( - "ACTIVESTATE_CLI_DISABLE_RUNTIME=false", - ), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Downloading", termtest.OptExpectTimeout(40*time.Second)) @@ -425,6 +424,7 @@ version: %s `, constants.BranchName, constants.Version)) ts.PrepareActiveStateYAML(content) + ts.PrepareCommitIdFile("59404293-e5a9-4fd0-8843-77cd4761b5b5") // Pull to ensure we have an up to date config file cp := ts.Spawn("pull") @@ -455,7 +455,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivate_NamespaceWins() { suite.Require().NoError(err) // Create the project file at the root of the temp dir - ts.PrepareProject("ActiveState-CLI/Python3", "") + ts.PrepareProject("ActiveState-CLI/Python3", "59404293-e5a9-4fd0-8843-77cd4761b5b5") // Pull to ensure we have an up to date config file cp := ts.Spawn("pull") @@ -512,7 +512,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivate_FromCache() { cp := ts.SpawnWithOpts( e2e.OptArgs("activate", "ActiveState-CLI/small-python", "--path", ts.Dirs.Work), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Downloading") cp.Expect("Installing") @@ -525,7 +525,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivate_FromCache() { // next activation is cached cp = ts.SpawnWithOpts( e2e.OptArgs("activate", "ActiveState-CLI/small-python", "--path", ts.Dirs.Work), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.ExpectInput(e2e.RuntimeSourcingTimeoutOpt) @@ -550,10 +550,10 @@ func (suite *ActivateIntegrationTestSuite) TestActivateCommitURL() { contents := fmt.Sprintf("project: https://platform.activestate.com/commit/%s\n", commitID) ts.PrepareActiveStateYAML(contents) - // Ensure we have the most up to date version of the project before activating cp := ts.Spawn("activate") - cp.Expect("Cannot activate a headless project", e2e.RuntimeSourcingTimeoutOpt) + cp.Expect("Cannot initialize runtime for a headless project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *ActivateIntegrationTestSuite) TestActivate_AlreadyActive() { @@ -665,7 +665,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivateArtifactsCached() { cp := ts.SpawnWithOpts( e2e.OptArgs("activate", namespace), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) @@ -694,7 +694,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivateArtifactsCached() { cp = ts.SpawnWithOpts( e2e.OptArgs("activate", namespace), e2e.OptAppendEnv( - "ACTIVESTATE_CLI_DISABLE_RUNTIME=false", + constants.DisableRuntime+"=false", "VERBOSE=true", // Necessary to assert "Fetched cached artifact" ), ) diff --git a/test/integration/analytics_int_test.go b/test/integration/analytics_int_test.go index e0807e5dae..9cf00bf195 100644 --- a/test/integration/analytics_int_test.go +++ b/test/integration/analytics_int_test.go @@ -496,6 +496,7 @@ func (suite *AnalyticsIntegrationTestSuite) TestInputError() { cp := ts.Spawn("clean", "uninstall", "badarg", "--mono") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() events := parseAnalyticsEvents(suite, ts) suite.assertSequentialEvents(events) diff --git a/test/integration/auth_int_test.go b/test/integration/auth_int_test.go index fb875fc8a7..3b42136177 100644 --- a/test/integration/auth_int_test.go +++ b/test/integration/auth_int_test.go @@ -78,6 +78,7 @@ func (suite *AuthIntegrationTestSuite) loginFlags(ts *e2e.Session, username stri cp := ts.Spawn(tagsuite.Auth, "--username", username, "--password", "bad-password") cp.Expect("You are not authorized, did you provide valid login credentials?") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *AuthIntegrationTestSuite) ensureLogout(ts *e2e.Session) { diff --git a/test/integration/bundle_int_test.go b/test/integration/bundle_int_test.go index d7a90bc98f..2d2f6c0a49 100644 --- a/test/integration/bundle_int_test.go +++ b/test/integration/bundle_int_test.go @@ -8,6 +8,7 @@ import ( "github.com/ActiveState/termtest" "github.com/stretchr/testify/suite" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" ) @@ -82,6 +83,7 @@ func (suite *BundleIntegrationTestSuite) TestBundle_project_invalid() { cp := ts.Spawn("bundles", "--namespace", "junk/junk") cp.Expect("The requested project junk does not exist under junk") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *BundleIntegrationTestSuite) TestBundle_searchSimple() { @@ -130,6 +132,7 @@ func (suite *BundleIntegrationTestSuite) TestBundle_searchWithExactTermWrongTerm cp := ts.Spawn("bundles", "search", "xxxUtilitiesxxx", "--exact-term") cp.Expect("No bundles in our catalog match") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *BundleIntegrationTestSuite) TestBundle_searchWithLang() { @@ -152,6 +155,7 @@ func (suite *BundleIntegrationTestSuite) TestBundle_searchWithWrongLang() { cp := ts.Spawn("bundles", "search", "Utilities", "--language=python") cp.Expect("No bundles in our catalog match") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *BundleIntegrationTestSuite) TestBundle_searchWithBadLang() { @@ -163,6 +167,7 @@ func (suite *BundleIntegrationTestSuite) TestBundle_searchWithBadLang() { cp := ts.Spawn("bundles", "search", "Utilities", "--language=bad") cp.Expect("Cannot obtain search") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *BundleIntegrationTestSuite) TestBundle_detached_operation() { @@ -197,6 +202,7 @@ func (suite *BundleIntegrationTestSuite) TestBundle_detached_operation() { cp := ts.Spawn("bundles", "install", "Utilities@0.7.6") cp.ExpectRe("(?:bundle updated|being built)") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() }) */ @@ -230,7 +236,7 @@ func (suite *BundleIntegrationTestSuite) TestJSON() { cp = ts.SpawnWithOpts( e2e.OptArgs("bundles", "install", "Testing", "--output", "json"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(`"name":"Testing"`, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -238,7 +244,7 @@ func (suite *BundleIntegrationTestSuite) TestJSON() { cp = ts.SpawnWithOpts( e2e.OptArgs("bundles", "uninstall", "Testing", "-o", "editor"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(`"name":"Testing"`, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) diff --git a/test/integration/checkout_int_test.go b/test/integration/checkout_int_test.go index e1984baeb5..e7a3a87ed6 100644 --- a/test/integration/checkout_int_test.go +++ b/test/integration/checkout_int_test.go @@ -33,7 +33,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckout() { // Checkout and verify. cp := ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/Python-3.9", "."), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) suite.Require().True(fileutils.DirExists(ts.Dirs.Work), "state checkout should have created "+ts.Dirs.Work) @@ -67,7 +67,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckout() { cp = ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/Python-3.9", "."), e2e.OptAppendEnv( - "ACTIVESTATE_CLI_DISABLE_RUNTIME=false", + constants.DisableRuntime+"=false", "VERBOSE=true", // Necessary to assert "Fetched cached artifact" ), ) @@ -92,10 +92,11 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutNonEmptyDir() { // Checkout and verify. cp := ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/Python3", tmpdir), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=true"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("already a project checked out at") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -105,7 +106,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutNonEmptyDir() { suite.Require().NoError(os.Remove(filepath.Join(tmpdir, constants.ConfigFileName))) cp = ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/Python3", tmpdir), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=true"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Checked out project") cp.ExpectExitCode(0) @@ -164,6 +165,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutWithFlags() { cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Python-3.9", branchPath, "--branch", "doesNotExist")) cp.Expect("This project has no branch with label matching doesNotExist") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *CheckoutIntegrationTestSuite) TestCheckoutCustomRTPath() { @@ -180,7 +182,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutCustomRTPath() { // Checkout and verify. cp := ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/Python3", fmt.Sprintf("--runtime-path=%s", customRTPath)), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) @@ -196,7 +198,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutCustomRTPath() { // Verify that state exec works with custom cache. cp = ts.SpawnWithOpts( e2e.OptArgs("exec", "python3", "--", "-c", "import sys;print(sys.executable)"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), e2e.OptWD(filepath.Join(ts.Dirs.Work, "Python3")), ) if runtime.GOOS == "windows" { @@ -217,6 +219,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutNotFound() { cp.Expect("does not exist under") // error cp.Expect("If this is a private project") // tip cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -236,6 +239,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutAlreadyCheckedOut() { cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/small-python")) cp.Expect("already a project checked out at") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func (suite *CheckoutIntegrationTestSuite) TestJSON() { @@ -246,6 +250,13 @@ func (suite *CheckoutIntegrationTestSuite) TestJSON() { cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/small-python", "-o", "json")) cp.ExpectExitCode(0) AssertValidJSON(suite.T(), cp) + + cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Bogus-Project-That-Doesnt-Exist", "-o", "json")) + cp.Expect("does not exist") // error + cp.Expect(`"tips":["If this is a private project`) // tip + cp.ExpectNotExitCode(0) + AssertValidJSON(suite.T(), cp) + ts.IgnoreLogErrors() } func (suite *CheckoutIntegrationTestSuite) TestCheckoutCaseInsensitive() { diff --git a/test/integration/commit_int_test.go b/test/integration/commit_int_test.go index d88a27b373..57bf60d0fe 100644 --- a/test/integration/commit_int_test.go +++ b/test/integration/commit_int_test.go @@ -32,7 +32,7 @@ func (suite *CommitIntegrationTestSuite) TestCommitManualBuildScriptMod() { "ActiveState-CLI/Commit-Test-A#7a1b416e-c17f-4d4a-9e27-cbad9e8f5655", ".", ), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Checked out", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) diff --git a/test/integration/condition_int_test.go b/test/integration/condition_int_test.go index b134c8f059..96115e083f 100644 --- a/test/integration/condition_int_test.go +++ b/test/integration/condition_int_test.go @@ -111,6 +111,7 @@ func (suite *ConditionIntegrationTestSuite) TestConditionSyntaxError() { ) cp.Expect(`not defined`) // for now we aren't passing the error up the chain, so invalid syntax will lead to empty result cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *ConditionIntegrationTestSuite) PrepareActiveStateYAML(ts *e2e.Session) { diff --git a/test/integration/config_int_test.go b/test/integration/config_int_test.go index 5cccd5c88e..c40231a849 100644 --- a/test/integration/config_int_test.go +++ b/test/integration/config_int_test.go @@ -21,6 +21,7 @@ func (suite *ConfigIntegrationTestSuite) TestConfig() { cp := ts.Spawn("config", "set", "invalid++", "value") cp.Expect("Invalid") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.Spawn("config", "set", constants.UnstableConfig, "true") cp.Expect("Successfully") diff --git a/test/integration/cve_int_test.go b/test/integration/cve_int_test.go index 6b05d49da3..c6de5b664e 100644 --- a/test/integration/cve_int_test.go +++ b/test/integration/cve_int_test.go @@ -97,6 +97,7 @@ func (suite *CveIntegrationTestSuite) TestCveInvalidProject() { cp.Expect("Found no project with specified organization and name") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func (suite *CveIntegrationTestSuite) TestJSON() { diff --git a/test/integration/deploy_int_test.go b/test/integration/deploy_int_test.go index e2505cb6aa..7da84609ce 100644 --- a/test/integration/deploy_int_test.go +++ b/test/integration/deploy_int_test.go @@ -37,7 +37,7 @@ func (suite *DeployIntegrationTestSuite) deploy(ts *e2e.Session, prj string, tar case "windows": cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", prj, "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) case "darwin": // On MacOS the command is the same as Linux, however some binaries @@ -45,13 +45,13 @@ func (suite *DeployIntegrationTestSuite) deploy(ts *e2e.Session, prj string, tar cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", prj, "--path", targetPath, "--force"), e2e.OptAppendEnv("SHELL=bash"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) default: cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", prj, "--path", targetPath), e2e.OptAppendEnv("SHELL=bash"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) } @@ -98,13 +98,14 @@ func (suite *DeployIntegrationTestSuite) TestDeployPerl() { "cmd.exe", e2e.OptArgs("/k", filepath.Join(targetPath, "bin", "shell.bat")), e2e.OptAppendEnv("PATHEXT=.COM;.EXE;.BAT;.LNK", "SHELL="), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) } else { cp = ts.SpawnCmdWithOpts( "/bin/bash", e2e.OptAppendEnv("PROMPT_COMMAND="), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false")) + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) cp.SendLine(fmt.Sprintf("source %s\n", filepath.Join(targetPath, "bin", "shell.sh"))) } @@ -174,13 +175,14 @@ func (suite *DeployIntegrationTestSuite) TestDeployPython() { "cmd.exe", e2e.OptArgs("/k", filepath.Join(targetPath, "bin", "shell.bat")), e2e.OptAppendEnv("PATHEXT=.COM;.EXE;.BAT;.LNK", "SHELL="), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) } else { cp = ts.SpawnCmdWithOpts( "/bin/bash", e2e.OptAppendEnv("PROMPT_COMMAND="), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false")) + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) cp.SendLine(fmt.Sprintf("source %s\n", filepath.Join(targetPath, "bin", "shell.sh"))) } @@ -242,7 +244,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployInstall() { func (suite *DeployIntegrationTestSuite) InstallAndAssert(ts *e2e.Session, targetPath string) { cp := ts.SpawnWithOpts( e2e.OptArgs("deploy", "install", "ActiveState-CLI/Python3", "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Installing Runtime") @@ -270,22 +272,23 @@ func (suite *DeployIntegrationTestSuite) TestDeployConfigure() { // Install step is required cp := ts.SpawnWithOpts( e2e.OptArgs("deploy", "configure", "ActiveState-CLI/Python3", "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("need to run the install step") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() suite.InstallAndAssert(ts, targetPath) if runtime.GOOS != "windows" { cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "configure", "ActiveState-CLI/Python3", "--path", targetPath), e2e.OptAppendEnv("SHELL=bash"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) } else { cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "configure", "ActiveState-CLI/Python3", "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) } @@ -296,7 +299,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployConfigure() { if runtime.GOOS == "windows" { cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "configure", "ActiveState-CLI/Python3", "--path", targetPath, "--user"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Configuring shell", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -346,21 +349,22 @@ func (suite *DeployIntegrationTestSuite) TestDeploySymlink() { // Install step is required cp := ts.SpawnWithOpts( e2e.OptArgs("deploy", "symlink", "ActiveState-CLI/Python3", "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("need to run the install step") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() suite.InstallAndAssert(ts, targetPath) if runtime.GOOS != "darwin" { cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "symlink", "ActiveState-CLI/Python3", "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) } else { cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "symlink", "ActiveState-CLI/Python3", "--path", targetPath, "--force"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) } @@ -387,15 +391,16 @@ func (suite *DeployIntegrationTestSuite) TestDeployReport() { // Install step is required cp := ts.SpawnWithOpts( e2e.OptArgs("deploy", "report", "ActiveState-CLI/Python3", "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("need to run the install step") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() suite.InstallAndAssert(ts, targetPath) cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "report", "ActiveState-CLI/Python3", "--path", targetPath), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Deployment Information") cp.Expect(targetID.String()) // expect bin dir @@ -426,7 +431,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployTwice() { cp := ts.SpawnWithOpts( e2e.OptArgs("deploy", "symlink", "ActiveState-CLI/Python3", "--path", targetPath), e2e.OptAppendEnv(fmt.Sprintf("PATH=%s", pathDir)), // Avoid conflicts - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.ExpectExitCode(0) @@ -439,7 +444,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployTwice() { cpx := ts.SpawnWithOpts( e2e.OptArgs("deploy", "symlink", "ActiveState-CLI/Python3", "--path", targetPath), e2e.OptAppendEnv(fmt.Sprintf("PATH=%s", pathDir)), // Avoid conflicts - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cpx.ExpectExitCode(0) } @@ -465,7 +470,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployUninstall() { // Uninstall deployed runtime. cp := ts.SpawnWithOpts( e2e.OptArgs("deploy", "uninstall", "--path", filepath.Join(ts.Dirs.Work, "target")), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Uninstall Deployed Runtime") cp.Expect("Successful") @@ -476,16 +481,17 @@ func (suite *DeployIntegrationTestSuite) TestDeployUninstall() { // Trying to uninstall again should fail cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "uninstall", "--path", filepath.Join(ts.Dirs.Work, "target")), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("no deployed runtime") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() suite.True(fileutils.IsDir(ts.Dirs.Work), "Work dir was unexpectedly deleted") // Trying to uninstall in a non-deployment directory should fail. cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "uninstall"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("no deployed runtime") cp.ExpectExitCode(1) @@ -494,7 +500,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployUninstall() { // Trying to uninstall in a non-deployment directory should not delete that directory. cp = ts.SpawnWithOpts( e2e.OptArgs("deploy", "uninstall", "--path", ts.Dirs.Work), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("no deployed runtime") cp.ExpectExitCode(1) diff --git a/test/integration/edit_int_test.go b/test/integration/edit_int_test.go index 683ad7ad6d..37357360c6 100644 --- a/test/integration/edit_int_test.go +++ b/test/integration/edit_int_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/suite" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/environment" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/testhelpers/e2e" @@ -79,7 +80,7 @@ func (suite *EditIntegrationTestSuite) TestEdit_NonInteractive() { } ts, env := suite.setup() defer ts.Close() - extraEnv := e2e.OptAppendEnv("ACTIVESTATE_NONINTERACTIVE=true") + extraEnv := e2e.OptAppendEnv(constants.NonInteractiveEnvVarName + "=true") cp := ts.SpawnWithOpts(e2e.OptArgs("scripts", "edit", "test-script"), env, extraEnv) cp.Expect("Watching file changes") diff --git a/test/integration/errors_int_test.go b/test/integration/errors_int_test.go index 787e750fb7..96cd7b4af1 100644 --- a/test/integration/errors_int_test.go +++ b/test/integration/errors_int_test.go @@ -23,6 +23,7 @@ func (suite *ErrorsIntegrationTestSuite) TestTips() { cp.Expect("Run →") cp.Expect("Ask For Help →") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *ErrorsIntegrationTestSuite) TestMultiError() { @@ -33,6 +34,7 @@ func (suite *ErrorsIntegrationTestSuite) TestMultiError() { cp := ts.Spawn("__test", "multierror") cp.ExpectRe(`\s+x error1\s+\s+x error2\s+x error3\s+x error4\s+█\s+Need More Help`) cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func TestErrorsIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/exec_int_test.go b/test/integration/exec_int_test.go index be1857cac9..9f5861661e 100644 --- a/test/integration/exec_int_test.go +++ b/test/integration/exec_int_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/suite" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" @@ -181,7 +182,7 @@ func (suite *ExecIntegrationTestSuite) TestExecWithPath() { cp = ts.SpawnWithOpts( e2e.OptArgs("exec", "--path", pythonDir, "which", "python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Operating on project ActiveState-CLI/Python-3.9", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectRe(regexp.MustCompile("cache/[0-9A-Fa-f]+/usr/bin/python3").String()) @@ -189,7 +190,7 @@ func (suite *ExecIntegrationTestSuite) TestExecWithPath() { cp = ts.SpawnWithOpts( e2e.OptArgs("exec", "echo", "python3", "--path", pythonDir, "--", "--path", "doesNotExist", "--", "extra"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("python3 --path doesNotExist -- extra") cp.ExpectExitCode(0) diff --git a/test/integration/executor_int_test.go b/test/integration/executor_int_test.go index ae181ed2b3..f65577d71a 100644 --- a/test/integration/executor_int_test.go +++ b/test/integration/executor_int_test.go @@ -35,7 +35,7 @@ func (suite *ExecutorIntegrationTestSuite) TestExecutorForwards() { cp = ts.SpawnWithOpts( e2e.OptArgs("shell", "ActiveState-CLI/Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectInput() @@ -62,7 +62,7 @@ func (suite *ExecutorIntegrationTestSuite) TestExecutorExitCode() { cp = ts.SpawnWithOpts( e2e.OptArgs("shell", "ActiveState-CLI/Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectInput() diff --git a/test/integration/export_int_test.go b/test/integration/export_int_test.go index 8f05f060bc..691fcc9cc5 100644 --- a/test/integration/export_int_test.go +++ b/test/integration/export_int_test.go @@ -1,9 +1,11 @@ package integration import ( + "path/filepath" "testing" "time" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" "github.com/ActiveState/termtest" @@ -55,6 +57,7 @@ func (suite *ExportIntegrationTestSuite) TestExport_InvalidPlatform() { ts.PrepareProject("cli-integration-tests/Export", "") cp := ts.Spawn("export", "recipe", "--platform", "junk") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *ExportIntegrationTestSuite) TestExport_ConfigDir() { @@ -65,6 +68,7 @@ func (suite *ExportIntegrationTestSuite) TestExport_ConfigDir() { ts.PrepareProject("cli-integration-tests/Export", "") cp := ts.Spawn("export", "config", "--filter", "junk") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *ExportIntegrationTestSuite) TestExport_Config() { @@ -87,7 +91,7 @@ func (suite *ExportIntegrationTestSuite) TestExport_Env() { ts.PrepareProject("ActiveState-CLI/Export", "5397f645-da8a-4591-b106-9d7fa99545fe") cp := ts.SpawnWithOpts( e2e.OptArgs("export", "env"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(`PATH: `, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -95,6 +99,24 @@ func (suite *ExportIntegrationTestSuite) TestExport_Env() { suite.Assert().NotContains(cp.Output(), "ACTIVESTATE_ACTIVATED") } +func (suite *ExportIntegrationTestSuite) TestLog() { + suite.OnlyRunForTags(tagsuite.Export) + ts := e2e.New(suite.T(), false) + defer ts.ClearCache() + + cp := ts.Spawn("export", "log") + cp.Expect(filepath.Join(ts.Dirs.Config, "logs")) + cp.ExpectRe(`state-\d+`) + cp.Expect(".log") + cp.ExpectExitCode(0) + + cp = ts.Spawn("export", "log", "state-svc") + cp.Expect(filepath.Join(ts.Dirs.Config, "logs")) + cp.ExpectRe(`state-svc-\d+`) + cp.Expect(".log") + cp.ExpectExitCode(0) +} + func (suite *ExportIntegrationTestSuite) TestJSON() { suite.OnlyRunForTags(tagsuite.Export, tagsuite.JSON) ts := e2e.New(suite.T(), false) @@ -107,13 +129,13 @@ func (suite *ExportIntegrationTestSuite) TestJSON() { cp = ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/small-python", "."), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.ExpectExitCode(0, e2e.RuntimeSourcingTimeoutOpt) cp = ts.SpawnWithOpts( e2e.OptArgs("export", "env", "-o", "json"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.ExpectExitCode(0, e2e.RuntimeSourcingTimeoutOpt) AssertValidJSON(suite.T(), cp) @@ -129,6 +151,12 @@ func (suite *ExportIntegrationTestSuite) TestJSON() { cp.Expect(`}`) cp.ExpectExitCode(0) // AssertValidJSON(suite.T(), cp) // recipe is too large to fit in terminal snapshot + + cp = ts.Spawn("export", "log", "-o", "json") + cp.Expect(`{"logFile":"`) + cp.Expect(`.log"}`) + cp.ExpectExitCode(0) + AssertValidJSON(suite.T(), cp) } func TestExportIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/fork_int_test.go b/test/integration/fork_int_test.go index caed6c377b..ea74b4e889 100644 --- a/test/integration/fork_int_test.go +++ b/test/integration/fork_int_test.go @@ -37,6 +37,7 @@ func (suite *ForkIntegrationTestSuite) TestFork() { cp = ts.Spawn("fork", "ActiveState-CLI/Python3", "--name", "Test-Python3", "--org", username) cp.Expect(`You already have a project with the name`) cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *ForkIntegrationTestSuite) TestFork_FailNameExists() { @@ -48,6 +49,7 @@ func (suite *ForkIntegrationTestSuite) TestFork_FailNameExists() { cp := ts.SpawnWithOpts(e2e.OptArgs("fork", "ActiveState-CLI/Python3", "--org", e2e.PersistentUsername)) cp.Expect("You already have a project with the name 'Python3'", termtest.OptExpectTimeout(30*time.Second)) cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func TestForkIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/hello_int_example_test.go b/test/integration/hello_int_example_test.go index 3953b4a0aa..8bddeb18ba 100644 --- a/test/integration/hello_int_example_test.go +++ b/test/integration/hello_int_example_test.go @@ -29,6 +29,7 @@ func (suite *HelloIntegrationTestSuite) TestHello() { cp = ts.Spawn("_hello", "") cp.Expect("Cannot say hello because no name was provided") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() cp = ts.Spawn("_hello", "Person", "--extra") cp.Expect("Project: ActiveState-CLI/small-python") diff --git a/test/integration/import_int_test.go b/test/integration/import_int_test.go index 059c06048f..d94a86eca3 100644 --- a/test/integration/import_int_test.go +++ b/test/integration/import_int_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "io/ioutil" "path/filepath" "runtime" @@ -49,6 +50,89 @@ func (suite *ImportIntegrationTestSuite) TestImport_detached() { cp.ExpectExitCode(0) } +const ( + reqsFileName = "requirements.txt" + reqsData = `Click==7.0 +Flask==1.1.1 +Flask-Cors==3.0.8 +itsdangerous==1.1.0 +Jinja2==2.10.3 +MarkupSafe==1.1.1 +packaging==20.3 +pyparsing==2.4.6 +six==1.14.0 +Werkzeug==0.15.6 +` + badReqsData = `Click==7.0 +garbage---<<001.X +six==1.14.0 +` + + complexReqsData = `coverage!=3.5 +docopt>=0.6.1 +Mopidy-Dirble>=1.1,<2 +requests>=2.2,<2.31.0 +urllib3>=1.21.1,<=1.26.5 +` +) + +func (suite *ImportIntegrationTestSuite) TestImport() { + suite.OnlyRunForTags(tagsuite.Import) + ts := e2e.New(suite.T(), false) + defer ts.Close() + + username, _ := ts.CreateNewUser() + namespace := fmt.Sprintf("%s/%s", username, "Python3") + + cp := ts.Spawn("init", "--language", "python", namespace, ts.Dirs.Work) + cp.Expect("successfully initialized") + cp.ExpectExitCode(0) + + reqsFilePath := filepath.Join(cp.WorkDirectory(), reqsFileName) + + suite.Run("invalid requirements.txt", func() { + ts.SetT(suite.T()) + ts.PrepareFile(reqsFilePath, badReqsData) + + cp := ts.Spawn("import", "requirements.txt") + cp.ExpectNotExitCode(0) + }) + + suite.Run("valid requirements.txt", func() { + ts.SetT(suite.T()) + ts.PrepareFile(reqsFilePath, reqsData) + + cp := ts.Spawn("import", "requirements.txt") + cp.ExpectExitCode(0) + + cp = ts.Spawn("push") + cp.ExpectExitCode(0) + + cp = ts.Spawn("import", "requirements.txt") + cp.Expect("already exists") + cp.ExpectNotExitCode(0) + }) + + suite.Run("complex requirements.txt", func() { + ts.SetT(suite.T()) + ts.PrepareFile(reqsFilePath, complexReqsData) + + cp := ts.Spawn("import", "requirements.txt") + cp.ExpectExitCode(0) + + cp = ts.Spawn("packages") + cp.Expect("coverage") + cp.Expect("docopt") + cp.Expect("Mopidy-Dirble") + cp.Expect("requests") + cp.Expect("Auto") // DX-2272 will change this to 2.30.0 + cp.Expect("urllib3") + cp.Expect("Auto") // DX-2272 will change this to 1.26.5 + cp.ExpectExitCode(0) + }) + ts.IgnoreLogErrors() +} + func TestImportIntegrationTestSuite(t *testing.T) { suite.Run(t, new(ImportIntegrationTestSuite)) } diff --git a/test/integration/info_int_test.go b/test/integration/info_int_test.go index f3d88fef41..2a91c321c7 100644 --- a/test/integration/info_int_test.go +++ b/test/integration/info_int_test.go @@ -44,6 +44,7 @@ func (suite *InfoIntegrationTestSuite) TestInfo_UnavailableVersion() { cp := ts.Spawn("info", "pylint@9.9.9", "--language", "python") cp.Expect("Could not find version 9.9.9 for package pylint") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *InfoIntegrationTestSuite) TestJSON() { @@ -62,6 +63,7 @@ func (suite *InfoIntegrationTestSuite) TestJSON() { cp.Expect(`"error":`) cp.ExpectExitCode(1) AssertValidJSON(suite.T(), cp) + ts.IgnoreLogErrors() } func TestInfoIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/init_int_test.go b/test/integration/init_int_test.go index 47b2a2dec9..eaca6a87ec 100644 --- a/test/integration/init_int_test.go +++ b/test/integration/init_int_test.go @@ -100,6 +100,7 @@ func (suite *InitIntegrationTestSuite) TestInit_NoLanguage() { cp := ts.Spawn("init", "test-user/test-project") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func (suite *InitIntegrationTestSuite) TestInit_InferLanguageFromUse() { @@ -115,7 +116,7 @@ func (suite *InitIntegrationTestSuite) TestInit_InferLanguageFromUse() { cp = ts.SpawnWithOpts( e2e.OptArgs("use", "Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) diff --git a/test/integration/install_int_test.go b/test/integration/install_int_test.go index 7f78ff5846..a2bfb29d7d 100644 --- a/test/integration/install_int_test.go +++ b/test/integration/install_int_test.go @@ -35,6 +35,7 @@ func (suite *InstallIntegrationTestSuite) TestInstall_InvalidCommit() { //cp.Expect("Could not find or read the commit file") // re-enable in DX-2307 cp.Expect("Invalid commit ID") // remove in DX-2307 cp.ExpectExitCode(1) + ts.IgnoreLogErrors() // Re-enable in DX-2307. //if strings.Count(cp.Snapshot(), " x ") != 1 { @@ -52,6 +53,7 @@ func (suite *InstallIntegrationTestSuite) TestInstall_NoMatches_NoAlternatives() cp.Expect("No results found for search term") cp.Expect("find alternatives") // This verifies no alternatives were found cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(strings.ReplaceAll(cp.Snapshot(), " x Failed", ""), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -68,6 +70,7 @@ func (suite *InstallIntegrationTestSuite) TestInstall_NoMatches_Alternatives() { cp.Expect("No results found for search term") cp.Expect("did you mean") // This verifies alternatives were found cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(strings.ReplaceAll(cp.Snapshot(), " x Failed", ""), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -83,6 +86,7 @@ func (suite *InstallIntegrationTestSuite) TestInstall_BuildPlannerError() { cp := ts.SpawnWithOpts(e2e.OptArgs("install", "trender@999.0"), e2e.OptAppendEnv(constants.DisableRuntime+"=true")) cp.Expect("Could not plan build, platform responded with", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(strings.ReplaceAll(cp.Snapshot(), " x Failed", ""), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) diff --git a/test/integration/install_scripts_int_test.go b/test/integration/install_scripts_int_test.go index 04314fb3cf..0253876346 100644 --- a/test/integration/install_scripts_int_test.go +++ b/test/integration/install_scripts_int_test.go @@ -93,14 +93,14 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall() { if runtime.GOOS != "windows" { cp = ts.SpawnCmdWithOpts( "bash", e2e.OptArgs(argsWithActive...), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.AppInstallDirOverrideEnvVarName, appInstallDir)), e2e.OptAppendEnv(fmt.Sprintf("%s=FOO", constants.OverrideSessionTokenEnvVarName)), ) } else { cp = ts.SpawnCmdWithOpts("powershell.exe", e2e.OptArgs(argsWithActive...), e2e.OptAppendEnv("SHELL="), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.AppInstallDirOverrideEnvVarName, appInstallDir)), e2e.OptAppendEnv(fmt.Sprintf("%s=FOO", constants.OverrideSessionTokenEnvVarName)), ) @@ -185,6 +185,7 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall_NonEmptyTarget() { // because of powershell. // Since we asserted the expected error above we don't need to go on a wild goose chase here. cp.ExpectExit() + ts.IgnoreLogErrors() } func (suite *InstallScriptsIntegrationTestSuite) TestInstall_VersionDoesNotExist() { @@ -206,6 +207,7 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall_VersionDoesNotExist cp.Expect("Could not download") } cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } // scriptPath returns the path to an installation script copied to targetDir, if useTestUrl is true, the install script is modified to download from the local test server instead diff --git a/test/integration/invite_int_test.go b/test/integration/invite_int_test.go index 5bb6276aa8..7f47ff1532 100644 --- a/test/integration/invite_int_test.go +++ b/test/integration/invite_int_test.go @@ -23,6 +23,7 @@ func (suite *InviteIntegrationTestSuite) TestInvite_NotAuthenticated() { cp := ts.Spawn("invite", "test-user@test.com") cp.Expect("You need to authenticate") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func TestInviteIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/languages_int_test.go b/test/integration/languages_int_test.go index 801947c3c0..e97788bb26 100644 --- a/test/integration/languages_int_test.go +++ b/test/integration/languages_int_test.go @@ -40,6 +40,7 @@ func (suite *LanguagesIntegrationTestSuite) TestLanguages_listNoCommitID() { cp := ts.Spawn("languages") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func (suite *LanguagesIntegrationTestSuite) TestLanguages_install() { @@ -59,6 +60,7 @@ func (suite *LanguagesIntegrationTestSuite) TestLanguages_install() { cp = ts.Spawn("languages", "install", "python") cp.Expect("Language: python is already installed") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.Spawn("languages", "install", "python@3.9.16") cp.Expect("Language added: python@3.9.16") diff --git a/test/integration/msg_int_test.go b/test/integration/msg_int_test.go index 546c66dc22..9889339cd9 100644 --- a/test/integration/msg_int_test.go +++ b/test/integration/msg_int_test.go @@ -28,9 +28,8 @@ func (suite *MsgIntegrationTestSuite) TestMessage_None() { cp.Expect("Usage:") cp.ExpectExitCode(0) - // Since message failures should fail silently without impacting the user we need to check the logs for any - // potential issues. - ts.DetectLogErrors() + // Note: since message failures should fail silently without impacting the user we need to check + // the logs for any potential issues. This is done automatically by ts.Close(). } func (suite *MsgIntegrationTestSuite) TestMessage_Basic() { @@ -167,6 +166,7 @@ func (suite *MsgIntegrationTestSuite) TestMessage_Basic_InterruptExit() { cp.ExpectExitCode(1) suite.Require().Contains(cp.Snapshot(), "This is a simple message") suite.Require().NotContains(cp.Output(), "Usage:") + ts.IgnoreLogErrors() } func TestMsgIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/offinstall_int_test.go b/test/integration/offinstall_int_test.go deleted file mode 100644 index 83d21b8cb5..0000000000 --- a/test/integration/offinstall_int_test.go +++ /dev/null @@ -1,509 +0,0 @@ -package integration - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "github.com/ActiveState/termtest" - "github.com/mholt/archiver" - - "github.com/ActiveState/cli/internal/analytics/client/sync/reporters" - anaConst "github.com/ActiveState/cli/internal/analytics/constants" - "github.com/ActiveState/cli/internal/constants" - "github.com/ActiveState/cli/internal/environment" - "github.com/ActiveState/cli/internal/exeutils" - "github.com/ActiveState/cli/internal/fileutils" - "github.com/ActiveState/cli/internal/offinstall" - "github.com/ActiveState/cli/internal/osutils" - "github.com/ActiveState/cli/internal/osutils/user" - "github.com/ActiveState/cli/internal/subshell/cmd" - "github.com/ActiveState/cli/internal/testhelpers/e2e" - "github.com/ActiveState/cli/internal/testhelpers/tagsuite" - "github.com/ActiveState/cli/pkg/project" - "github.com/google/uuid" -) - -type OffInstallIntegrationTestSuite struct { - tagsuite.Suite - - installerPath string - uninstallerPath string -} - -const ( - defaultOrg = "ActiveState-Test" - defaultProject = "IntegrationTest" - anotherProject = "Another-IntegrationTest" - defaultArtifactsPayload = "artifacts-payload" - anotherArtifactsPayload = "another-artifacts-payload" - defaultInstalledExecutable = "test-offline-install" - anotherInstalledExecutable = "test-another-offline-install" -) - -func (suite *OffInstallIntegrationTestSuite) TestInstallAndUninstall() { - suite.OnlyRunForTags(tagsuite.OffInstall) - - // Clean up env after test - if runtime.GOOS == "windows" { - env := cmd.NewCmdEnv(true) - origPath, err := env.Get("PATH") - suite.Require().NoError(err) - defer func() { - suite.Require().NoError(env.Set("PATH", origPath)) - }() - } else { - originalPath, exists := os.LookupEnv("PATH") - defer func() { - if !exists { - return - } - suite.Require().NoError(os.Setenv("PATH", originalPath)) - }() - } - - ts := e2e.New(suite.T(), true) - defer ts.Close() - - testReportFilename := filepath.Join(ts.Dirs.Config, reporters.TestReportFilename) - suite.Require().NoFileExists(testReportFilename) - - fmt.Printf("Work dir: %s\n", ts.Dirs.Work) - - suite.preparePayload(ts, defaultArtifactsPayload, defaultProject) - - defaultInstallParentDir, err := offinstall.DefaultInstallParentDir() - suite.Require().NoError(err) - defaultInstallDir := filepath.Join(defaultInstallParentDir, "IntegrationTest") - - env := []string{constants.DisableRuntime + "=false"} - if runtime.GOOS != "windows" { - env = append(env, "SHELL=bash") - } - namespace := project.NewNamespace(defaultOrg, defaultProject, "") - { // Install - suite.runOfflineInstaller(ts, defaultInstallDir, env) - - // Verify that our analytics event was fired - time.Sleep(2 * time.Second) // give time to let rtwatcher detect process has exited - events := parseAnalyticsEvents(suite, ts) - suite.Require().NotEmpty(events) - - heartbeat := suite.filterEvent(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat) - suite.assertDimensions(heartbeat) - - nDelete := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeDelete, anaConst.SrcOfflineInstaller) - if nDelete != 0 { - suite.FailNow(fmt.Sprintf("Expected 0 delete events, got %d, events:\n%#v", nDelete, events)) - } - - // Ensure shell env is updated - suite.assertShellUpdated(defaultInstallDir, namespace.String(), true, ts) - - // Ensure installation dir looks correct - suite.assertInstallDir(defaultInstallDir, defaultInstalledExecutable, true) - - // Run executable and validate that it has the relocated value - if runtime.GOOS == "windows" { - refreshEnv := filepath.Join(environment.GetRootPathUnsafe(), "test", "integration", "testdata", "tools", "refreshenv", "refreshenv.bat") - tp := ts.SpawnCmd("cmd", "/C", refreshEnv+" && "+defaultInstalledExecutable) - tp.Expect("TEST REPLACEMENT", termtest.OptExpectTimeout(5*time.Second)) - tp.ExpectExitCode(0) - } else { - // Disabled for now: DX-1307 - // tp = ts.SpawnCmd("bash") - // time.Sleep(1 * time.Second) // Give zsh a second to start -- can't use ExpectInput as it doesn't respect a custom HOME dir - // tp.Send("test-offline-install") - // tp.Expect("TEST REPLACEMENT", termtest.OptExpectTimeout(5*time.Second)) - // tp.Send("exit") - // tp.ExpectExitCode(0) - } - } - - { // Uninstall - tp := ts.SpawnCmdWithOpts( - suite.uninstallerPath, - e2e.OptArgs(defaultInstallDir), - e2e.OptAppendEnv(env...), - ) - tp.Expect("continue?") - tp.SendLine("y") - tp.Expect("Uninstall Complete", termtest.OptExpectTimeout(5*time.Second)) - tp.Expect("Press enter to exit") - tp.SendEnter() - tp.ExpectExitCode(0) - - // Ensure shell env is updated - suite.assertShellUpdated(defaultInstallDir, namespace.String(), false, ts) - - // Ensure installation files are removed - suite.assertInstallDir(defaultInstallDir, defaultInstalledExecutable, false) - - // Verify that our analytics event was fired - events := parseAnalyticsEvents(suite, ts) - suite.Require().NotEmpty(events) - nHeartbeat := countEvents(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcExecutor) - if nHeartbeat != 1 { - suite.FailNow(fmt.Sprintf("Expected 1 heartbeat event, got %d, events:\n%#v", nHeartbeat, events)) - } - del := suite.filterEvent(events, anaConst.CatRuntimeUsage, anaConst.ActRuntimeDelete) - suite.assertDimensions(del) - } -} - -func (suite *OffInstallIntegrationTestSuite) TestInstallNoPermission() { - suite.OnlyRunForTags(tagsuite.OffInstall) - - ts := e2e.New(suite.T(), true) - defer ts.Close() - - suite.preparePayload(ts, defaultArtifactsPayload, defaultProject) - - pathWithNoPermission := "/no-permission" - if runtime.GOOS == "windows" { - pathWithNoPermission = "C:\\Program Files\\No Permission" - } - - tp := ts.SpawnCmdWithOpts( - suite.installerPath, - e2e.OptArgs(pathWithNoPermission), - ) - tp.Expect("Please ensure that the directory is writeable", termtest.OptExpectTimeout(5*time.Second)) - tp.Expect("Press enter to exit", termtest.OptExpectTimeout(5*time.Second)) - tp.SendEnter() - tp.ExpectExitCode(1) -} - -func (suite *OffInstallIntegrationTestSuite) TestInstallMultiple() { - suite.OnlyRunForTags(tagsuite.OffInstall) - - // Clean up env after test - if runtime.GOOS == "windows" { - env := cmd.NewCmdEnv(true) - origPath, err := env.Get("PATH") - suite.Require().NoError(err) - defer func() { - suite.Require().NoError(env.Set("PATH", origPath)) - }() - } else { - originalPath, exists := os.LookupEnv("PATH") - defer func() { - if !exists { - return - } - suite.Require().NoError(os.Setenv("PATH", originalPath)) - }() - } - - ts := e2e.New(suite.T(), true) - defer ts.Close() - - testReportFilename := filepath.Join(ts.Dirs.Config, reporters.TestReportFilename) - suite.Require().NoFileExists(testReportFilename) - - suite.preparePayload(ts, defaultArtifactsPayload, defaultProject) - - defaultInstallParentDir, err := offinstall.DefaultInstallParentDir() - suite.Require().NoError(err) - firstInstallDir := filepath.Join(defaultInstallParentDir, "IntegrationTest") - secondInstallDir := filepath.Join(defaultInstallParentDir, "Another-IntegrationTest") - - firstNamespace := project.NewNamespace(defaultOrg, defaultProject, "") - secondNamespace := project.NewNamespace(defaultOrg, anotherProject, "") - - env := []string{constants.DisableRuntime + "=false"} - if runtime.GOOS != "windows" { - env = append(env, "SHELL=bash") - } - - // Run offline installer for first project - suite.runOfflineInstaller(ts, firstInstallDir, env) - - // Prepare new payload and run offline installer for second project - suite.preparePayload(ts, anotherArtifactsPayload, anotherProject) - suite.runOfflineInstaller(ts, secondInstallDir, env) - - // Assert first projects updates are still in place - suite.assertShellUpdated(firstInstallDir, firstNamespace.String(), true, ts) - suite.assertInstallDir(firstInstallDir, defaultInstalledExecutable, true) - - // Assert second projects updates are also in place - suite.assertShellUpdated(secondInstallDir, firstNamespace.String(), true, ts) - suite.assertInstallDir(secondInstallDir, anotherInstalledExecutable, true) - - // Uninstall first project - suite.runOfflineUninstaller(ts, firstInstallDir, env) - - // Assert first project's update are removed - suite.assertShellUpdated(firstInstallDir, firstNamespace.String(), false, ts) - - // Assert first project's installation files are removed - suite.assertInstallDir(firstInstallDir, defaultInstalledExecutable, false) - - // Uninstall second project - suite.runOfflineUninstaller(ts, secondInstallDir, env) - - // Assert second project's update are removed - suite.assertShellUpdated(secondInstallDir, secondNamespace.String(), false, ts) - - // Assert second project's installation files are removed - suite.assertInstallDir(secondInstallDir, anotherInstalledExecutable, false) -} - -func (suite *OffInstallIntegrationTestSuite) TestInstallTwice() { - suite.OnlyRunForTags(tagsuite.OffInstall) - - // Clean up env after test - if runtime.GOOS == "windows" { - env := cmd.NewCmdEnv(true) - origPath, err := env.Get("PATH") - suite.Require().NoError(err) - defer func() { - suite.Require().NoError(env.Set("PATH", origPath)) - }() - } else { - originalPath, exists := os.LookupEnv("PATH") - defer func() { - if !exists { - return - } - suite.Require().NoError(os.Setenv("PATH", originalPath)) - }() - } - - ts := e2e.New(suite.T(), true) - defer ts.Close() - - suite.preparePayload(ts, defaultArtifactsPayload, defaultProject) - - defaultInstallParentDir, err := offinstall.DefaultInstallParentDir() - suite.Require().NoError(err) - defaultInstallDir := filepath.Join(defaultInstallParentDir, "IntegrationTest") - - env := []string{constants.DisableRuntime + "=false"} - if runtime.GOOS != "windows" { - env = append(env, "SHELL=bash") - } - - suite.runOfflineInstaller(ts, defaultInstallDir, env) - - // Running offline installer again should not cause an error - tp := ts.SpawnCmdWithOpts( - suite.installerPath, - e2e.OptArgs(defaultInstallDir), - e2e.OptAppendEnv(env...), - ) - tp.Expect("Installation directory is not empty") - tp.Send("y") - tp.Expect("Do you accept the ActiveState Runtime Installer License Agreement? (y/N)", termtest.OptExpectTimeout(5*time.Second)) - tp.Send("y") - tp.Expect("Extracting", termtest.OptExpectTimeout(time.Second)) - tp.Expect("Installation complete") - tp.Expect("Press enter to exit") - tp.SendEnter() - tp.ExpectExitCode(0) - - // Uninstall - suite.runOfflineUninstaller(ts, defaultInstallDir, env) -} - -func (suite *OffInstallIntegrationTestSuite) runOfflineInstaller(ts *e2e.Session, installDir string, env []string) { - tp := ts.SpawnCmdWithOpts( - suite.installerPath, - e2e.OptArgs(installDir), - e2e.OptAppendEnv(env...), - ) - tp.Expect("Do you accept the ActiveState Runtime Installer License Agreement? (y/N)", termtest.OptExpectTimeout(5*time.Second)) - tp.Send("y") - tp.Expect("Extracting", termtest.OptExpectTimeout(time.Second)) - tp.Expect("Installing") - tp.Expect("Installation complete") - tp.Expect("Press enter to exit") - tp.SendEnter() - tp.ExpectExitCode(0) -} - -func (suite *OffInstallIntegrationTestSuite) runOfflineUninstaller(ts *e2e.Session, installDir string, env []string) { - tp := ts.SpawnCmdWithOpts( - suite.uninstallerPath, - e2e.OptArgs(installDir), - e2e.OptAppendEnv(env...), - ) - tp.Expect("continue?") - tp.SendLine("y") - tp.Expect("Uninstall Complete", termtest.OptExpectTimeout(5*time.Second)) - tp.Expect("Press enter to exit") - tp.SendEnter() - tp.ExpectExitCode(0) -} - -func (suite *OffInstallIntegrationTestSuite) preparePayload(ts *e2e.Session, payloadName, projectName string) { - root := environment.GetRootPathUnsafe() - - suffix := "-windows" - if runtime.GOOS != "windows" { - suffix = "-nix" - } - - // The payload is an artifact that contains mocked installation files - payloadPath := filepath.Join(root, "test", "integration", "testdata", "offline-install", payloadName+suffix, "artifact") - - // The asset path contains additional files that we want to embed into the executable, such as the license - assetPath := filepath.Join(root, "test", "integration", "testdata", "offline-install", "assets", projectName) - - // The payload archive is effectively double encrypted. We have the artifact itself, as well as the archive that - // wraps it. Our test code only has one artifact, but in the wild there can and most likely will be multiple - artifactMockPath := filepath.Join(ts.Dirs.Work, uuid.New().String()+".tar.gz") - payloadMockPath := filepath.Join(ts.Dirs.Work, "artifacts.tar.gz") - - // The paths of the installer and uninstaller - suite.installerPath = filepath.Join(ts.Dirs.Bin, "offline-installer"+exeutils.Extension) - suite.uninstallerPath = filepath.Join(ts.Dirs.Bin, "uninstall"+exeutils.Extension) - - archiver := archiver.NewTarGz() - { // Create the artifact archive - err := archiver.Archive(fileutils.ListFilesUnsafe(payloadPath), artifactMockPath) - suite.Require().NoError(err) - } - - { // Create the payload archive which contains the artifact - if fileutils.TargetExists(payloadMockPath) { - err := os.RemoveAll(payloadMockPath) - suite.Require().NoError(err) - } - err := archiver.Archive([]string{artifactMockPath}, payloadMockPath) - suite.Require().NoError(err) - } - - { // Use a distinct copy of the installer to test with - err := fileutils.CopyFile(filepath.Join(root, "build", "offline", "offline-installer"+exeutils.Extension), suite.installerPath) - suite.Require().NoError(err) - } - - { // Use a distinct copy of the uninstaller to test with - err := fileutils.CopyFile(filepath.Join(root, "build", "offline", "uninstall"+exeutils.Extension), suite.uninstallerPath) - suite.Require().NoError(err) - } - - // Copy all assets to same dir so gozip doesn't include their relative or absolute paths - buildPath := filepath.Join(ts.Dirs.Work, "build") - suite.Require().NoError(fileutils.MkdirUnlessExists(buildPath)) - suite.Require().NoError(fileutils.CopyMultipleFiles(map[string]string{ - payloadMockPath: filepath.Join(buildPath, filepath.Base(payloadMockPath)), - filepath.Join(assetPath, "installer_config.json"): filepath.Join(buildPath, "installer_config.json"), - filepath.Join(assetPath, "LICENSE.txt"): filepath.Join(buildPath, "LICENSE.txt"), - suite.uninstallerPath: filepath.Join(buildPath, filepath.Base(suite.uninstallerPath)), - })) - - // Append our assets to the installer executable - tp := ts.SpawnCmdWithOpts("gozip", - e2e.OptWD(buildPath), - e2e.OptArgs( - "-c", suite.installerPath, - filepath.Base(payloadMockPath), - "installer_config.json", - "LICENSE.txt", - filepath.Base(suite.uninstallerPath), - ), - ) - tp.ExpectExitCode(0) - - suite.Require().NoError(os.Chmod(suite.installerPath, 0775)) // ensure file is executable - suite.Require().NoError(os.Chmod(suite.uninstallerPath, 0775)) // ensure file is executable -} - -func (suite *OffInstallIntegrationTestSuite) assertShellUpdated(dir, namespace string, exists bool, ts *e2e.Session) { - if runtime.GOOS != "windows" { - // Test bashrc - homeDir, err := user.HomeDir() - suite.Require().NoError(err) - - fname := ".bashrc" - if runtime.GOOS == "darwin" { - fname = ".bash_profile" - } - - assert := suite.Contains - if !exists { - assert = suite.NotContains - } - - fpath := filepath.Join(homeDir, fname) - rcContents := fileutils.ReadFileUnsafe(fpath) - assert(string(rcContents), fmt.Sprintf("%s-%s", constants.RCAppendOfflineInstallStartLine, namespace), fpath) - assert(string(rcContents), fmt.Sprintf("%s-%s", constants.RCAppendOfflineInstallStopLine, namespace), fpath) - assert(string(rcContents), dir) - } else { - // It seems there is a race condition with updating the registry and asserting it was updated - time.Sleep(time.Second) - - // Test registry - isAdmin, err := osutils.IsAdmin() - suite.Require().NoError(err) - regKey := `HKCU\Environment` - if isAdmin { - regKey = `HKLM\SYSTEM\ControlSet001\Control\Session Manager\Environment` - } - out, err := exec.Command("reg", "query", regKey, "/v", "Path").Output() - suite.Require().NoError(err) - - assert := strings.Contains - if !exists { - assert = func(s, substr string) bool { - return !strings.Contains(s, substr) - } - } - - // we need to look for the short and the long version of the target PATH, because Windows translates between them arbitrarily - shortPath, _ := fileutils.GetShortPathName(dir) - longPath, _ := fileutils.GetLongPathName(dir) - if !assert(string(out), shortPath) && !assert(string(out), longPath) && !assert(string(out), dir) { - suite.T().Errorf("registry PATH \"%s\" validation failed for \"%s\", \"%s\" or \"%s\", should contain: %v", out, dir, shortPath, longPath, exists) - } - } -} - -func (suite *OffInstallIntegrationTestSuite) filterEvent(events []reporters.TestLogEntry, category string, action string) reporters.TestLogEntry { - ev := filterEvents(events, func(e reporters.TestLogEntry) bool { - return e.Category == category && e.Action == action - }) - suite.Require().Len(ev, 1) - return ev[0] -} - -func (suite *OffInstallIntegrationTestSuite) assertInstallDir(dir, executable string, exists bool) { - assert := suite.Require().FileExists - if !exists { - assert = suite.Require().NoFileExists - } - if runtime.GOOS == "windows" { - assert(filepath.Join(dir, "bin", fmt.Sprintf("%s.bat", executable))) - } else { - assert(filepath.Join(dir, "bin", fmt.Sprintf("%s", executable))) - } - if runtime.GOOS == "windows" { - assert(filepath.Join(dir, "bin", "shell.bat")) - } - assert(filepath.Join(dir, "LICENSE.txt")) -} - -func (suite *OffInstallIntegrationTestSuite) assertDimensions(event reporters.TestLogEntry) { - evdbg, err := json.Marshal(event) - suite.Require().NoError(err) - dbg := fmt.Sprintf("Event: %s", string(evdbg)) - suite.Require().NotNil(event.Dimensions.ProjectNameSpace, dbg) - suite.Require().NotNil(event.Dimensions.CommitID, dbg) - suite.Require().Equal("ActiveState-Test/IntegrationTest", *event.Dimensions.ProjectNameSpace) - suite.Require().Equal("00000000-0000-0000-0000-000000000000", *event.Dimensions.CommitID) -} - -func TestOffInstallIntegrationTestSuite(t *testing.T) { - t.Skip("Skipping offline installer tests as they will soon live in a separate repo") - // suite.Run(t, new(OffInstallIntegrationTestSuite)) -} diff --git a/test/integration/package_int_test.go b/test/integration/package_int_test.go index 77f4e4c4c4..280337dcb0 100644 --- a/test/integration/package_int_test.go +++ b/test/integration/package_int_test.go @@ -4,6 +4,7 @@ import ( "fmt" "path/filepath" "runtime" + "strings" "testing" "time" @@ -89,6 +90,7 @@ func (suite *PackageIntegrationTestSuite) TestPackages_project_invalid() { cp := ts.Spawn("packages", "--namespace", "junk/junk") cp.Expect("The requested project junk does not exist under junk") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestPackage_listingWithCommitValid() { @@ -114,6 +116,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_listingWithCommitInvalid() cp := ts.Spawn("packages", "--commit", "junk") cp.Expect("Cannot obtain") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestPackage_listingWithCommitUnknown() { @@ -126,6 +129,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_listingWithCommitUnknown() cp := ts.Spawn("packages", "--commit", "00010001-0001-0001-0001-000100010001") cp.Expect("No data") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestPackage_listingWithCommitValidNoPackages() { @@ -185,6 +189,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_searchWithExactTermWrongTe cp := ts.Spawn("search", "Requests", "--exact-term") cp.Expect("No packages in our catalog match") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.Spawn("search", "xxxrequestsxxx", "--exact-term") cp.Expect("No packages in our catalog match") @@ -232,6 +237,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_searchWithWrongLang() { cp := ts.Spawn("search", "xxxjunkxxx", "--language=perl") cp.Expect("No packages in our catalog match") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestPackage_searchWithBadLang() { @@ -243,6 +249,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_searchWithBadLang() { cp := ts.Spawn("search", "numpy", "--language=bad") cp.Expect("Cannot obtain search") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestPackage_info() { @@ -257,7 +264,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_info() { cp.Expect("Version") cp.Expect("Available") cp.Expect("What's next?") - cp.Expect("run `state install") + cp.Expect("run 'state install") cp.ExpectExitCode(0) } @@ -270,64 +277,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_infoWrongCase() { cp := ts.Spawn("info", "Pexpect") cp.Expect("No packages in our catalog are an exact match") cp.ExpectExitCode(1) -} - -const ( - reqsFileName = "requirements.txt" - reqsData = `Click==7.0 -Flask==1.1.1 -Flask-Cors==3.0.8 -itsdangerous==1.1.0 -Jinja2==2.10.3 -MarkupSafe==1.1.1 -packaging==20.3 -pyparsing==2.4.6 -six==1.14.0 -Werkzeug==0.15.6 -` - badReqsData = `Click==7.0 -garbage---<<001.X -six==1.14.0 -` -) - -func (suite *PackageIntegrationTestSuite) TestPackage_import() { - suite.OnlyRunForTags(tagsuite.Package) - ts := e2e.New(suite.T(), false) - defer ts.Close() - - username, _ := ts.CreateNewUser() - namespace := fmt.Sprintf("%s/%s", username, "Python3") - - cp := ts.Spawn("init", "--language", "python", namespace, ts.Dirs.Work) - cp.Expect("successfully initialized") - cp.ExpectExitCode(0) - - reqsFilePath := filepath.Join(cp.WorkDirectory(), reqsFileName) - - suite.Run("invalid requirements.txt", func() { - ts.SetT(suite.T()) - ts.PrepareFile(reqsFilePath, badReqsData) - - cp := ts.Spawn("import", "requirements.txt") - cp.ExpectNotExitCode(0) - }) - - suite.Run("valid requirements.txt", func() { - ts.SetT(suite.T()) - ts.PrepareFile(reqsFilePath, reqsData) - - cp := ts.Spawn("import", "requirements.txt") - cp.ExpectExitCode(0) - - cp = ts.Spawn("push") - cp.ExpectExitCode(0) - - cp = ts.Spawn("import", "requirements.txt") - cp.Expect("Are you sure") - cp.SendLine("n") - cp.ExpectNotExitCode(0) - }) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestPackage_detached_operation() { @@ -430,8 +380,13 @@ func (suite *PackageIntegrationTestSuite) TestPackage_Duplicate() { cp.ExpectExitCode(0) cp = ts.Spawn("install", "requests") // install again - cp.Expect("No new changes to commit") + cp.Expect("already installed") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() + + if strings.Count(cp.Snapshot(), " x ") != 2 { // 2 because "Creating commit x Failed" is also printed + suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) + } } func (suite *PackageIntegrationTestSuite) PrepareActiveStateYAML(ts *e2e.Session) { @@ -456,8 +411,13 @@ func (suite *PackageIntegrationTestSuite) TestPackage_UninstallDoesNotExist() { suite.PrepareActiveStateYAML(ts) cp := ts.Spawn("uninstall", "doesNotExist") - cp.Expect("Error occurred while trying to create a commit") + cp.Expect("does not exist") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() + + if strings.Count(cp.Snapshot(), " x ") != 2 { // 2 because "Creating commit x Failed" is also printed + suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) + } } func (suite *PackageIntegrationTestSuite) TestJSON() { @@ -479,7 +439,7 @@ func (suite *PackageIntegrationTestSuite) TestJSON() { cp = ts.SpawnWithOpts( e2e.OptArgs("install", "Text-CSV", "--output", "editor"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(`{"name":"Text-CSV"`, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -492,7 +452,7 @@ func (suite *PackageIntegrationTestSuite) TestJSON() { cp = ts.SpawnWithOpts( e2e.OptArgs("uninstall", "Text-CSV", "-o", "json"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(`{"name":"Text-CSV"`, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -521,7 +481,7 @@ func (suite *PackageIntegrationTestSuite) TestNormalize() { cp = ts.SpawnWithOpts( e2e.OptArgs("install", "Charset_normalizer"), e2e.OptWD(dir), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("charset-normalizer") cp.Expect("is different") @@ -541,7 +501,7 @@ func (suite *PackageIntegrationTestSuite) TestNormalize() { cp = ts.SpawnWithOpts( e2e.OptArgs("install", "charset-normalizer"), e2e.OptWD(anotherDir), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("charset-normalizer") cp.ExpectExitCode(0) @@ -566,6 +526,7 @@ func (suite *PackageIntegrationTestSuite) TestInstall_InvalidVersion() { // We only assert the state tool curated part of the error as the underlying build planner error may change cp.Expect("Could not plan build") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestUpdate_InvalidVersion() { @@ -589,6 +550,7 @@ func (suite *PackageIntegrationTestSuite) TestUpdate_InvalidVersion() { // We only assert the state tool curated part of the error as the underlying build planner error may change cp.Expect("Could not plan build") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *PackageIntegrationTestSuite) TestUpdate() { diff --git a/test/integration/performance_int_test.go b/test/integration/performance_int_test.go index 173f731cee..a3e6c7a11b 100644 --- a/test/integration/performance_int_test.go +++ b/test/integration/performance_int_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/exeutils" "github.com/ActiveState/termtest" @@ -54,7 +55,7 @@ func performanceTest(commands []string, expect string, samples int, maxTime time for x := 0; x < samples+1; x++ { opts := []e2e.SpawnOptSetter{ e2e.OptArgs(commands...), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_UPDATES=true", "ACTIVESTATE_PROFILE=true"), + e2e.OptAppendEnv(constants.DisableUpdates+"=true", constants.ProfileEnvVarName+"=true"), } termtestLogs := &bytes.Buffer{} if verbose { diff --git a/test/integration/pjfile_int_test.go b/test/integration/pjfile_int_test.go index 51c803c603..f11da737ba 100644 --- a/test/integration/pjfile_int_test.go +++ b/test/integration/pjfile_int_test.go @@ -33,6 +33,7 @@ languages: e2e.OptArgs("scripts"), ) cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func TestPjFileIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/prepare_int_test.go b/test/integration/prepare_int_test.go index e8c17833e4..48f165886a 100644 --- a/test/integration/prepare_int_test.go +++ b/test/integration/prepare_int_test.go @@ -50,7 +50,7 @@ func (suite *PrepareIntegrationTestSuite) TestPrepare() { cp := ts.SpawnWithOpts( e2e.OptArgs("_prepare"), e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.AutostartPathOverrideEnvVarName, autostartDir)), - // e2e.OptAppendEnv(fmt.Sprintf("ACTIVESTATE_CLI_CONFIGDIR=%s", ts.Dirs.Work)), + // e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.ConfigEnvVarName, ts.Dirs.Work)), ) cp.ExpectExitCode(0) @@ -119,7 +119,7 @@ func (suite *PrepareIntegrationTestSuite) AssertConfig(target string) { func (suite *PrepareIntegrationTestSuite) TestResetExecutors() { suite.OnlyRunForTags(tagsuite.Prepare) - ts := e2e.New(suite.T(), true, "ACTIVESTATE_CLI_DISABLE_RUNTIME=false") + ts := e2e.New(suite.T(), true, constants.DisableRuntime+"=false") err := ts.ClearCache() suite.Require().NoError(err) defer ts.Close() diff --git a/test/integration/progress_int_test.go b/test/integration/progress_int_test.go index 811b617c88..998d4bdd56 100644 --- a/test/integration/progress_int_test.go +++ b/test/integration/progress_int_test.go @@ -3,6 +3,7 @@ package integration import ( "testing" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" @@ -20,7 +21,7 @@ func (suite *ProgressIntegrationTestSuite) TestProgress() { cp := ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/small-python"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(locale.T("setup_runtime")) cp.Expect("Checked out", e2e.RuntimeSourcingTimeoutOpt) @@ -29,7 +30,7 @@ func (suite *ProgressIntegrationTestSuite) TestProgress() { cp = ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/small-python", "small-python2", "--non-interactive"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(locale.T("setup_runtime")) cp.Expect("...") diff --git a/test/integration/projects_int_test.go b/test/integration/projects_int_test.go index 80393b4068..4064e54933 100644 --- a/test/integration/projects_int_test.go +++ b/test/integration/projects_int_test.go @@ -150,6 +150,7 @@ func (suite *ProjectsIntegrationTestSuite) TestEdit_Visibility() { cp = ts.Spawn("checkout", namespace) cp.Expect("does not exist under ActiveState-CLI") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() ts.LoginAsPersistentUser() diff --git a/test/integration/pull_int_test.go b/test/integration/pull_int_test.go index 3056c65cb3..004df9fb1a 100644 --- a/test/integration/pull_int_test.go +++ b/test/integration/pull_int_test.go @@ -56,6 +56,7 @@ func (suite *PullIntegrationTestSuite) TestPullSetProject() { cp.SendLine("n") cp.Expect("Pull aborted by user") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() cp = ts.Spawn("pull", "--non-interactive", "--set-project", "ActiveState-CLI/small-python-fork") cp.Expect("activestate.yaml has been updated") @@ -74,6 +75,7 @@ func (suite *PullIntegrationTestSuite) TestPullSetProjectUnrelated() { cp.SendLine("n") cp.Expect("Pull aborted by user") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() cp = ts.Spawn("pull", "--non-interactive", "--set-project", "ActiveState-CLI/Python3") cp.Expect("no common base") @@ -106,6 +108,7 @@ func (suite *PullIntegrationTestSuite) TestPull_Merge() { cp := ts.SpawnWithOpts(e2e.OptArgs("push"), e2e.OptWD(wd)) cp.Expect("Your project has new changes available") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.SpawnWithOpts(e2e.OptArgs("pull"), e2e.OptWD(wd)) cp.Expect("Merging history") @@ -132,6 +135,7 @@ func (suite *PullIntegrationTestSuite) TestPull_RestoreNamespace() { cp := ts.Spawn("pull", "--non-interactive", "--set-project", "ActiveState-CLI/Python3") cp.Expect("no common base") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() // Verify namespace is unchanged. cp = ts.Spawn("show") @@ -154,7 +158,7 @@ func (suite *PullIntegrationTestSuite) TestMergeBuildScript() { cp = ts.SpawnWithOpts( e2e.OptArgs("install", "requests"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Package added", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -168,6 +172,7 @@ func (suite *PullIntegrationTestSuite) TestMergeBuildScript() { cp = ts.Spawn("pull") cp.Expect("Unable to automatically merge build scripts") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() _, err = buildscript.NewScriptFromProject(proj, nil) suite.Assert().Error(err) diff --git a/test/integration/push_int_test.go b/test/integration/push_int_test.go index 4615c9f9b7..d7a18ca19c 100644 --- a/test/integration/push_int_test.go +++ b/test/integration/push_int_test.go @@ -173,7 +173,7 @@ func (suite *PushIntegrationTestSuite) TestCarlisle() { e2e.OptArgs( "activate", suite.baseProject, "--path", wd), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) // The activestate.yaml on Windows runs custom activation to set shortcuts and file associations. cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) @@ -188,7 +188,8 @@ func (suite *PushIntegrationTestSuite) TestCarlisle() { cp = ts.SpawnWithOpts(e2e.OptArgs( "install", suite.extraPackage), e2e.OptWD(wd), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false")) + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) switch runtime.GOOS { case "darwin": cp.ExpectRe("added|being built", e2e.RuntimeSourcingTimeoutOpt) // while cold storage is off @@ -222,6 +223,7 @@ func (suite *PushIntegrationTestSuite) TestPush_NoProject() { cp := ts.SpawnWithOpts(e2e.OptArgs("push")) cp.Expect("No project found") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -239,6 +241,7 @@ func (suite *PushIntegrationTestSuite) TestPush_NoAuth() { cp := ts.SpawnWithOpts(e2e.OptArgs("push")) cp.Expect("you need to be authenticated") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -258,6 +261,7 @@ func (suite *PushIntegrationTestSuite) TestPush_NoChanges() { cp = ts.SpawnWithOpts(e2e.OptArgs("push")) cp.Expect("no local changes to push") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -278,6 +282,7 @@ func (suite *PushIntegrationTestSuite) TestPush_NameInUse() { cp := ts.SpawnWithOpts(e2e.OptArgs("push", "-n", "ActiveState-CLI/push-error-test")) cp.Expect("already in use") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -303,6 +308,7 @@ func (suite *PushIntegrationTestSuite) TestPush_Aborted() { cp.SendLine("n") cp.Expect("Project creation aborted by user", termtest.OptExpectTimeout(5*time.Second)) cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -323,6 +329,7 @@ func (suite *PushIntegrationTestSuite) TestPush_InvalidHistory() { cp := ts.SpawnWithOpts(e2e.OptArgs("push", "ActiveState-CLI/push-error-test")) cp.Expect("commit history does not match") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -342,6 +349,7 @@ func (suite *PushIntegrationTestSuite) TestPush_PullNeeded() { cp := ts.SpawnWithOpts(e2e.OptArgs("push")) cp.Expect("changes available that need to be merged") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() if strings.Count(cp.Snapshot(), " x ") != 1 { suite.Fail("Expected exactly ONE error message, got: ", cp.Snapshot()) @@ -371,6 +379,7 @@ func (suite *PushIntegrationTestSuite) TestPush_Outdated() { cp := ts.SpawnWithOpts(e2e.OptArgs("push"), e2e.OptWD(wd)) cp.Expect("Your project has new changes available") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func TestPushIntegrationTestSuite(t *testing.T) { diff --git a/test/integration/refresh_int_test.go b/test/integration/refresh_int_test.go index 1001b5baa8..55898b01a1 100644 --- a/test/integration/refresh_int_test.go +++ b/test/integration/refresh_int_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" "github.com/stretchr/testify/suite" @@ -22,7 +23,7 @@ func (suite *RefreshIntegrationTestSuite) TestRefresh() { cp := ts.SpawnWithOpts( e2e.OptArgs("refresh"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Setting Up Runtime") cp.Expect("Runtime updated", e2e.RuntimeSourcingTimeoutOpt) @@ -30,15 +31,16 @@ func (suite *RefreshIntegrationTestSuite) TestRefresh() { cp = ts.SpawnWithOpts( e2e.OptArgs("exec", "--", "python3", "-c", "import requests"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("ModuleNotFoundError") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() suite.PrepareActiveStateYAML(ts, "ActiveState-CLI/Branches", "secondbranch", "46c83477-d580-43e2-a0c6-f5d3677517f1") cp = ts.SpawnWithOpts( e2e.OptArgs("refresh"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Setting Up Runtime") cp.Expect("Runtime updated", e2e.RuntimeSourcingTimeoutOpt) @@ -46,7 +48,7 @@ func (suite *RefreshIntegrationTestSuite) TestRefresh() { cp = ts.SpawnWithOpts( e2e.OptArgs("exec", "--", "python3", "-c", "import requests"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.ExpectExitCode(0, e2e.RuntimeSourcingTimeoutOpt) diff --git a/test/integration/revert_int_test.go b/test/integration/revert_int_test.go index a7230c411d..be86d8bd60 100644 --- a/test/integration/revert_int_test.go +++ b/test/integration/revert_int_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" "github.com/stretchr/testify/suite" @@ -51,7 +52,7 @@ func (suite *RevertIntegrationTestSuite) TestRevert() { // Verify that argparse still exists (it was not reverted along with urllib3). cp = ts.SpawnWithOpts( e2e.OptArgs("shell", "Revert"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.ExpectInput(e2e.RuntimeSourcingTimeoutOpt) cp.SendLine("python3") @@ -85,6 +86,7 @@ func (suite *RevertIntegrationTestSuite) TestRevert_failsOnCommitNotInHistory() cp.Expect(commitID) cp.Expect("The target commit is not within the current commit's history") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func (suite *RevertIntegrationTestSuite) TestRevertTo() { @@ -142,6 +144,7 @@ func (suite *RevertIntegrationTestSuite) TestRevertTo_failsOnCommitNotInHistory( cp.Expect(commitID) cp.Expect("The target commit is not") cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() } func (suite *RevertIntegrationTestSuite) TestJSON() { diff --git a/test/integration/run_int_test.go b/test/integration/run_int_test.go index 01a7b1fbe9..1f14495762 100644 --- a/test/integration/run_int_test.go +++ b/test/integration/run_int_test.go @@ -14,6 +14,7 @@ import ( "github.com/ActiveState/termtest" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/environment" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/testhelpers/e2e" @@ -196,6 +197,7 @@ func (suite *RunIntegrationTestSuite) TestTwoInterrupts() { cp.SendCtrlC() suite.expectTerminateBatchJob(cp) cp.ExpectExitCode(123) + ts.IgnoreLogErrors() suite.Require().NotContains( cp.Output(), "not printed after second interrupt", ) @@ -292,7 +294,7 @@ func (suite *RunIntegrationTestSuite) TestRun_Perl_Variable() { cp := ts.SpawnWithOpts( e2e.OptArgs("activate"), e2e.OptAppendEnv( - "ACTIVESTATE_CLI_DISABLE_RUNTIME=false", + constants.DisableRuntime+"=false", "PERL_VERSION=does_not_exist", ), ) diff --git a/test/integration/runtime_int_test.go b/test/integration/runtime_int_test.go index 19261c24bc..c7ec78a267 100644 --- a/test/integration/runtime_int_test.go +++ b/test/integration/runtime_int_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/exeutils" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" @@ -87,7 +88,7 @@ func (suite *RuntimeIntegrationTestSuite) TestInterruptSetup() { cp := ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/test-interrupt-small-python#863c45e2-3626-49b6-893c-c15e85a17241", "."), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) @@ -99,8 +100,8 @@ func (suite *RuntimeIntegrationTestSuite) TestInterruptSetup() { cp = ts.SpawnWithOpts( e2e.OptArgs("pull"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false", - "ACTIVESTATE_CLI_RUNTIME_SETUP_WAIT=true"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false", + constants.RuntimeSetupWaitEnvVarName+"=true"), ) time.Sleep(30 * time.Second) cp.SendCtrlC() // cancel pull/update diff --git a/test/integration/shell_int_test.go b/test/integration/shell_int_test.go index 306aa55863..459f68dd5e 100644 --- a/test/integration/shell_int_test.go +++ b/test/integration/shell_int_test.go @@ -13,6 +13,8 @@ import ( "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/subshell" + "github.com/ActiveState/cli/internal/subshell/bash" + "github.com/ActiveState/cli/internal/subshell/sscommon" "github.com/ActiveState/cli/internal/subshell/zsh" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" @@ -88,7 +90,7 @@ func (suite *ShellIntegrationTestSuite) TestDefaultShell() { // Use. cp = ts.SpawnWithOpts( e2e.OptArgs("use", "ActiveState-CLI/small-python"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -188,7 +190,7 @@ func (suite *ShellIntegrationTestSuite) TestDefaultNoLongerExists() { cp = ts.SpawnWithOpts( e2e.OptArgs("use", "ActiveState-CLI/Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -226,7 +228,7 @@ func (suite *ShellIntegrationTestSuite) TestUseShellUpdates() { cp = ts.SpawnWithOpts( e2e.OptArgs("use", "ActiveState-CLI/Python3"), e2e.OptAppendEnv("SHELL=bash"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -285,6 +287,84 @@ func (suite *ShellIntegrationTestSuite) TestRuby() { cp.Expect("ActiveState") } +func (suite *ShellIntegrationTestSuite) TestNestedShellNotification() { + if runtime.GOOS == "windows" { + return // cmd.exe does not have an RC file to check for nested shells in + } + suite.OnlyRunForTags(tagsuite.Shell) + ts := e2e.New(suite.T(), false) + defer ts.Close() + + var ss subshell.SubShell + var rcFile string + env := []string{"ACTIVESTATE_CLI_DISABLE_RUNTIME=false"} + switch runtime.GOOS { + case "darwin": + ss = &zsh.SubShell{} + ss.SetBinary("zsh") + rcFile = filepath.Join(ts.Dirs.HomeDir, ".zshrc") + suite.Require().NoError(sscommon.WriteRcFile("zshrc_append.sh", rcFile, sscommon.DefaultID, nil)) + env = append(env, "SHELL=zsh") // override since CI tests are running on bash + case "linux": + ss = &bash.SubShell{} + ss.SetBinary("bash") + rcFile = filepath.Join(ts.Dirs.HomeDir, ".bashrc") + suite.Require().NoError(sscommon.WriteRcFile("bashrc_append.sh", rcFile, sscommon.DefaultID, nil)) + default: + suite.Fail("Unsupported OS") + } + suite.Require().Equal(filepath.Dir(rcFile), ts.Dirs.HomeDir, "rc file not in test suite homedir") + suite.Require().Contains(string(fileutils.ReadFileUnsafe(rcFile)), "State Tool is operating on project") + + cp := ts.Spawn("checkout", "ActiveState-CLI/small-python") + cp.Expect("Checked out project") + cp.ExpectExitCode(0) + + cp = ts.SpawnWithOpts( + e2e.OptArgs("shell", "small-python"), + e2e.OptAppendEnv(env...)) + cp.Expect("Activated") + suite.Assert().NotContains(cp.Snapshot(), "State Tool is operating on project") + cp.SendLine(fmt.Sprintf(`export HOME="%s"`, ts.Dirs.HomeDir)) // some shells do not forward this + + cp.SendLine(ss.Binary()) // platform-specific shell (zsh on macOS, bash on Linux, etc.) + cp.Expect("State Tool is operating on project ActiveState-CLI/small-python") + cp.SendLine("exit") // subshell within a subshell + cp.SendLine("exit") + cp.ExpectExitCode(0) +} + +func (suite *ShellIntegrationTestSuite) TestPs1() { + if runtime.GOOS == "windows" { + return // cmd.exe does not have a PS1 to modify + } + suite.OnlyRunForTags(tagsuite.Shell) + ts := e2e.New(suite.T(), false) + defer ts.Close() + + cp := ts.Spawn("checkout", "ActiveState-CLI/small-python") + cp.Expect("Checked out project") + cp.ExpectExitCode(0) + + cp = ts.SpawnWithOpts( + e2e.OptArgs("shell", "small-python"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), + ) + cp.Expect("Activated") + cp.Expect("[ActiveState-CLI/small-python]") + cp.SendLine("exit") + cp.ExpectExitCode(0) + + cp = ts.Spawn("config", "set", constants.PreservePs1ConfigKey, "true") + cp.ExpectExitCode(0) + + cp = ts.Spawn("shell", "small-python") + cp.Expect("Activated") + suite.Assert().NotContains(cp.Snapshot(), "[ActiveState-CLI/small-python]") + cp.SendLine("exit") + cp.ExpectExitCode(0) +} + func TestShellIntegrationTestSuite(t *testing.T) { suite.Run(t, new(ShellIntegrationTestSuite)) } diff --git a/test/integration/shells_int_test.go b/test/integration/shells_int_test.go index 2ac9cb1f0f..06d1c5a81d 100644 --- a/test/integration/shells_int_test.go +++ b/test/integration/shells_int_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/suite" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" ) @@ -34,7 +35,7 @@ func (suite *ShellsIntegrationTestSuite) TestShells() { // Checkout the first instance. It doesn't matter which shell is used. cp := ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/small-python"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -54,7 +55,7 @@ func (suite *ShellsIntegrationTestSuite) TestShells() { // There are 2 or more instances checked out, so we should get a prompt in whichever shell we // use. - cp = ts.SpawnShellWithOpts(shell, e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false")) + cp = ts.SpawnShellWithOpts(shell, e2e.OptAppendEnv(constants.DisableRuntime+"=false")) cp.SendLine(e2e.QuoteCommand(shell, ts.ExecutablePath(), "shell", "small-python")) cp.Expect("Multiple project paths") diff --git a/test/integration/show_int_test.go b/test/integration/show_int_test.go index c8ea19107c..c53381b290 100644 --- a/test/integration/show_int_test.go +++ b/test/integration/show_int_test.go @@ -26,7 +26,7 @@ func (suite *ShowIntegrationTestSuite) TestShow() { cp := ts.SpawnWithOpts( e2e.OptArgs("activate"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.ExpectInput(e2e.RuntimeSourcingTimeoutOpt) diff --git a/test/integration/switch_int_test.go b/test/integration/switch_int_test.go index c755cf5897..3e0607e073 100644 --- a/test/integration/switch_int_test.go +++ b/test/integration/switch_int_test.go @@ -102,6 +102,7 @@ func (suite *SwitchIntegrationTestSuite) TestSwitch_CommitID_NotInHistory() { cp.Expect("Commit does not belong") if runtime.GOOS != "windows" { cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } // Check that branch and commitID were not updated diff --git a/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/installdir/bin/test-another-offline-install b/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/installdir/bin/test-another-offline-install deleted file mode 100755 index cce31695ba..0000000000 --- a/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/installdir/bin/test-another-offline-install +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env sh -echo TEST PLACEHOLDER diff --git a/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/installdir/bin/test.hello b/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/installdir/bin/test.hello deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/runtime.json b/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/runtime.json deleted file mode 100644 index 90afca4c3e..0000000000 --- a/test/integration/testdata/offline-install/another-artifacts-payload-nix/artifact/runtime.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "env": [ - { - "env_name": "PATH", - "join": "prepend", - "inherit": true, - "values": [ - "${INSTALLDIR}/bin" - ], - "separator": ";" - } - ], - "file_transforms": [ - { - "pattern": "PLACEHOLDER", - "with": "ANOTHER", - "in": [ - "bin/test-another-offline-install" - ] - } - ], - "installdir": "installdir" -} \ No newline at end of file diff --git a/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/installdir/bin/test-another-offline-install.bat b/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/installdir/bin/test-another-offline-install.bat deleted file mode 100644 index c14ffd1645..0000000000 --- a/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/installdir/bin/test-another-offline-install.bat +++ /dev/null @@ -1 +0,0 @@ -echo TEST PLACEHOLDER \ No newline at end of file diff --git a/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/installdir/bin/test.hello b/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/installdir/bin/test.hello deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/runtime.json b/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/runtime.json deleted file mode 100644 index c348000216..0000000000 --- a/test/integration/testdata/offline-install/another-artifacts-payload-windows/artifact/runtime.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "env": [ - { - "env_name": "PATH", - "join": "prepend", - "inherit": true, - "values": [ - "${INSTALLDIR}\\bin" - ], - "separator": ";" - } - ], - "file_transforms": [ - { - "pattern": "PLACEHOLDER", - "with": "ANOTHER", - "in": [ - "bin/test-another-offline-install.bat" - ] - } - ], - "installdir": "installdir" -} \ No newline at end of file diff --git a/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/installdir/bin/test-offline-install b/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/installdir/bin/test-offline-install deleted file mode 100755 index cce31695ba..0000000000 --- a/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/installdir/bin/test-offline-install +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env sh -echo TEST PLACEHOLDER diff --git a/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/installdir/bin/test.hello b/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/installdir/bin/test.hello deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/runtime.json b/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/runtime.json deleted file mode 100644 index e16b19bf70..0000000000 --- a/test/integration/testdata/offline-install/artifacts-payload-nix/artifact/runtime.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "env": [ - { - "env_name": "PATH", - "join": "prepend", - "inherit": true, - "values": [ - "${INSTALLDIR}/bin" - ], - "separator": ";" - } - ], - "file_transforms": [ - { - "pattern": "PLACEHOLDER", - "with": "REPLACEMENT", - "in": [ - "bin/test-offline-install" - ] - } - ], - "installdir": "installdir" -} \ No newline at end of file diff --git a/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/installdir/bin/test-offline-install.bat b/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/installdir/bin/test-offline-install.bat deleted file mode 100644 index c14ffd1645..0000000000 --- a/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/installdir/bin/test-offline-install.bat +++ /dev/null @@ -1 +0,0 @@ -echo TEST PLACEHOLDER \ No newline at end of file diff --git a/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/installdir/bin/test.hello b/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/installdir/bin/test.hello deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/runtime.json b/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/runtime.json deleted file mode 100644 index e04e2f102e..0000000000 --- a/test/integration/testdata/offline-install/artifacts-payload-windows/artifact/runtime.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "env": [ - { - "env_name": "PATH", - "join": "prepend", - "inherit": true, - "values": [ - "${INSTALLDIR}\\bin" - ], - "separator": ";" - } - ], - "file_transforms": [ - { - "pattern": "PLACEHOLDER", - "with": "REPLACEMENT", - "in": [ - "bin/test-offline-install.bat" - ] - } - ], - "installdir": "installdir" -} \ No newline at end of file diff --git a/test/integration/testdata/offline-install/assets/Another-IntegrationTest/LICENSE.txt b/test/integration/testdata/offline-install/assets/Another-IntegrationTest/LICENSE.txt deleted file mode 100644 index f406868075..0000000000 --- a/test/integration/testdata/offline-install/assets/Another-IntegrationTest/LICENSE.txt +++ /dev/null @@ -1,563 +0,0 @@ -ACTIVESTATE® COMMUNITY EDITION LICENSE AGREEMENT - -Version effective date: March 25, 2019 - -This license agreement (the “Agreement”) is made between you (either -an individual or a company or organization, not including its -affiliates or wholly owned subsidiaries) (“You”) and ActiveState -Software Inc. (“ActiveSstate”). This Agreement establishes the terms -under which ActiveState will license the Software (as defined below) -to You and establishes the terms under which You may use, copy, -modify, distribute, and/or Redistribute (as defined below) the -Software. This Agreement does not apply to Maintenance and Support, -anything Beyond Development Use, OEM Distribution (all such -capitalized terms as defined below) each of which requires a separate -agreement with ActiveState. For more information on these types of -agreements, please visit www.activestate.com. The intent of this -Agreement is to allow ActiveState to maintain control over the -development and distribution of the Software while allowing its use in -a variety of ways. If the terms and conditions of this Agreement do -not permit Your proposed use of the Software or if You require -clarification regarding the scope of Your intended use of the -Software, please contact sales@activestate.com. - -PLEASE READ THIS AGREEMENT CAREFULLY BEFORE INSTALLING OR USING THE -SOFTWARE. BY CLICKING ON “YES, ACCEPT” OR BY INSTALLING THE SOFTWARE, -YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. -IF YOU ARE ENTERING INTO THIS AGREEMENT ON BEHALF OF A PERSON, YOUR -ACCEPTANCE REPRESENTS THAT YOU HAVE THE AUTHORITY TO BIND SUCH PERSON -TO THE TERMS AND CONDITIONS OF THIS AGREEMENT, IN WHICH CASE “YOU” OR -“YOUR” WILL REFER TO THE PERSON ON BEHALF OF WHICH YOU ACT (“YOUR -ENTITY”). IF YOU DO NOT AGREE WITH THE TERMS AND CONDITIONS OF THIS -AGREEMENT OR IF YOU DO NOT HAVE THE AUTHORITY TO BIND YOUR ENTITY, YOU -HAVE NO RIGHT TO INSTALL OR USE THE SOFTWARE AND YOU SHOULD (A) -RETURN, DELETE, OR DISABLE THE SOFTWARE OR (B) IF YOU PURCHASED A -PRODUCT FROM ACTIVESTATE OR ITS RESELLER OR DISTRIBUTOR ON WHICH THE -SOFTWARE IS PRE-INSTALLED BY ACTIVESTATE, RETURN THE PURCHASED PRODUCT -TO ACTIVESTATE OR THE APPLICABLE RESELLER OR DISTRIBUTOR FROM WHOM YOU -OBTAINED THE PRODUCT. - -1. Definitions. - -“Accessible Code” means source code contained within the Software that -is licensed under an open source license. - -“Confidential Information” means all information designated in writing -as confidential by each party, or which under the circumstances of -disclosure reasonably ought to be considered as confidential. Without -limiting the foregoing, ActiveState Confidential Information includes -the Software, including all source and object code, and all associated -documentation, but not Accessible Code. - -"Maintenance And Support" means maintenance and support for the -Software provided by ActiveState under separate terms. - -"OEM Distribution" means any distribution to, and/or use of the -Software by, others outside Your organization and distribution and/or -use of the Software as either a bundled add-on to, or embedded -component of another application, with such application being made -available to its users as, but not limited to, an on-premises -application, a hosted application, a software-as-a-service offering or -a subscription service for which the distributor of the application -receives a license fee or any form of direct or indirect compensation -and whether for commercial or non-commercial purposes. - -“Person” means any individual, sole proprietorship, partnership, firm, -entity, unincorporated association, unincorporated syndicate, -unincorporated organization, trust, body corporate or governmental or -regulatory authority, and where the context requires, any of the -foregoing when they are acting as trustee, executor, administrator or -other legal representative. - -“Beyond Development Use” means any use of the Software licensed under -this Agreement beyond software development with the Software. For -greater clarity, any use of the Software licensed under this Agreement -beyond the purpose of developing, prototyping or demonstrating Your -application with the Software or by the Software are not permitted -under this license. - -“Redistribute” means any distribution to, and/or use of the Software -by, others inside or outside Your organization and distribution and/or -use of the Software inside or outside Your organization. - -“Software” means any of ActivePerl, ActivePython, ActiveTcl, ActiveGo, -ActiveRuby, ActiveNode, or ActiveLua software and the accompanying -materials including, but not limited to, source code, binary -executables, documentation, images and scripts, which are distributed -by ActiveState, and derivatives of that collection and/or those files. - -“User Data” means all information and data collected by the Software -or otherwise transmitted by the Software to ActiveState, including any -data, metadata, metrics, statistics, or other information relating to -the performance, operations, resource, health, or other conditions of -the Software, any component thereof (including third party -components), and any related infrastructure, such as network host -names, IP addresses, interpreter used, and system architecture, which -includes filenames, full path, file size, and content hash. - -“Wrapped Application” means a single-file executable in which all -binary components are encapsulated in a single binary without exposing -the base programming language as a scripting language within Your own -application program to end users. - -2. License Grant. - - (a) Subject to the terms and conditions of this Agreement, - ActiveState hereby grants to You a limited, worldwide, - perpetual, paid up, free-of-charge, non-exclusive, - non-transferable, non-assignable, and non-sublicensable - license to install and use the Software on any computing - device, in accordance with the limitations and restrictions - set forth in this Agreement, for research and development - purposes only. You may not use the Software Beyond Development - Use, except as provided in Section 2(b) below. You may not - use the Software for OEM Distribution. You may copy the Software - for archival purposes or as necessary to use the Software as - authorized in this section. You also may modify the - Accessible Code to develop bug fixes, customizations, or - additional features, for the sole purpose of using the Software - as authorized by this Agreement. - - (b) ActiveState may, in its sole discretion, grant You the right to - use the Software Beyond Development Use and/or OEM Distribution - for limited, small-scale, non-commercial and/or open source - projects. To apply for this right, contact sales@activestate.com. - Without the prior approval of ActiveState, you may not use the - Software Beyond Development Use and/or for OEM Distribution. - -3. Restrictions. - - (a) Except as expressly provided in this Agreement, You may not: - - (i) transfer, assign, sublicense, resell, or rent the - Software; - (ii) modify or translate the Software to discover the source - code in the Software or create a functional equivalent in - the Software; - (iii) reverse engineer, decompile, or disassemble (except as - and only to the extent this restriction is prohibited by - applicable law) the Software; - (iv) create derivative works based on the Software; - (v) merge the Software with another product; - (vi) copy the Software; - (vii) remove or obscure any proprietary rights notices or - labels on the Software; - (viii) Redistribute, without entering into a separate agreement - with ActiveState: - I. the Software as a whole, whether as a Wrapped - Application or on a standalone basis; - II. parts of the Software to create a language - distribution; or - III. the Software (other than the Accessible Code) with - Your Wrapped Application; - (ix) distribute the Software by OEM Distribution without - entering into a separate OEM Distribution agreement with - ActiveState; - (x) permit others to use the Software; or - (xi) use the Software: - I. Beyond Development Use on any computing device in - whatever form or manner, whether physical or - virtual and external or internal-facing; - II. on any operating systems other than Windows, OSX, and - Linux; - III. on computing devices used for file and/or application - serving; - IV. on any computing devices used for business continuity - and disaster recovery; or - V. to provide content or functionality through - external-facing servers or internal-facing - production servers. - -4. Confidentiality. - - (a) Except as reasonably required to exercise Your rights under - this Agreement, You agree to prevent any unauthorized copying, - use, distribution, installation or transfer of possession of - Confidential Information received from ActiveState (the - “ACTIVESTATE CONFIDENTIAL INFORMATION”). You do not acquire - any interest in any ActiveState Confidential Information by - reason of this Agreement. ActiveState Confidential Information - does not include any information which (i) becomes part of the - public domain through no act or omission on Your part; (ii) is - lawfully acquired by You from a third party without any breach - of confidentiality; (iii) is independently developed by You - without reference to the ActiveState Confidential Information; - or (iv) is disclosed in accordance with judicial or other - governmental order or timely disclosure requirements imposed - by law or stock exchange policies. Notwithstanding the - foregoing, either party may disclose the terms and conditions - of this Agreement in conjunction with legal proceedings. - Without limiting the generality of the foregoing, You must - take reasonable steps to prevent any personnel from removing - any proprietary or other legend or restrictive notice - contained or included in any material provided by ActiveState - to You. - - (b) You acknowledge that any use or disclosure of the ActiveState - Confidential Information in a manner inconsistent with the - provisions of this Agreement may cause ActiveState irreparable - damage for which remedies other than injunctive relief may be - inadequate. You further agree that ActiveState will be - entitled to attempt to receive from a court of competent - jurisdiction injunctive or other equitable relief to restrain - such use or disclosure in addition to other appropriate - remedies. - -5. Open Source Acknowledgement. The Software is comprised of open - source software, which is subject to the terms of the open source - software license(s) accompanying or otherwise applicable to that - open source software included in the Software (the “Open Source - Components”). For reference, Tcl/Tk open source license terms can - be found in Exhibit A attached to this Agreement or obtained from - this link: https://www.tcl.tk/software/tcltk/license.html. You - acknowledge that Your own distribution or deployment of instances - containing or linking to the Software, including the Open Source - Components, or any other open source software may trigger open - source license requirements for which You are responsible. Nothing - in this Agreement limits Your rights under or grants rights to You - that supersede the terms of any applicable open source software - license for the applicable Open Source Components. - -6. Intellectual Property Ownership. All right, title and interest in - and to the Software and all intellectual property rights embodied - therein, including copyrights, trade names, trademarks, service - marks, product names, trade secrets embodied in the Software's - design and coding methodology and other proprietary materials in - the Software belong exclusively to ActiveState or its third party - licensors. The Software is protected by Canada and United States - copyright laws and international treaty provisions as implemented - locally in different jurisdictions. Except as specifically - provided under this Agreement, You acknowledge that no other - right, title or interest in and to the Software or any parts - thereof is granted to You. ActiveState grants You the limited - right to use the trade names, trademarks, service marks or product - names of ActiveState as required for reasonable and customary use - in describing the origin of the Software. You may not use the - trade names, trademarks, service marks or product names of - ActiveState in any way that might state or imply that ActiveState - endorses Your work, or might state or imply that You created the - Software. - -7. User Data. You acknowledge, agree, and expressly consent to - ActiveState’s collection of Your User Data through the Software. - ActiveState does not claim ownership of any User Data. You hereby - grant to ActiveState and its sublicensees a royalty-free, - perpetual, irrevocable, transferable, worldwide non-exclusive - right to reproduce, analyse, review, process, diagnose, or - otherwise use the User Data (in whole or in part) for the purpose - of supporting, maintaining, and providing the Software, the - Maintenance and Support, if any, and any related services provided - by ActiveState relating to the Software (the “User Data License”). - ActiveState will not disclose the User Data to any third parties - and will only use the User Data in accordance with the User Data - License, except that ActiveState may provide Your User Data to - third parties providing services relating to the Software to - ActiveState (which will protect the User Data on terms and - conditions that are commensurate in scope with this Agreement). In - addition to the rights granted under the User Data License, You - acknowledge and agree that ActiveState has the right to (i) - publicly disclose, in any manner whatsoever, User Data that have - been anonymized; and (ii) review or analyze the User Data and - publicly disclose any results of such review or analysis, - including in the form of reports, blog posts, newsletters, - marketing materials, or otherwise, provided You will not be - identified in such publicly disclosed materials. - -8. Term. This Agreement will be effective upon Your agreement to be - bound by the terms and conditions of this Agreement and will - continue in effect unless otherwise terminated in accordance with - the terms and conditions of this Agreement. - -9. Termination. If You breach any term or condition of this - Agreement, ActiveState may immediately terminate this Agreement - with respect to the Software that You have licensed under this - Agreement by providing notice to You. ActiveState may also - terminate this Agreement, without any liability to You, if any - law, regulations, orders, or legal requirements prohibits - ActiveState’s provision or licensing of the Software to You. Upon - termination of this Agreement by ActiveState, You will immediately - cease all use of the Software and return all copies of the - Software that are under Your control to ActiveState or to delete - all such copies. - -10. Infringement Indemnification. You indemnify, hold harmless, and - defend ActiveState, its licensors, and their respective employees, - agents and distributors against any and all claims, proceedings, - demands and costs resulting from or in any way connected with Your - use of the Software and arising from Your breach of this - Agreement; provided, however, that ActiveState will notify You in - writing of any such claim. ActiveState will not enter into any - settlement or compromise any such claim without Your prior written - consent. You will have sole control of any such action and - settlement negotiations; and ActiveState will provide You with - commercially reasonable information and assistance, at Your - request and expense, necessary to settle or defend such claim. - -11. Disclaimer Of Warranty. - - (a) NEITHER ACTIVESTATE NOR ANY OF ITS SUPPLIERS, LICENSORS, OR - RESELLERS MAKES ANY WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - UNDER THIS AGREEMENT. TO THE MAXIMUM EXTENT PERMITTED UNDER - APPLICABLE LAW, ACTIVESTATE AND ITS SUPPLIERS, LICENSORS, AND - RESELLERS SPECIFICALLY DISCLAIM ALL WARRANTIES AND CONDITIONS - WITH RESPECT TO THE SOFTWARE, EITHER EXPRESS, IMPLIED OR - STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OR - CONDITION OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, - DURABILITY, MERCHANTABLE QUALITY, FITNESS FOR A PARTICULAR - PURPOSE, UPDATES, UPGRADES, SUPPLEMENTS, PRODUCTS, APPLIANCES, - SYSTEM INTEGRATION, DATA ACCURACY AND ANY OTHER ITEMS PROVIDED - HEREUNDER. ACTIVESTATE MAKES NO WARRANTY OR GUARANTEE THAT THE - OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED, ERROR-FREE, - OR VIRUS-FREE, OR THAT THE SOFTWARE WILL MEET ANY PARTICULAR - CRITERIA OF PERFORMANCE, QUALITY, ACCURACY, PURPOSE, OR NEED. - YOU ASSUME THE ENTIRE RISK OF SELECTION, INSTALLATION, AND USE - OF THE SOFTWARE. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN - ESSENTIAL PART OF THIS AGREEMENT. - - (b) TO THE EXTENT ANY IMPLIED WARRANTIES CANNOT BE DISCLAIMED - UNDER APPLICABLE LAW, ANY IMPLIED WARRANTIES ARE LIMITED IN - DURATION TO THE PERIOD REQUIRED BY APPLICABLE LAW. - - (c) SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED - WARRANTIES OR LIMITATIONS ON APPLICABLE STATUTORY RIGHTS OF A - CONSUMER, AND SO SOME OR ALL OF THE EXCLUSION OF IMPLIED - WARRANTIES OR LIMITATIONS SET OUT IN THIS SECTION MAY NOT - APPLY TO YOU. - -12. Limitation Of Liability. - - (a) INDEPENDENT OF THE FOREGOING PROVISIONS, TO THE MAXIMUM EXTENT - PERMITTED UNDER APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL - THEORY, INCLUDING WITHOUT LIMITATION, TORT, CONTRACT, OR - STRICT PRODUCTS LIABILITY, WILL ACTIVESTATE, ITS DIRECTORS, - OFFICERS, EMPLOYEES, AFFILIATES, AGENTS, CONTRACTORS, - PRINCIPALS, SUPPLIERS OR LICENSORS BE LIABLE TO YOU OR ANY - OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR - CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING WITHOUT - LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, - COMPUTER MALFUNCTION, OR ANY OTHER KIND OF COMMERCIAL DAMAGE, - EVEN IF ACTIVESTATE HAS BEEN ADVISED OF THE POSSIBILITY OF - SUCH DAMAGES. - - (b) TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE LAW, IN NO - EVENT WILL ACTIVESTATE BE LIABLE TO YOU FOR DAMAGES UNDER THIS - AGREEMENT FOR ANY CAUSE WHATSOEVER, AND REGARDLESS OF THE FORM - OF ACTION. - -13. Export Controls. You must comply with all export laws and - restrictions and regulations of Canada, the United States or - foreign agencies or authorities, and not to export or re-export - the Software or any direct product thereof in violation of any - such restrictions, laws or regulations, or without all necessary - approvals. As applicable, each party will obtain and bear all - expenses relating to any necessary licenses and/or exemptions with - respect to its own export of the Software from Canada or the U.S. - Neither the Software nor the underlying information or technology - may be electronically transmitted or otherwise exported or - re-exported: into any country subject to Canada or U.S. trade - sanctions covering the Software, to individuals or entities - controlled by such countries, or to nationals or residents of such - countries other than nationals who are lawfully admitted permanent - residents of countries not subject to such sanctions; to anyone on - Canada's Area Control List of the Export and Import Permits Act; - or to anyone on the U.S. Treasury Department's list of Specially - Designated Nationals and Blocked Persons or the U.S. Commerce - Department's Table of Denial Orders. By installing or using the - Software, You agree to the foregoing and represent and warrant - that it complies with these conditions. - -14. U.S. Government End-Users. The Software is a "commercial item," as - that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of - "commercial computer software" and "commercial computer software - documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. - 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 - through 227.7202-4 (June 1995), all U.S. Government End Users - acquire the Software with only those rights as are granted to - all other end users pursuant to the terms and conditions herein. - Unpublished rights are reserved under the copyright laws of Canada - and the United States. - -15. Licensee Outside The U.S. If You are located outside the U.S., - then the following provisions will apply: (a) Les parties aux - presentes confirment leur volonte que cette convention de meme que - tous les documents y compris tout avis qui siy rattache, soient - rediges en langue anglaise (translation: "The parties confirm that - this Agreement and all related documentation is and will be in the - English language."); and (b) You are responsible for complying - with any local laws in Your jurisdiction which might impact Your - right to import, export or use the Software, and You represent - that You have complied with any regulations or registration - procedures required by applicable law to make this license - enforceable. - -16. Entire Agreement. This Agreement constitutes the entire - understanding of the parties with respect to the subject matter of - this Agreement and merges all prior communications, - representations, and agreements. - -17. Severability. If any provision of this Agreement is declared - invalid or unenforceable, such provision will be deemed modified - to the extent necessary and possible to render it valid and - enforceable. In any event, the unenforceability or invalidity of - any provision will not affect any other provision of this - Agreement, and this Agreement will continue in full force and - effect, and be construed and enforced, as if such provision had - not been included, or had been modified as above provided, as the - case may be. - -18. Entire Agreement & Amendment. This Agreement constitutes the - complete agreement between the parties and supersedes all prior or - contemporaneous agreements or representations, written or oral, - concerning the subject matter of this Agreement, appendices and - attachments. ActiveState reserves the right to change this - Agreement at any time, which change shall be effective as of the - effective date for the terms and conditions of this Agreement as - shown on ActiveState’s Website (the “Change Effective Date”). Your - continued use of the Software after the Change Effective Date - constitutes Your acceptance of such changes. This Agreement may - not be otherwise amended without ActiveState's prior written - agreement. You agree to periodically review the terms and - conditions of this Agreement as updated from time to time on - ActiveState’s website. - -19. Arbitration. Except for actions to protect intellectual property - rights and to enforce an arbitrator's decision hereunder, all - disputes, controversies, or claims arising out of or relating to - this Agreement or a breach thereof will be submitted to and be - finally resolved by arbitration under the rules of the American - Arbitration Association ("AAA") then in effect. There will be one - arbitrator, and such arbitrator will be chosen by mutual agreement - of the parties in accordance with AAA rules. The arbitration will - take place in Vancouver, BC, Canada, and may be conducted by - telephone or online. The arbitrator will apply the laws of the - Province of British Columbia, Canada to all issues in dispute. The - controversy or claim will be arbitrated on an individual basis, - and will not be consolidated in any arbitration with any claim or - controversy of any other party. The findings of the arbitrator - will be final and binding on the parties, and may be entered in - any court of competent jurisdiction for enforcement. Enforcements - of any award or judgment will be governed by the United Nations - Convention on the Recognition and Enforcement of Foreign Arbitral - Awards. Should either party file an action contrary to this - provision, the other party may recover legal fees and costs up to - $1,000.00. - -20. Jurisdiction And Venue. The superior courts of Vancouver in the - Province of British Columbia, Canada will be the exclusive - jurisdiction and venue for all legal proceedings that are not - arbitrated under this Agreement. - -21. Force Majeure. Neither party will be liable for damages for any - delay or failure of delivery arising out of causes beyond their - reasonable control and without their fault or negligence, - including, but not limited to, Acts of God, acts of civil or - military authority, fires, riots, wars, embargoes, Internet - disruptions, hacker attacks, or communications failures. - Notwithstanding anything to the contrary contained herein, if - either party is unable to perform hereunder for a period of - thirty (30) consecutive days, then the other party may terminate - this Agreement immediately without liability by ten (10) days’ - written notice to the other. - -22. Publicity And Audit Rights. - - (a) You grant ActiveState the right to include Your name, trade - name, trademark, service mark or logo in its Software - promotional material. You may retract this grant at any time - in writing to marcom@activestate.com, requesting Your name, - trade name, trademark, service mark or logo be excluded from - future releases of ActiveState Software promotional material. - Requests cannot be complied with retroactively and may require - up to thirty (30) days to process. - - (b) If You entered into this Agreement on behalf of a Person, - where such Person has more than 100 employees, if requested by - ActiveState, You will furnish ActiveState with a signed - certification (i) verifying that the Software is being used - pursuant to the terms of this Agreement, including any user - limitations and (ii) listing the locations where the Software - is being used, the version(s) of the Software being used, how - long and how the Software is being used, and the number - computing devices and operating systems the Software is being - used with. You agree to grant ActiveState reasonable access to - Your site(s) and/or systems, upon prior notice during normal - business hours, to audit the use of the Software. Any such - audit shall be at ActiveState’s expense. - -23. Assignment. Except as expressly provided herein, neither this - Agreement nor any rights granted hereunder, nor the use of any of - the Software may be assigned, or otherwise transferred, in whole - or in part, by You, without the prior written consent of - ActiveState. Any permitted assignment by You under this Section - will be conditional upon You delivering all copies of the Software - to the transferee along with a copy of this Agreement, the - transferee accepting the terms and conditions of this Agreement, - and Your license to the Software terminating upon transfer. Any - attempted assignment by You will be void and of no effect unless - permitted by the foregoing. You acknowledge and agree that - ActiveState may assign this Agreement to any third party without - Your prior consent. - -24. Enurement. This Agreement will enure to the benefit of the - parties’ permitted successors and assigns. - -25. Governing Law. This Agreement will be construed under the laws of - the Province of British Columbia and the federal laws of Canada - applicable therein, without regard to the conflict of law rules. - The application of the United Nations Convention of Contracts for - the International Sale of Goods and any local implementations - thereof are expressly excluded. The parties agree that the Uniform - Computer - -Transactions Act or any version thereof, adopted by any jurisdiction, -in any form ("UCITA"), will not apply to this Agreement, and to the -extent that UCITA may be applicable, the parties agree to opt out of -the applicability of UCITA pursuant to the opt-out provision(s) -contained therein. - - - -EXHIBIT A - -Tcl/tk License Terms - - -This software is copyrighted by the Regents of the University of -California, Sun Microsystems, Inc., Scriptics Corporation, and other -parties. The following terms apply to all files associated with the -software unless explicitly disclaimed in individual files. - -The authors hereby grant permission to use, copy, modify, distribute, -and license this software and its documentation for any purpose, -provided that existing copyright notices are retained in all copies -and that this notice is included verbatim in any distributions. No -written agreement, license, or royalty fee is required for any of the -authorized uses. Modifications to this software may be copyrighted by -their authors and need not follow the licensing terms described here, -provided that the new terms are clearly indicated on the first page of -each file where they apply. - -IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY -FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY -DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND -NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND -THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE -MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - -GOVERNMENT USE: If you are acquiring this software on behalf of the -U.S. government, the Government shall have only "Restricted Rights" -in the software and related documentation as defined in the Federal -Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you -are acquiring the software on behalf of the Department of Defense, -the software shall be classified as "Commercial Computer Software" -and the Government shall have only "Restricted Rights" as defined -in Clause 252.227-7013 (c)(1) of DFARs. Notwithstanding the foregoing, -the authors grant the U.S. Government and others acting in its behalf -permission to use and distribute the software in accordance with the -terms specified in this license. diff --git a/test/integration/testdata/offline-install/assets/Another-IntegrationTest/installer_config.json b/test/integration/testdata/offline-install/assets/Another-IntegrationTest/installer_config.json deleted file mode 100644 index 3fd0854480..0000000000 --- a/test/integration/testdata/offline-install/assets/Another-IntegrationTest/installer_config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "org_name": "ActiveState-Test", - "project_id": "00000000-0000-0000-0000-000000000000", - "project_name": "Another-IntegrationTest", - "commit_id": "00000000-0000-0000-0000-000000000000" -} \ No newline at end of file diff --git a/test/integration/testdata/offline-install/assets/IntegrationTest/LICENSE.txt b/test/integration/testdata/offline-install/assets/IntegrationTest/LICENSE.txt deleted file mode 100644 index f406868075..0000000000 --- a/test/integration/testdata/offline-install/assets/IntegrationTest/LICENSE.txt +++ /dev/null @@ -1,563 +0,0 @@ -ACTIVESTATE® COMMUNITY EDITION LICENSE AGREEMENT - -Version effective date: March 25, 2019 - -This license agreement (the “Agreement”) is made between you (either -an individual or a company or organization, not including its -affiliates or wholly owned subsidiaries) (“You”) and ActiveState -Software Inc. (“ActiveSstate”). This Agreement establishes the terms -under which ActiveState will license the Software (as defined below) -to You and establishes the terms under which You may use, copy, -modify, distribute, and/or Redistribute (as defined below) the -Software. This Agreement does not apply to Maintenance and Support, -anything Beyond Development Use, OEM Distribution (all such -capitalized terms as defined below) each of which requires a separate -agreement with ActiveState. For more information on these types of -agreements, please visit www.activestate.com. The intent of this -Agreement is to allow ActiveState to maintain control over the -development and distribution of the Software while allowing its use in -a variety of ways. If the terms and conditions of this Agreement do -not permit Your proposed use of the Software or if You require -clarification regarding the scope of Your intended use of the -Software, please contact sales@activestate.com. - -PLEASE READ THIS AGREEMENT CAREFULLY BEFORE INSTALLING OR USING THE -SOFTWARE. BY CLICKING ON “YES, ACCEPT” OR BY INSTALLING THE SOFTWARE, -YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. -IF YOU ARE ENTERING INTO THIS AGREEMENT ON BEHALF OF A PERSON, YOUR -ACCEPTANCE REPRESENTS THAT YOU HAVE THE AUTHORITY TO BIND SUCH PERSON -TO THE TERMS AND CONDITIONS OF THIS AGREEMENT, IN WHICH CASE “YOU” OR -“YOUR” WILL REFER TO THE PERSON ON BEHALF OF WHICH YOU ACT (“YOUR -ENTITY”). IF YOU DO NOT AGREE WITH THE TERMS AND CONDITIONS OF THIS -AGREEMENT OR IF YOU DO NOT HAVE THE AUTHORITY TO BIND YOUR ENTITY, YOU -HAVE NO RIGHT TO INSTALL OR USE THE SOFTWARE AND YOU SHOULD (A) -RETURN, DELETE, OR DISABLE THE SOFTWARE OR (B) IF YOU PURCHASED A -PRODUCT FROM ACTIVESTATE OR ITS RESELLER OR DISTRIBUTOR ON WHICH THE -SOFTWARE IS PRE-INSTALLED BY ACTIVESTATE, RETURN THE PURCHASED PRODUCT -TO ACTIVESTATE OR THE APPLICABLE RESELLER OR DISTRIBUTOR FROM WHOM YOU -OBTAINED THE PRODUCT. - -1. Definitions. - -“Accessible Code” means source code contained within the Software that -is licensed under an open source license. - -“Confidential Information” means all information designated in writing -as confidential by each party, or which under the circumstances of -disclosure reasonably ought to be considered as confidential. Without -limiting the foregoing, ActiveState Confidential Information includes -the Software, including all source and object code, and all associated -documentation, but not Accessible Code. - -"Maintenance And Support" means maintenance and support for the -Software provided by ActiveState under separate terms. - -"OEM Distribution" means any distribution to, and/or use of the -Software by, others outside Your organization and distribution and/or -use of the Software as either a bundled add-on to, or embedded -component of another application, with such application being made -available to its users as, but not limited to, an on-premises -application, a hosted application, a software-as-a-service offering or -a subscription service for which the distributor of the application -receives a license fee or any form of direct or indirect compensation -and whether for commercial or non-commercial purposes. - -“Person” means any individual, sole proprietorship, partnership, firm, -entity, unincorporated association, unincorporated syndicate, -unincorporated organization, trust, body corporate or governmental or -regulatory authority, and where the context requires, any of the -foregoing when they are acting as trustee, executor, administrator or -other legal representative. - -“Beyond Development Use” means any use of the Software licensed under -this Agreement beyond software development with the Software. For -greater clarity, any use of the Software licensed under this Agreement -beyond the purpose of developing, prototyping or demonstrating Your -application with the Software or by the Software are not permitted -under this license. - -“Redistribute” means any distribution to, and/or use of the Software -by, others inside or outside Your organization and distribution and/or -use of the Software inside or outside Your organization. - -“Software” means any of ActivePerl, ActivePython, ActiveTcl, ActiveGo, -ActiveRuby, ActiveNode, or ActiveLua software and the accompanying -materials including, but not limited to, source code, binary -executables, documentation, images and scripts, which are distributed -by ActiveState, and derivatives of that collection and/or those files. - -“User Data” means all information and data collected by the Software -or otherwise transmitted by the Software to ActiveState, including any -data, metadata, metrics, statistics, or other information relating to -the performance, operations, resource, health, or other conditions of -the Software, any component thereof (including third party -components), and any related infrastructure, such as network host -names, IP addresses, interpreter used, and system architecture, which -includes filenames, full path, file size, and content hash. - -“Wrapped Application” means a single-file executable in which all -binary components are encapsulated in a single binary without exposing -the base programming language as a scripting language within Your own -application program to end users. - -2. License Grant. - - (a) Subject to the terms and conditions of this Agreement, - ActiveState hereby grants to You a limited, worldwide, - perpetual, paid up, free-of-charge, non-exclusive, - non-transferable, non-assignable, and non-sublicensable - license to install and use the Software on any computing - device, in accordance with the limitations and restrictions - set forth in this Agreement, for research and development - purposes only. You may not use the Software Beyond Development - Use, except as provided in Section 2(b) below. You may not - use the Software for OEM Distribution. You may copy the Software - for archival purposes or as necessary to use the Software as - authorized in this section. You also may modify the - Accessible Code to develop bug fixes, customizations, or - additional features, for the sole purpose of using the Software - as authorized by this Agreement. - - (b) ActiveState may, in its sole discretion, grant You the right to - use the Software Beyond Development Use and/or OEM Distribution - for limited, small-scale, non-commercial and/or open source - projects. To apply for this right, contact sales@activestate.com. - Without the prior approval of ActiveState, you may not use the - Software Beyond Development Use and/or for OEM Distribution. - -3. Restrictions. - - (a) Except as expressly provided in this Agreement, You may not: - - (i) transfer, assign, sublicense, resell, or rent the - Software; - (ii) modify or translate the Software to discover the source - code in the Software or create a functional equivalent in - the Software; - (iii) reverse engineer, decompile, or disassemble (except as - and only to the extent this restriction is prohibited by - applicable law) the Software; - (iv) create derivative works based on the Software; - (v) merge the Software with another product; - (vi) copy the Software; - (vii) remove or obscure any proprietary rights notices or - labels on the Software; - (viii) Redistribute, without entering into a separate agreement - with ActiveState: - I. the Software as a whole, whether as a Wrapped - Application or on a standalone basis; - II. parts of the Software to create a language - distribution; or - III. the Software (other than the Accessible Code) with - Your Wrapped Application; - (ix) distribute the Software by OEM Distribution without - entering into a separate OEM Distribution agreement with - ActiveState; - (x) permit others to use the Software; or - (xi) use the Software: - I. Beyond Development Use on any computing device in - whatever form or manner, whether physical or - virtual and external or internal-facing; - II. on any operating systems other than Windows, OSX, and - Linux; - III. on computing devices used for file and/or application - serving; - IV. on any computing devices used for business continuity - and disaster recovery; or - V. to provide content or functionality through - external-facing servers or internal-facing - production servers. - -4. Confidentiality. - - (a) Except as reasonably required to exercise Your rights under - this Agreement, You agree to prevent any unauthorized copying, - use, distribution, installation or transfer of possession of - Confidential Information received from ActiveState (the - “ACTIVESTATE CONFIDENTIAL INFORMATION”). You do not acquire - any interest in any ActiveState Confidential Information by - reason of this Agreement. ActiveState Confidential Information - does not include any information which (i) becomes part of the - public domain through no act or omission on Your part; (ii) is - lawfully acquired by You from a third party without any breach - of confidentiality; (iii) is independently developed by You - without reference to the ActiveState Confidential Information; - or (iv) is disclosed in accordance with judicial or other - governmental order or timely disclosure requirements imposed - by law or stock exchange policies. Notwithstanding the - foregoing, either party may disclose the terms and conditions - of this Agreement in conjunction with legal proceedings. - Without limiting the generality of the foregoing, You must - take reasonable steps to prevent any personnel from removing - any proprietary or other legend or restrictive notice - contained or included in any material provided by ActiveState - to You. - - (b) You acknowledge that any use or disclosure of the ActiveState - Confidential Information in a manner inconsistent with the - provisions of this Agreement may cause ActiveState irreparable - damage for which remedies other than injunctive relief may be - inadequate. You further agree that ActiveState will be - entitled to attempt to receive from a court of competent - jurisdiction injunctive or other equitable relief to restrain - such use or disclosure in addition to other appropriate - remedies. - -5. Open Source Acknowledgement. The Software is comprised of open - source software, which is subject to the terms of the open source - software license(s) accompanying or otherwise applicable to that - open source software included in the Software (the “Open Source - Components”). For reference, Tcl/Tk open source license terms can - be found in Exhibit A attached to this Agreement or obtained from - this link: https://www.tcl.tk/software/tcltk/license.html. You - acknowledge that Your own distribution or deployment of instances - containing or linking to the Software, including the Open Source - Components, or any other open source software may trigger open - source license requirements for which You are responsible. Nothing - in this Agreement limits Your rights under or grants rights to You - that supersede the terms of any applicable open source software - license for the applicable Open Source Components. - -6. Intellectual Property Ownership. All right, title and interest in - and to the Software and all intellectual property rights embodied - therein, including copyrights, trade names, trademarks, service - marks, product names, trade secrets embodied in the Software's - design and coding methodology and other proprietary materials in - the Software belong exclusively to ActiveState or its third party - licensors. The Software is protected by Canada and United States - copyright laws and international treaty provisions as implemented - locally in different jurisdictions. Except as specifically - provided under this Agreement, You acknowledge that no other - right, title or interest in and to the Software or any parts - thereof is granted to You. ActiveState grants You the limited - right to use the trade names, trademarks, service marks or product - names of ActiveState as required for reasonable and customary use - in describing the origin of the Software. You may not use the - trade names, trademarks, service marks or product names of - ActiveState in any way that might state or imply that ActiveState - endorses Your work, or might state or imply that You created the - Software. - -7. User Data. You acknowledge, agree, and expressly consent to - ActiveState’s collection of Your User Data through the Software. - ActiveState does not claim ownership of any User Data. You hereby - grant to ActiveState and its sublicensees a royalty-free, - perpetual, irrevocable, transferable, worldwide non-exclusive - right to reproduce, analyse, review, process, diagnose, or - otherwise use the User Data (in whole or in part) for the purpose - of supporting, maintaining, and providing the Software, the - Maintenance and Support, if any, and any related services provided - by ActiveState relating to the Software (the “User Data License”). - ActiveState will not disclose the User Data to any third parties - and will only use the User Data in accordance with the User Data - License, except that ActiveState may provide Your User Data to - third parties providing services relating to the Software to - ActiveState (which will protect the User Data on terms and - conditions that are commensurate in scope with this Agreement). In - addition to the rights granted under the User Data License, You - acknowledge and agree that ActiveState has the right to (i) - publicly disclose, in any manner whatsoever, User Data that have - been anonymized; and (ii) review or analyze the User Data and - publicly disclose any results of such review or analysis, - including in the form of reports, blog posts, newsletters, - marketing materials, or otherwise, provided You will not be - identified in such publicly disclosed materials. - -8. Term. This Agreement will be effective upon Your agreement to be - bound by the terms and conditions of this Agreement and will - continue in effect unless otherwise terminated in accordance with - the terms and conditions of this Agreement. - -9. Termination. If You breach any term or condition of this - Agreement, ActiveState may immediately terminate this Agreement - with respect to the Software that You have licensed under this - Agreement by providing notice to You. ActiveState may also - terminate this Agreement, without any liability to You, if any - law, regulations, orders, or legal requirements prohibits - ActiveState’s provision or licensing of the Software to You. Upon - termination of this Agreement by ActiveState, You will immediately - cease all use of the Software and return all copies of the - Software that are under Your control to ActiveState or to delete - all such copies. - -10. Infringement Indemnification. You indemnify, hold harmless, and - defend ActiveState, its licensors, and their respective employees, - agents and distributors against any and all claims, proceedings, - demands and costs resulting from or in any way connected with Your - use of the Software and arising from Your breach of this - Agreement; provided, however, that ActiveState will notify You in - writing of any such claim. ActiveState will not enter into any - settlement or compromise any such claim without Your prior written - consent. You will have sole control of any such action and - settlement negotiations; and ActiveState will provide You with - commercially reasonable information and assistance, at Your - request and expense, necessary to settle or defend such claim. - -11. Disclaimer Of Warranty. - - (a) NEITHER ACTIVESTATE NOR ANY OF ITS SUPPLIERS, LICENSORS, OR - RESELLERS MAKES ANY WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - UNDER THIS AGREEMENT. TO THE MAXIMUM EXTENT PERMITTED UNDER - APPLICABLE LAW, ACTIVESTATE AND ITS SUPPLIERS, LICENSORS, AND - RESELLERS SPECIFICALLY DISCLAIM ALL WARRANTIES AND CONDITIONS - WITH RESPECT TO THE SOFTWARE, EITHER EXPRESS, IMPLIED OR - STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OR - CONDITION OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, - DURABILITY, MERCHANTABLE QUALITY, FITNESS FOR A PARTICULAR - PURPOSE, UPDATES, UPGRADES, SUPPLEMENTS, PRODUCTS, APPLIANCES, - SYSTEM INTEGRATION, DATA ACCURACY AND ANY OTHER ITEMS PROVIDED - HEREUNDER. ACTIVESTATE MAKES NO WARRANTY OR GUARANTEE THAT THE - OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED, ERROR-FREE, - OR VIRUS-FREE, OR THAT THE SOFTWARE WILL MEET ANY PARTICULAR - CRITERIA OF PERFORMANCE, QUALITY, ACCURACY, PURPOSE, OR NEED. - YOU ASSUME THE ENTIRE RISK OF SELECTION, INSTALLATION, AND USE - OF THE SOFTWARE. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN - ESSENTIAL PART OF THIS AGREEMENT. - - (b) TO THE EXTENT ANY IMPLIED WARRANTIES CANNOT BE DISCLAIMED - UNDER APPLICABLE LAW, ANY IMPLIED WARRANTIES ARE LIMITED IN - DURATION TO THE PERIOD REQUIRED BY APPLICABLE LAW. - - (c) SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED - WARRANTIES OR LIMITATIONS ON APPLICABLE STATUTORY RIGHTS OF A - CONSUMER, AND SO SOME OR ALL OF THE EXCLUSION OF IMPLIED - WARRANTIES OR LIMITATIONS SET OUT IN THIS SECTION MAY NOT - APPLY TO YOU. - -12. Limitation Of Liability. - - (a) INDEPENDENT OF THE FOREGOING PROVISIONS, TO THE MAXIMUM EXTENT - PERMITTED UNDER APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL - THEORY, INCLUDING WITHOUT LIMITATION, TORT, CONTRACT, OR - STRICT PRODUCTS LIABILITY, WILL ACTIVESTATE, ITS DIRECTORS, - OFFICERS, EMPLOYEES, AFFILIATES, AGENTS, CONTRACTORS, - PRINCIPALS, SUPPLIERS OR LICENSORS BE LIABLE TO YOU OR ANY - OTHER PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR - CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING WITHOUT - LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, - COMPUTER MALFUNCTION, OR ANY OTHER KIND OF COMMERCIAL DAMAGE, - EVEN IF ACTIVESTATE HAS BEEN ADVISED OF THE POSSIBILITY OF - SUCH DAMAGES. - - (b) TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE LAW, IN NO - EVENT WILL ACTIVESTATE BE LIABLE TO YOU FOR DAMAGES UNDER THIS - AGREEMENT FOR ANY CAUSE WHATSOEVER, AND REGARDLESS OF THE FORM - OF ACTION. - -13. Export Controls. You must comply with all export laws and - restrictions and regulations of Canada, the United States or - foreign agencies or authorities, and not to export or re-export - the Software or any direct product thereof in violation of any - such restrictions, laws or regulations, or without all necessary - approvals. As applicable, each party will obtain and bear all - expenses relating to any necessary licenses and/or exemptions with - respect to its own export of the Software from Canada or the U.S. - Neither the Software nor the underlying information or technology - may be electronically transmitted or otherwise exported or - re-exported: into any country subject to Canada or U.S. trade - sanctions covering the Software, to individuals or entities - controlled by such countries, or to nationals or residents of such - countries other than nationals who are lawfully admitted permanent - residents of countries not subject to such sanctions; to anyone on - Canada's Area Control List of the Export and Import Permits Act; - or to anyone on the U.S. Treasury Department's list of Specially - Designated Nationals and Blocked Persons or the U.S. Commerce - Department's Table of Denial Orders. By installing or using the - Software, You agree to the foregoing and represent and warrant - that it complies with these conditions. - -14. U.S. Government End-Users. The Software is a "commercial item," as - that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of - "commercial computer software" and "commercial computer software - documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. - 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 - through 227.7202-4 (June 1995), all U.S. Government End Users - acquire the Software with only those rights as are granted to - all other end users pursuant to the terms and conditions herein. - Unpublished rights are reserved under the copyright laws of Canada - and the United States. - -15. Licensee Outside The U.S. If You are located outside the U.S., - then the following provisions will apply: (a) Les parties aux - presentes confirment leur volonte que cette convention de meme que - tous les documents y compris tout avis qui siy rattache, soient - rediges en langue anglaise (translation: "The parties confirm that - this Agreement and all related documentation is and will be in the - English language."); and (b) You are responsible for complying - with any local laws in Your jurisdiction which might impact Your - right to import, export or use the Software, and You represent - that You have complied with any regulations or registration - procedures required by applicable law to make this license - enforceable. - -16. Entire Agreement. This Agreement constitutes the entire - understanding of the parties with respect to the subject matter of - this Agreement and merges all prior communications, - representations, and agreements. - -17. Severability. If any provision of this Agreement is declared - invalid or unenforceable, such provision will be deemed modified - to the extent necessary and possible to render it valid and - enforceable. In any event, the unenforceability or invalidity of - any provision will not affect any other provision of this - Agreement, and this Agreement will continue in full force and - effect, and be construed and enforced, as if such provision had - not been included, or had been modified as above provided, as the - case may be. - -18. Entire Agreement & Amendment. This Agreement constitutes the - complete agreement between the parties and supersedes all prior or - contemporaneous agreements or representations, written or oral, - concerning the subject matter of this Agreement, appendices and - attachments. ActiveState reserves the right to change this - Agreement at any time, which change shall be effective as of the - effective date for the terms and conditions of this Agreement as - shown on ActiveState’s Website (the “Change Effective Date”). Your - continued use of the Software after the Change Effective Date - constitutes Your acceptance of such changes. This Agreement may - not be otherwise amended without ActiveState's prior written - agreement. You agree to periodically review the terms and - conditions of this Agreement as updated from time to time on - ActiveState’s website. - -19. Arbitration. Except for actions to protect intellectual property - rights and to enforce an arbitrator's decision hereunder, all - disputes, controversies, or claims arising out of or relating to - this Agreement or a breach thereof will be submitted to and be - finally resolved by arbitration under the rules of the American - Arbitration Association ("AAA") then in effect. There will be one - arbitrator, and such arbitrator will be chosen by mutual agreement - of the parties in accordance with AAA rules. The arbitration will - take place in Vancouver, BC, Canada, and may be conducted by - telephone or online. The arbitrator will apply the laws of the - Province of British Columbia, Canada to all issues in dispute. The - controversy or claim will be arbitrated on an individual basis, - and will not be consolidated in any arbitration with any claim or - controversy of any other party. The findings of the arbitrator - will be final and binding on the parties, and may be entered in - any court of competent jurisdiction for enforcement. Enforcements - of any award or judgment will be governed by the United Nations - Convention on the Recognition and Enforcement of Foreign Arbitral - Awards. Should either party file an action contrary to this - provision, the other party may recover legal fees and costs up to - $1,000.00. - -20. Jurisdiction And Venue. The superior courts of Vancouver in the - Province of British Columbia, Canada will be the exclusive - jurisdiction and venue for all legal proceedings that are not - arbitrated under this Agreement. - -21. Force Majeure. Neither party will be liable for damages for any - delay or failure of delivery arising out of causes beyond their - reasonable control and without their fault or negligence, - including, but not limited to, Acts of God, acts of civil or - military authority, fires, riots, wars, embargoes, Internet - disruptions, hacker attacks, or communications failures. - Notwithstanding anything to the contrary contained herein, if - either party is unable to perform hereunder for a period of - thirty (30) consecutive days, then the other party may terminate - this Agreement immediately without liability by ten (10) days’ - written notice to the other. - -22. Publicity And Audit Rights. - - (a) You grant ActiveState the right to include Your name, trade - name, trademark, service mark or logo in its Software - promotional material. You may retract this grant at any time - in writing to marcom@activestate.com, requesting Your name, - trade name, trademark, service mark or logo be excluded from - future releases of ActiveState Software promotional material. - Requests cannot be complied with retroactively and may require - up to thirty (30) days to process. - - (b) If You entered into this Agreement on behalf of a Person, - where such Person has more than 100 employees, if requested by - ActiveState, You will furnish ActiveState with a signed - certification (i) verifying that the Software is being used - pursuant to the terms of this Agreement, including any user - limitations and (ii) listing the locations where the Software - is being used, the version(s) of the Software being used, how - long and how the Software is being used, and the number - computing devices and operating systems the Software is being - used with. You agree to grant ActiveState reasonable access to - Your site(s) and/or systems, upon prior notice during normal - business hours, to audit the use of the Software. Any such - audit shall be at ActiveState’s expense. - -23. Assignment. Except as expressly provided herein, neither this - Agreement nor any rights granted hereunder, nor the use of any of - the Software may be assigned, or otherwise transferred, in whole - or in part, by You, without the prior written consent of - ActiveState. Any permitted assignment by You under this Section - will be conditional upon You delivering all copies of the Software - to the transferee along with a copy of this Agreement, the - transferee accepting the terms and conditions of this Agreement, - and Your license to the Software terminating upon transfer. Any - attempted assignment by You will be void and of no effect unless - permitted by the foregoing. You acknowledge and agree that - ActiveState may assign this Agreement to any third party without - Your prior consent. - -24. Enurement. This Agreement will enure to the benefit of the - parties’ permitted successors and assigns. - -25. Governing Law. This Agreement will be construed under the laws of - the Province of British Columbia and the federal laws of Canada - applicable therein, without regard to the conflict of law rules. - The application of the United Nations Convention of Contracts for - the International Sale of Goods and any local implementations - thereof are expressly excluded. The parties agree that the Uniform - Computer - -Transactions Act or any version thereof, adopted by any jurisdiction, -in any form ("UCITA"), will not apply to this Agreement, and to the -extent that UCITA may be applicable, the parties agree to opt out of -the applicability of UCITA pursuant to the opt-out provision(s) -contained therein. - - - -EXHIBIT A - -Tcl/tk License Terms - - -This software is copyrighted by the Regents of the University of -California, Sun Microsystems, Inc., Scriptics Corporation, and other -parties. The following terms apply to all files associated with the -software unless explicitly disclaimed in individual files. - -The authors hereby grant permission to use, copy, modify, distribute, -and license this software and its documentation for any purpose, -provided that existing copyright notices are retained in all copies -and that this notice is included verbatim in any distributions. No -written agreement, license, or royalty fee is required for any of the -authorized uses. Modifications to this software may be copyrighted by -their authors and need not follow the licensing terms described here, -provided that the new terms are clearly indicated on the first page of -each file where they apply. - -IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY -FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY -DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND -NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND -THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE -MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - -GOVERNMENT USE: If you are acquiring this software on behalf of the -U.S. government, the Government shall have only "Restricted Rights" -in the software and related documentation as defined in the Federal -Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you -are acquiring the software on behalf of the Department of Defense, -the software shall be classified as "Commercial Computer Software" -and the Government shall have only "Restricted Rights" as defined -in Clause 252.227-7013 (c)(1) of DFARs. Notwithstanding the foregoing, -the authors grant the U.S. Government and others acting in its behalf -permission to use and distribute the software in accordance with the -terms specified in this license. diff --git a/test/integration/testdata/offline-install/assets/IntegrationTest/installer_config.json b/test/integration/testdata/offline-install/assets/IntegrationTest/installer_config.json deleted file mode 100644 index 615cac0a65..0000000000 --- a/test/integration/testdata/offline-install/assets/IntegrationTest/installer_config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "org_name": "ActiveState-Test", - "project_id": "00000000-0000-0000-0000-000000000000", - "project_name": "IntegrationTest", - "commit_id": "00000000-0000-0000-0000-000000000000" -} diff --git a/test/integration/update_int_test.go b/test/integration/update_int_test.go index 5b3c8c1df2..9f0da8ea5c 100644 --- a/test/integration/update_int_test.go +++ b/test/integration/update_int_test.go @@ -51,13 +51,13 @@ func (suite *UpdateIntegrationTestSuite) env(disableUpdates, forceUpdate bool) [ env := []string{} if disableUpdates { - env = append(env, "ACTIVESTATE_CLI_DISABLE_UPDATES=true") + env = append(env, constants.DisableUpdates+"=true") } else { - env = append(env, "ACTIVESTATE_CLI_DISABLE_UPDATES=false") + env = append(env, constants.DisableUpdates+"=false") } if forceUpdate { - env = append(env, "ACTIVESTATE_FORCE_UPDATE=true") + env = append(env, constants.ForceUpdateEnvVarName+"=true") } dir, err := ioutil.TempDir("", "system*") @@ -296,7 +296,7 @@ func (suite *UpdateIntegrationTestSuite) testAutoUpdate(ts *e2e.Session, baseDir e2e.OptArgs("--version"), e2e.OptAppendEnv(suite.env(false, true)...), e2e.OptAppendEnv(fmt.Sprintf("HOME=%s", fakeHome)), - e2e.OptAppendEnv("ACTIVESTATE_TEST_AUTO_UPDATE=true"), + e2e.OptAppendEnv(constants.TestAutoUpdateEnvVarName + "=true"), } if opts != nil { spawnOpts = append(spawnOpts, opts...) @@ -361,7 +361,7 @@ func (suite *UpdateIntegrationTestSuite) TestAutoUpdateToCurrent() { suite.installLatestReleaseVersion(ts, installDir) - suite.testAutoUpdate(ts, installDir, e2e.OptAppendEnv(fmt.Sprintf("ACTIVESTATE_CLI_UPDATE_BRANCH=%s", constants.BranchName))) + suite.testAutoUpdate(ts, installDir, e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.UpdateBranchEnvVarName, constants.BranchName))) } func (suite *UpdateIntegrationTestSuite) TestUpdateToCurrent() { @@ -379,5 +379,5 @@ func (suite *UpdateIntegrationTestSuite) TestUpdateToCurrent() { suite.installLatestReleaseVersion(ts, installDir) - suite.testUpdate(ts, installDir, e2e.OptAppendEnv(fmt.Sprintf("ACTIVESTATE_CLI_UPDATE_BRANCH=%s", constants.BranchName))) + suite.testUpdate(ts, installDir, e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.UpdateBranchEnvVarName, constants.BranchName))) } diff --git a/test/integration/update_lock_int_test.go b/test/integration/update_lock_int_test.go index 889aa8d870..c7ccd9fb9e 100644 --- a/test/integration/update_lock_int_test.go +++ b/test/integration/update_lock_int_test.go @@ -106,6 +106,7 @@ func (suite *UpdateIntegrationTestSuite) TestLockedChannel() { cp = ts.SpawnWithOpts(e2e.OptArgs("--version"), e2e.OptAppendEnv(suite.env(true, false)...)) cp.Expect("This project is locked at State Tool version") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() return } }) @@ -161,6 +162,7 @@ func (suite *UpdateIntegrationTestSuite) TestUpdateLockedConfirmation() { cp.Expect("Cancelling") } cp.ExpectNotExitCode(0) + ts.IgnoreLogErrors() }) } } diff --git a/test/integration/use_int_test.go b/test/integration/use_int_test.go index 6745178f3d..d0a315528b 100644 --- a/test/integration/use_int_test.go +++ b/test/integration/use_int_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/ActiveState/cli/internal/config" + "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/subshell" @@ -35,7 +36,7 @@ func (suite *UseIntegrationTestSuite) TestUse() { // Use. cp = ts.SpawnWithOpts( e2e.OptArgs("use", "ActiveState-CLI/Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -55,7 +56,7 @@ func (suite *UseIntegrationTestSuite) TestUse() { // Use it. cp = ts.SpawnWithOpts( e2e.OptArgs("use", "ActiveState-CLI/Python-3.9"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -68,7 +69,7 @@ func (suite *UseIntegrationTestSuite) TestUse() { // Switch back using just the project name. cp = ts.SpawnWithOpts( e2e.OptArgs("use", "Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -82,6 +83,7 @@ func (suite *UseIntegrationTestSuite) TestUse() { cp = ts.SpawnWithOpts(e2e.OptArgs("use", "NotCheckedOut")) cp.Expect("Cannot find the NotCheckedOut project.") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *UseIntegrationTestSuite) TestUseCwd() { @@ -100,7 +102,7 @@ func (suite *UseIntegrationTestSuite) TestUseCwd() { cp = ts.SpawnWithOpts( e2e.OptArgs("use"), e2e.OptWD(pythonDir), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -113,6 +115,7 @@ func (suite *UseIntegrationTestSuite) TestUseCwd() { ) cp.Expect("Unable to use project") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() } func (suite *UseIntegrationTestSuite) TestReset() { @@ -131,7 +134,7 @@ func (suite *UseIntegrationTestSuite) TestReset() { cp = ts.SpawnWithOpts( e2e.OptArgs("use", "ActiveState-CLI/Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -152,6 +155,7 @@ func (suite *UseIntegrationTestSuite) TestReset() { cp.SendLine("n") cp.Expect("Reset aborted by user") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.SpawnWithOpts(e2e.OptArgs("use", "reset", "--non-interactive")) cp.Expect("Stopped using your project runtime") @@ -178,6 +182,7 @@ func (suite *UseIntegrationTestSuite) TestShow() { cp := ts.SpawnWithOpts(e2e.OptArgs("use", "show")) cp.Expect("No project is being used") cp.ExpectExitCode(1) + ts.IgnoreLogErrors() cp = ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Python3")) cp.Expect("Skipping runtime setup") @@ -186,7 +191,7 @@ func (suite *UseIntegrationTestSuite) TestShow() { cp = ts.SpawnWithOpts( e2e.OptArgs("use", "ActiveState-CLI/Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) @@ -236,7 +241,7 @@ func (suite *UseIntegrationTestSuite) TestSetupNotice() { cp := ts.SpawnWithOpts( e2e.OptArgs("checkout", "ActiveState-CLI/Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Setting Up Runtime") cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) @@ -253,7 +258,7 @@ func (suite *UseIntegrationTestSuite) TestSetupNotice() { cp = ts.SpawnWithOpts( e2e.OptArgs("use", "Python3"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect("Setting Up Runtime") cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) @@ -272,7 +277,7 @@ func (suite *UseIntegrationTestSuite) TestJSON() { cp = ts.SpawnWithOpts( e2e.OptArgs("use", "-o", "json"), - e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), + e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) cp.Expect(`"namespace":`, e2e.RuntimeSourcingTimeoutOpt) cp.Expect(`"path":`) diff --git a/version.txt b/version.txt index 29f03fbb86..944a0f00cc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.42.0-RC2 \ No newline at end of file +0.44.0-RC1 \ No newline at end of file