From d9811c7c589015b84a30ed229e3a5b23ad45128e Mon Sep 17 00:00:00 2001
From: Luke Watts <luke@snyk.io>
Date: Fri, 13 Dec 2024 11:50:36 +0100
Subject: [PATCH 1/2] spike: transfer data via filesystem for TS <=> Go

CLI-646
---
 cliv2/cmd/cliv2/main.go                | 45 +++++++++++++++++++++-----
 cliv2/pkg/basic_workflows/legacycli.go |  5 ++-
 src/cli/commands/test/index.ts         | 11 +------
 src/cli/main.ts                        |  6 ++++
 4 files changed, 48 insertions(+), 19 deletions(-)

diff --git a/cliv2/cmd/cliv2/main.go b/cliv2/cmd/cliv2/main.go
index 0a02fb75c3..c6394cab7e 100644
--- a/cliv2/cmd/cliv2/main.go
+++ b/cliv2/cmd/cliv2/main.go
@@ -22,19 +22,19 @@ import (
 	"github.com/snyk/cli-extension-sbom/pkg/sbom"
 	"github.com/snyk/cli/cliv2/internal/cliv2"
 	"github.com/snyk/cli/cliv2/internal/constants"
+	cli_errors "github.com/snyk/cli/cliv2/internal/errors"
+	"github.com/snyk/cli/cliv2/pkg/basic_workflows"
 	"github.com/snyk/container-cli/pkg/container"
+	"github.com/snyk/error-catalog-golang-public/snyk_errors"
 	"github.com/snyk/go-application-framework/pkg/analytics"
 	"github.com/snyk/go-application-framework/pkg/app"
 	"github.com/snyk/go-application-framework/pkg/configuration"
 	"github.com/snyk/go-application-framework/pkg/instrumentation"
-	"github.com/snyk/go-application-framework/pkg/local_workflows/network_utils"
-	"github.com/snyk/go-application-framework/pkg/local_workflows/output_workflow"
-	"github.com/spf13/cobra"
-	"github.com/spf13/pflag"
-
 	localworkflows "github.com/snyk/go-application-framework/pkg/local_workflows"
 	"github.com/snyk/go-application-framework/pkg/local_workflows/content_type"
 	"github.com/snyk/go-application-framework/pkg/local_workflows/json_schemas"
+	"github.com/snyk/go-application-framework/pkg/local_workflows/network_utils"
+	"github.com/snyk/go-application-framework/pkg/local_workflows/output_workflow"
 	"github.com/snyk/go-application-framework/pkg/networking"
 	"github.com/snyk/go-application-framework/pkg/runtimeinfo"
 	"github.com/snyk/go-application-framework/pkg/ui"
@@ -42,11 +42,10 @@ import (
 	"github.com/snyk/go-application-framework/pkg/workflow"
 	"github.com/snyk/go-httpauth/pkg/httpauth"
 	"github.com/snyk/snyk-iac-capture/pkg/capture"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
 
 	snykls "github.com/snyk/snyk-ls/ls_extension"
-
-	cli_errors "github.com/snyk/cli/cliv2/internal/errors"
-	"github.com/snyk/cli/cliv2/pkg/basic_workflows"
 )
 
 var internalOS string
@@ -621,6 +620,36 @@ func MainWithErrorCode() int {
 		cliAnalytics.GetInstrumentation().SetStatus(analytics.Failure)
 	}
 
+	// Load error out of run-errors.json
+	filePath := globalConfiguration.GetString(configuration.TEMP_DIR_PATH) + "/typescript-runtime-errors"
+
+	// Read the entire file into a byte slice
+	errDataFromFile, err := os.ReadFile(filePath)
+	if err != nil {
+		globalLogger.Printf("Failed to read file: %v", err)
+	}
+
+	// Convert the byte slice to a string and print it
+	fmt.Println(`LADEBUG`)
+	fmt.Println(string(errDataFromFile))
+	cliError := snyk_errors.Error{
+		ID:             uuid.NewString(),
+		Title:          "Unable to set environment",
+		Description:    "The specified environment cannot be used. As a result, the configuration remains unchanged.Provide the correct specifications for the environment and try again.",
+		StatusCode:     200,
+		ErrorCode:      string(errDataFromFile),
+		Classification: "ACTIONABLE",
+		Links: []string{
+			"https://docs.snyk.io/snyk-cli/commands/config-environment",
+		},
+		Level:  "error",
+		Detail: string(errDataFromFile),
+	}
+	cliAnalytics.GetInstrumentation().AddError(
+		cliError,
+	)
+	// END error loading
+
 	if !globalConfiguration.GetBool(configuration.ANALYTICS_DISABLED) {
 		sendAnalytics(cliAnalytics, globalLogger)
 	}
diff --git a/cliv2/pkg/basic_workflows/legacycli.go b/cliv2/pkg/basic_workflows/legacycli.go
index bae4e7e1f9..41a2a95603 100644
--- a/cliv2/pkg/basic_workflows/legacycli.go
+++ b/cliv2/pkg/basic_workflows/legacycli.go
@@ -110,7 +110,10 @@ func legacycliWorkflow(
 	if len(apiToken) == 0 {
 		apiToken = "random"
 	}
-	cli.AppendEnvironmentVariables([]string{constants.SNYK_API_TOKEN_ENV + "=" + apiToken})
+	cli.AppendEnvironmentVariables([]string{
+		constants.SNYK_API_TOKEN_ENV + "=" + apiToken,
+		"SNYK_TEMP_DIR_PATH=" + config.GetString(configuration.TEMP_DIR_PATH),
+	})
 
 	err = cli.Init()
 	if err != nil {
diff --git a/src/cli/commands/test/index.ts b/src/cli/commands/test/index.ts
index 39a28b9f32..f3d0844cc4 100644
--- a/src/cli/commands/test/index.ts
+++ b/src/cli/commands/test/index.ts
@@ -295,16 +295,7 @@ export default async function test(
 
   if (notSuccess) {
     response += chalk.bold.red(summaryMessage);
-    const error = new Error(response) as any;
-    // take the code of the first problem to go through error
-    // translation
-    // HACK as there can be different errors, and we pass only the
-    // first one
-    error.code = errorResults[0].code;
-    error.userMessage = errorResults[0].userMessage;
-    error.strCode = errorResults[0].strCode;
-    error.innerError = errorResults[0].innerError;
-    throw error;
+    throw errorResults[0];
   }
 
   if (foundVulnerabilities) {
diff --git a/src/cli/main.ts b/src/cli/main.ts
index b03d6f1d2c..e831a4895d 100755
--- a/src/cli/main.ts
+++ b/src/cli/main.ts
@@ -195,6 +195,12 @@ async function handleError(args, error) {
     analytics.add('error', true);
     analytics.add('command', args.command);
   } else {
+    const errorFilePath = pathLib.join(process.env.SNYK_TEMP_DIR_PATH || getFullPath(''), './typescript-runtime-errors');
+
+    // TODO: If error is NOT valid error catalog message transform to match structure
+    await saveJsonResultsToFile(JSON.stringify(
+      analyticsError,
+    ), errorFilePath);
     analytics.add('error-message', analyticsError.message);
     // Note that error.stack would also contain the error message
     // (see https://nodejs.org/api/errors.html#errors_error_stack)

From 54a78f040eae8fef639983fe86d883ba0ab6bce5 Mon Sep 17 00:00:00 2001
From: CatalinSnyk <catalin.iuga@snyk.io>
Date: Fri, 20 Dec 2024 17:04:22 +0200
Subject: [PATCH 2/2] chore: cleaning up main and moving stuff around

---
 cliv2/cmd/cliv2/main.go                | 31 --------------------------
 cliv2/internal/cliv2/cliv2.go          | 18 +++++++++++++++
 cliv2/pkg/basic_workflows/legacycli.go |  5 +----
 src/cli/ipc.ts                         | 22 ++++++++++++++++++
 src/cli/main.ts                        | 11 ++++-----
 5 files changed, 45 insertions(+), 42 deletions(-)
 create mode 100644 src/cli/ipc.ts

diff --git a/cliv2/cmd/cliv2/main.go b/cliv2/cmd/cliv2/main.go
index c6394cab7e..97bc91ead8 100644
--- a/cliv2/cmd/cliv2/main.go
+++ b/cliv2/cmd/cliv2/main.go
@@ -25,7 +25,6 @@ import (
 	cli_errors "github.com/snyk/cli/cliv2/internal/errors"
 	"github.com/snyk/cli/cliv2/pkg/basic_workflows"
 	"github.com/snyk/container-cli/pkg/container"
-	"github.com/snyk/error-catalog-golang-public/snyk_errors"
 	"github.com/snyk/go-application-framework/pkg/analytics"
 	"github.com/snyk/go-application-framework/pkg/app"
 	"github.com/snyk/go-application-framework/pkg/configuration"
@@ -620,36 +619,6 @@ func MainWithErrorCode() int {
 		cliAnalytics.GetInstrumentation().SetStatus(analytics.Failure)
 	}
 
-	// Load error out of run-errors.json
-	filePath := globalConfiguration.GetString(configuration.TEMP_DIR_PATH) + "/typescript-runtime-errors"
-
-	// Read the entire file into a byte slice
-	errDataFromFile, err := os.ReadFile(filePath)
-	if err != nil {
-		globalLogger.Printf("Failed to read file: %v", err)
-	}
-
-	// Convert the byte slice to a string and print it
-	fmt.Println(`LADEBUG`)
-	fmt.Println(string(errDataFromFile))
-	cliError := snyk_errors.Error{
-		ID:             uuid.NewString(),
-		Title:          "Unable to set environment",
-		Description:    "The specified environment cannot be used. As a result, the configuration remains unchanged.Provide the correct specifications for the environment and try again.",
-		StatusCode:     200,
-		ErrorCode:      string(errDataFromFile),
-		Classification: "ACTIONABLE",
-		Links: []string{
-			"https://docs.snyk.io/snyk-cli/commands/config-environment",
-		},
-		Level:  "error",
-		Detail: string(errDataFromFile),
-	}
-	cliAnalytics.GetInstrumentation().AddError(
-		cliError,
-	)
-	// END error loading
-
 	if !globalConfiguration.GetBool(configuration.ANALYTICS_DISABLED) {
 		sendAnalytics(cliAnalytics, globalLogger)
 	}
diff --git a/cliv2/internal/cliv2/cliv2.go b/cliv2/internal/cliv2/cliv2.go
index 3a6bc13433..be9346dab4 100644
--- a/cliv2/internal/cliv2/cliv2.go
+++ b/cliv2/internal/cliv2/cliv2.go
@@ -13,6 +13,7 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"regexp"
 	"slices"
 	"strings"
@@ -58,6 +59,8 @@ const (
 	V2_ABOUT   Handler = iota
 )
 
+const configKeyErrFile = "INTENAL_MY_ERR_FILE"
+
 func NewCLIv2(config configuration.Configuration, debugLogger *log.Logger, ri runtimeinfo.RuntimeInfo) (*CLI, error) {
 	cacheDirectory := config.GetString(configuration.CACHE_PATH)
 
@@ -351,6 +354,8 @@ func PrepareV1EnvironmentVariables(
 // Fill environment variables for the legacy CLI from the given configuration.
 func fillEnvironmentFromConfig(inputAsMap map[string]string, config configuration.Configuration, args []string) {
 	inputAsMap[constants.SNYK_INTERNAL_ORGID_ENV] = config.GetString(configuration.ORGANIZATION)
+	// TODO: pull into constants
+	inputAsMap["SNYK_ERR_FILE"] = config.GetString(configKeyErrFile)
 
 	if config.GetBool(configuration.PREVIEW_FEATURES_ENABLED) {
 		inputAsMap[constants.SNYK_INTERNAL_PREVIEW_FEATURES_ENABLED] = "1"
@@ -402,6 +407,9 @@ func (c *CLI) executeV1Default(proxyInfo *proxy.ProxyInfo, passThroughArgs []str
 		defer cancel()
 	}
 
+	filePath := filepath.Join(c.globalConfig.GetString(configuration.TEMP_DIR_PATH), fmt.Sprintf("err-file-%d", time.Now().Nanosecond()))
+	c.globalConfig.Set(configKeyErrFile, filePath)
+
 	snykCmd, err := c.PrepareV1Command(ctx, c.v1BinaryLocation, passThroughArgs, proxyInfo, c.GetIntegrationName(), GetFullVersion())
 
 	if c.DebugLogger.Writer() != io.Discard {
@@ -447,6 +455,16 @@ func (c *CLI) executeV1Default(proxyInfo *proxy.ProxyInfo, passThroughArgs []str
 	if errors.Is(ctx.Err(), context.DeadlineExceeded) {
 		return ctx.Err()
 	}
+
+	errDataFromFile, fileErr := os.ReadFile(filePath)
+	if fileErr != nil {
+		c.DebugLogger.Printf("Failed to read file: %v", fileErr)
+	}
+
+	c.DebugLogger.Println(`LADEBUG`)
+	c.DebugLogger.Println(string(errDataFromFile))
+	// TODO: wrap this in a custom struct/err format that we can use in the
+
 	return err
 }
 
diff --git a/cliv2/pkg/basic_workflows/legacycli.go b/cliv2/pkg/basic_workflows/legacycli.go
index 41a2a95603..bae4e7e1f9 100644
--- a/cliv2/pkg/basic_workflows/legacycli.go
+++ b/cliv2/pkg/basic_workflows/legacycli.go
@@ -110,10 +110,7 @@ func legacycliWorkflow(
 	if len(apiToken) == 0 {
 		apiToken = "random"
 	}
-	cli.AppendEnvironmentVariables([]string{
-		constants.SNYK_API_TOKEN_ENV + "=" + apiToken,
-		"SNYK_TEMP_DIR_PATH=" + config.GetString(configuration.TEMP_DIR_PATH),
-	})
+	cli.AppendEnvironmentVariables([]string{constants.SNYK_API_TOKEN_ENV + "=" + apiToken})
 
 	err = cli.Init()
 	if err != nil {
diff --git a/src/cli/ipc.ts b/src/cli/ipc.ts
new file mode 100644
index 0000000000..065e9d945b
--- /dev/null
+++ b/src/cli/ipc.ts
@@ -0,0 +1,22 @@
+import { writeFile } from 'fs/promises';
+import { ProblemError } from '@snyk/error-catalog-nodejs-public';
+
+// TODO: handle more gracefully
+const ERROR_FILE_PATH = process.env.SNYK_ERR_FILE!;
+
+export async function sendError(err: Error): Promise<void> {
+  const data = serializeError(err);
+  return await writeFile(ERROR_FILE_PATH, data);
+}
+
+// TBD: json format that the erorr get sent as (ProblemErrorJson, JSON API, custom one?)
+function serializeError(err: Error): string {
+  // @ts-expect-error Using this instead of 'instanceof' since the error might be caught from external CLI plugins.
+  // See: https://github.com/snyk/error-catalog/blob/main/packages/error-catalog-nodejs/src/problem-error.ts#L17-L19
+  if (err.isErrorCatalogError) {
+    // NOTE: there's also the JSON API version for this.
+    return JSON.stringify((err as ProblemError).toProblemJson('instance?'));
+  }
+
+  return JSON.stringify(err, Object.getOwnPropertyNames(err));
+}
diff --git a/src/cli/main.ts b/src/cli/main.ts
index e831a4895d..d2a1c655e2 100755
--- a/src/cli/main.ts
+++ b/src/cli/main.ts
@@ -48,6 +48,8 @@ import { SarifFileOutputEmptyError } from '../lib/errors/empty-sarif-output-erro
 import { InvalidDetectionDepthValue } from '../lib/errors/invalid-detection-depth-value';
 import { obfuscateArgs } from '../lib/utils';
 import { EXIT_CODES } from './exit-codes';
+import { sendError } from './ipc';
+
 const isEmpty = require('lodash/isEmpty');
 
 const debug = Debug('snyk');
@@ -195,12 +197,7 @@ async function handleError(args, error) {
     analytics.add('error', true);
     analytics.add('command', args.command);
   } else {
-    const errorFilePath = pathLib.join(process.env.SNYK_TEMP_DIR_PATH || getFullPath(''), './typescript-runtime-errors');
-
-    // TODO: If error is NOT valid error catalog message transform to match structure
-    await saveJsonResultsToFile(JSON.stringify(
-      analyticsError,
-    ), errorFilePath);
+    sendError(error);
     analytics.add('error-message', analyticsError.message);
     // Note that error.stack would also contain the error message
     // (see https://nodejs.org/api/errors.html#errors_error_stack)
@@ -220,7 +217,7 @@ async function handleError(args, error) {
   return { res, exitCode };
 }
 
-function getFullPath(filepathFragment: string): string {
+export function getFullPath(filepathFragment: string): string {
   if (pathLib.isAbsolute(filepathFragment)) {
     return filepathFragment;
   } else {